diff --git a/.github/workflows/solana.yml b/.github/workflows/solana.yml
new file mode 100644
index 000000000..04511a9e7
--- /dev/null
+++ b/.github/workflows/solana.yml
@@ -0,0 +1,203 @@
+name: Solana
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+concurrency:
+ group: solana-${{ github.ref }}
+ cancel-in-progress: true
+
+defaults:
+ run:
+ working-directory: ./chains/solana
+
+jobs:
+ get_anchor_version:
+ name: Get Anchor Version
+ runs-on: ubuntu-latest
+ outputs:
+ anchor_version: ${{ steps.anchorversion.outputs.anchor }}
+ steps:
+ - name: Checkout the repo
+ uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ - name: Get Anchor Version
+ id: anchorversion
+ run: |
+ anchor=$(make anchor_version)
+ echo "anchor=${anchor}" >>$GITHUB_OUTPUT
+
+ build_solana:
+ name: cache build artifacts
+ runs-on: ubuntu-latest-8cores-32GB
+ needs: [get_anchor_version]
+ steps:
+ - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ - name: cache docker build image
+ id: cache-image
+ uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
+ with:
+ lookup-only: true
+ path: chains/solana/contracts/docker-build.tar
+ key: ${{ runner.os }}-solana-build-${{ needs.get_anchor_version.outputs.anchor_version }}-${{ hashFiles('**/Cargo.lock') }}
+ - name: Cache cargo target dir
+ id: cache-target
+ uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
+ with:
+ lookup-only: true
+ path: chains/solana/contracts/target
+ key: ${{ runner.os }}-solana-contract-artifacts-${{ hashFiles('**/Cargo.lock') }}
+ - name: build & save image
+ if: steps.cache-image.outputs.cache-hit != 'true'
+ run: |
+ cd contracts
+ docker buildx build . -t ccip-solana:build --build-arg ANCHOR_CLI=${{ needs.get_anchor_version.outputs.anchor_version }}
+ docker save -o docker-build.tar ccip-solana
+ - name: build & save contract compilation artifacts
+ if: steps.cache-target.outputs.cache-hit != 'true'
+ run: |
+ docker run -v "$(pwd)/":/solana ccip-solana:build bash -c "\
+ set -eoux pipefail &&\
+ RUSTUP_HOME=\"/root/.rustup\" &&\
+ FORCE_COLOR=1 &&\
+ cd /solana/contracts &&\
+ anchor build &&\
+ chmod -R 755 ./target"
+
+ rust:
+ name: rust tests
+ runs-on: ubuntu-latest
+ needs: [get_anchor_version, build_solana]
+ steps:
+ - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ - name: Cache cargo target dir
+ uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
+ with:
+ fail-on-cache-miss: true
+ path: chains/solana/contracts/target
+ key: ${{ runner.os }}-solana-contract-artifacts-${{ hashFiles('**/Cargo.lock') }}
+ - name: cache docker build image
+ id: cache-image
+ uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
+ with:
+ fail-on-cache-miss: true
+ path: chains/solana/contracts/docker-build.tar
+ key: ${{ runner.os }}-solana-build-${{ needs.get_anchor_version.outputs.anchor_version }}-${{ hashFiles('**/Cargo.lock') }}
+ - name: load cached image
+ run: |
+ docker load --input contracts/docker-build.tar
+ - name: run tests
+ run: |
+ docker run -v "$(pwd)/":/solana ccip-solana:build bash -c "\
+ set -eoux pipefail &&\
+ RUSTUP_HOME=\"/root/.rustup\" &&\
+ FORCE_COLOR=1 &&\
+ cd /solana/contracts &&\
+ anchor build &&\
+ cargo check &&\
+ cargo clippy -- -D warnings &&\
+ cargo test --workspace"
+
+ go:
+ name: go tests
+ runs-on: ubuntu-latest-8cores-32GB
+ needs: [get_anchor_version, build_solana]
+ steps:
+ - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ - name: Cache cargo target dir
+ uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
+ with:
+ fail-on-cache-miss: true
+ path: chains/solana/contracts/target
+ key: ${{ runner.os }}-solana-contract-artifacts-${{ hashFiles('**/Cargo.lock') }}
+ - name: cache docker build image
+ id: cache-image
+ uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
+ with:
+ fail-on-cache-miss: true
+ path: chains/solana/contracts/docker-build.tar
+ key: ${{ runner.os }}-solana-build-${{ needs.get_anchor_version.outputs.anchor_version }}-${{ hashFiles('**/Cargo.lock') }}
+ - name: load cached image
+ run: |
+ docker load --input contracts/docker-build.tar
+ - name: Setup go
+ uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
+ with:
+ go-version-file: "./chains/solana/go.mod"
+ check-latest: true
+ cache-dependency-path: "./chains/solana/go.sum"
+ - name: Install gotestloghelper
+ run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/gotestloghelper@latest
+ - name: Install Solana CLI
+ run: |
+ sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" # always use latest stable release from solana
+ echo "PATH=$HOME/.local/share/solana/install/active_release/bin:$PATH" >> $GITHUB_ENV
+ - name: build + test
+ run: |
+ set -eoux pipefail
+ # compile artifacts
+ docker run -v "$(pwd)/":/solana ccip-solana:build bash -c "\
+ set -eoux pipefail &&\
+ RUSTUP_HOME=\"/root/.rustup\" &&\
+ FORCE_COLOR=1 &&\
+ cd /solana/contracts &&\
+ anchor build"
+ make go-tests
+
+ lint:
+ name: lint + check artifacts
+ runs-on: ubuntu-latest
+ needs: [get_anchor_version, build_solana]
+ steps:
+ - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ - name: Cache cargo target dir
+ uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
+ with:
+ fail-on-cache-miss: true
+ path: chains/solana/contracts/target
+ key: ${{ runner.os }}-solana-contract-artifacts-${{ hashFiles('**/Cargo.lock') }}
+ - name: cache docker build image
+ id: cache-image
+ uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
+ with:
+ fail-on-cache-miss: true
+ path: chains/solana/contracts/docker-build.tar
+ key: ${{ runner.os }}-solana-build-${{ needs.get_anchor_version.outputs.anchor_version }}-${{ hashFiles('**/Cargo.lock') }}
+ - name: load cached image
+ run: |
+ docker load --input contracts/docker-build.tar
+ - name: Setup go
+ uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
+ with:
+ go-version-file: "./chains/solana/go.mod"
+ check-latest: true
+ cache-dependency-path: "./chains/solana/go.sum"
+ - name: check artifacts
+ run: |
+ set -eoux pipefail
+ # compile artifacts
+ docker run -v "$(pwd)/":/solana ccip-solana:build bash -c "\
+ set -eoux pipefail &&\
+ RUSTUP_HOME=\"/root/.rustup\" &&\
+ FORCE_COLOR=1 &&\
+ cd /solana/contracts &&\
+ rm -rf target/idl &&\
+ anchor build"
+
+ go install github.com/gagliardetto/anchor-go@v0.2.3
+ ./scripts/anchor-go-gen.sh
+ make format
+ git diff --exit-code
+ - name: Install linter
+ run: |
+ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.62.2
+ - name: Run linter
+ run: make lint-go
+ - name: Print lint report artifact
+ if: failure()
+ shell: bash
+ run: cat ./golangci-lint-report.xml
+
+
diff --git a/CODEOWNERS b/CODEOWNERS
index 21f111605..f09b3c372 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,2 +1,5 @@
# 2024-12-04 - The codeowners file is correct
* @smartcontractkit/ccip-offchain
+
+# solana development ownership
+/chains/solana @smartcontractkit/ccip-onchain-solana
diff --git a/chains/solana/.gitignore b/chains/solana/.gitignore
new file mode 100644
index 000000000..dab408a66
--- /dev/null
+++ b/chains/solana/.gitignore
@@ -0,0 +1,29 @@
+.direnv
+*.DS_Store
+**/*.rs.bk
+.idea
+.vscode/
+
+# ignore target folders except IDLs
+contracts/target/*
+!contracts/target/idl/
+contracts/programs/*/target
+contracts/.anchor
+# keypair used by anchor/anchor test
+contracts/id.json
+# ignore all except shared localnet keys
+contracts/artifacts/*
+!contracts/artifacts/localnet/
+test-ledger
+
+# Test & linter reports
+.test_summary/
+*report.xml
+*report.json
+*.out
+*coverage*
+eslint-report.json
+.run.id
+override*.toml
+# go work files
+go.work*
diff --git a/chains/solana/.golangci.yml b/chains/solana/.golangci.yml
new file mode 100644
index 000000000..ab44ae4d2
--- /dev/null
+++ b/chains/solana/.golangci.yml
@@ -0,0 +1,156 @@
+run:
+ timeout: 15m0s
+linters:
+ enable:
+ - exhaustive
+ - exportloopref
+ - revive
+ - goimports
+ - gosec
+ - misspell
+ - rowserrcheck
+ - errorlint
+ - unconvert
+ - sqlclosecheck
+ - noctx
+ - whitespace
+ - depguard
+ - containedctx
+ - fatcontext
+ - mirror
+ - loggercheck
+linters-settings:
+ exhaustive:
+ default-signifies-exhaustive: true
+ goimports:
+ local-prefixes: github.com/smartcontractkit/chainlink-ccip/chains/solana
+ golint:
+ min-confidence: 1.0
+ gosec:
+ excludes:
+ - G101
+ - G104
+ # - G204
+ # - G304
+ # - G404
+ govet:
+ enable:
+ - shadow
+ settings:
+ printf:
+ # Additionally check custom logger
+ funcs:
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Debugf
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Infof
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Warnf
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Errorf
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Panicf
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Fatalf
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.SugaredLogger).AssumptionViolationf
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.SugaredLogger).Tracef
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.SugaredLogger).Criticalf
+ errorlint:
+ # Allow formatting of errors without %w
+ errorf: false
+ revive:
+ confidence: 0.8
+ rules:
+ - name: blank-imports
+ - name: context-as-argument
+ - name: context-keys-type
+ - name: dot-imports
+ - name: error-return
+ - name: error-strings
+ - name: error-naming
+ - name: exported
+ - name: if-return
+ - name: increment-decrement
+ - name: var-naming
+ - name: var-declaration
+ - name: package-comments
+ - name: range
+ - name: receiver-naming
+ - name: time-naming
+ # - name: unexported-return
+ - name: indent-error-flow
+ - name: errorf
+ - name: empty-block
+ - name: superfluous-else
+ # - name: unused-parameter
+ - name: unreachable-code
+ - name: redefines-builtin-id
+ - name: waitgroup-by-value
+ - name: unconditional-recursion
+ - name: struct-tag
+ # - name: string-format
+ - name: string-of-int
+ - name: range-val-address
+ - name: range-val-in-closure
+ - name: modifies-value-receiver
+ - name: modifies-parameter
+ - name: identical-branches
+ - name: get-return
+ # - name: flag-parameter
+ - name: early-return
+ - name: defer
+ - name: constant-logical-expr
+ # - name: confusing-naming
+ # - name: confusing-results
+ - name: bool-literal-in-expr
+ - name: atomic
+ depguard:
+ rules:
+ main:
+ list-mode: lax
+ deny:
+ - pkg: "cosmossdk.io/errors"
+ desc: Use the standard library instead
+ - pkg: "github.com/ethereum/go-ethereum"
+ desc: This is chain must be isolated from ethereum
+ - pkg: "github.com/go-gorm/gorm"
+ desc: Use github.com/jmoiron/sqlx directly instead
+ - pkg: "github.com/gofrs/uuid"
+ desc: Use github.com/google/uuid instead
+ - pkg: "github.com/pkg/errors"
+ desc: Use the standard library instead, for example https://pkg.go.dev/errors#Join
+ - pkg: "github.com/satori/go.uuid"
+ desc: Use github.com/google/uuid instead
+ - pkg: "github.com/test-go/testify/assert"
+ desc: Use github.com/stretchr/testify/assert instead
+ - pkg: "github.com/test-go/testify/mock"
+ desc: Use github.com/stretchr/testify/mock instead
+ - pkg: "github.com/test-go/testify/require"
+ desc: Use github.com/stretchr/testify/require instead
+ - pkg: "go.uber.org/multierr"
+ desc: Use the standard library instead, for example https://pkg.go.dev/errors#Join
+ - pkg: "gopkg.in/guregu/null.v1"
+ desc: Use gopkg.in/guregu/null.v4 instead
+ - pkg: "gopkg.in/guregu/null.v2"
+ desc: Use gopkg.in/guregu/null.v4 instead
+ - pkg: "gopkg.in/guregu/null.v3"
+ desc: Use gopkg.in/guregu/null.v4 instead
+ - pkg: github.com/go-gorm/gorm
+ desc: Use github.com/jmoiron/sqlx directly instead
+ loggercheck:
+ # Check that *w logging functions have even number of args (i.e., well formed key-value pairs).
+ rules:
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Debugw
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Infow
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Warnw
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Errorw
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Panicw
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.Logger).Fatalw
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.SugaredLogger).AssumptionViolationw
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.SugaredLogger).Tracew
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.SugaredLogger).Criticalw
+ - (github.com/smartcontractkit/chainlink-common/pkg/logger.SugaredLogger).With
+issues:
+ exclude-rules:
+ - path: test
+ text: "^G404:"
+ linters:
+ - gosec
+ - path: _test.go
+ text: "G115:" # ignore integer overflow in test conversions
+ linters:
+ - gosec
diff --git a/chains/solana/Makefile b/chains/solana/Makefile
new file mode 100644
index 000000000..af5099e63
--- /dev/null
+++ b/chains/solana/Makefile
@@ -0,0 +1,37 @@
+BIN_DIR = bin
+export GOPATH ?= $(shell go env GOPATH)
+export GO111MODULE ?= on
+export ANCHOR_VERSION ?=v0.29.0
+export ANCHOR_IMAGE ?= backpackapp/build:$(ANCHOR_VERSION)
+
+.PHONY: projectserum_version
+anchor_version:
+ @echo "${ANCHOR_VERSION}"
+
+.PHONY: gomods
+gomods: ## Install gomods
+ go install github.com/jmank88/gomods@v0.1.3
+
+.PHONY: gomodtidy
+gomodtidy: gomods
+ gomods tidy
+
+.PHONY: lint-go
+lint-go:
+ golangci-lint --max-issues-per-linter 0 --max-same-issues 0 --color=always --exclude=dot-imports --timeout 15m --out-format checkstyle:golangci-lint-report.xml run
+
+.PHONY: anchor-go-gen
+anchor-go-gen:
+ cd ./contracts && rm -rf ./target && anchor build && cd .. && ./scripts/anchor-go-gen.sh
+
+.PHONY: format
+format:
+ go fmt ./... && cd ./contracts && cargo fmt
+
+.PHONY: go-tests
+go-tests:
+ go test -v ./... -json -covermode=atomic -coverpkg=./... -coverprofile=integration_coverage.txt 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci=true -singlepackage=true -hidepassingtests=false -hidepassinglogs=false
+
+.PHONY: generate-idl
+generate-idl:
+ cd ./contracts && anchor build
diff --git a/chains/solana/README.md b/chains/solana/README.md
new file mode 100644
index 000000000..f83ddb986
--- /dev/null
+++ b/chains/solana/README.md
@@ -0,0 +1,28 @@
+# CCIP Solana Onchain
+
+- `contracts`: solana programs (rust) + tests (go) built on the anchor framework
+- `gobindings`: auto-generated go bindings for contracts using `anchor-go`
+- `scripts`: various scripts for generating artifacts
+
+## Dependencies
+
+- rust: https://www.rust-lang.org/tools/install
+- go: https://go.dev/doc/install
+- solana: https://docs.anza.xyz/cli/install
+- anchor: https://www.anchor-lang.com/docs/installation
+
+## Development
+
+```bash
+# install anchor-go if needed
+go install github.com/gagliardetto/anchor-go@v0.2.3
+
+# build contracts + IDL
+anchor build
+
+# go bindings need to be regenerated if contract changes were made
+./scrips/anchor-go-gen.sh
+
+# test contracts
+go test ./... -v -count=1 -failfast
+```
diff --git a/chains/solana/contracts/.dockerignore b/chains/solana/contracts/.dockerignore
new file mode 100644
index 000000000..3c3629e64
--- /dev/null
+++ b/chains/solana/contracts/.dockerignore
@@ -0,0 +1 @@
+node_modules
diff --git a/chains/solana/contracts/.env.template b/chains/solana/contracts/.env.template
new file mode 100644
index 000000000..817acdd50
--- /dev/null
+++ b/chains/solana/contracts/.env.template
@@ -0,0 +1,4 @@
+
+RPC_43113=RPC_FOR_AVAX_FUJI
+ON_RAMP_43113=ON_RAMP_ADDRESS_FOR_AVAX_FUJI
+SENDER_ADDRESS=YOUR_SENDER_ADDRESS_TO_FILTER
\ No newline at end of file
diff --git a/chains/solana/contracts/.prettierignore b/chains/solana/contracts/.prettierignore
new file mode 100644
index 000000000..b7f12190a
--- /dev/null
+++ b/chains/solana/contracts/.prettierignore
@@ -0,0 +1,2 @@
+pnpm-lock.yaml
+target
diff --git a/chains/solana/contracts/Anchor.toml b/chains/solana/contracts/Anchor.toml
new file mode 100644
index 000000000..be82ffac1
--- /dev/null
+++ b/chains/solana/contracts/Anchor.toml
@@ -0,0 +1,33 @@
+[toolchain]
+anchor_version = "0.29.0"
+
+[features]
+seeds = false
+skip-lint = false
+
+[registry]
+url = "https://anchor.projectserum.com"
+
+[provider]
+cluster = "localnet"
+wallet = "id.json"
+# wallet = "~/.config/solana/id.json"
+
+# [programs.mainnet]
+# TODO: add pubkeys
+
+# [programs.testnet]
+# TODO: add pubkeys
+
+# [programs.devnet]
+# TODO: add pubkeys
+
+[programs.localnet]
+access_controller = "9xi644bRR8birboDGdTiwBq3C7VEeR7VuamRYYXCubUW"
+ccip_receiver = "CtEVnHsQzhTNWav8skikiV2oF6Xx7r7uGGa8eCDQtTjH"
+ccip_invalid_receiver = "9Vjda3WU2gsJgE4VdU6QuDw8rfHLyigfFyWs3XDPNUn8"
+ccip_router = "C8WSPj3yyus1YN3yNB6YA5zStYtbjQWtpmKadmvyUXq8"
+token_pool = "GRvFSLwR7szpjgNEZbGe4HtxfJYXqySXuuRUAJDpu4WH"
+external_program_cpi_stub = "4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ"
+mcm = "6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX"
+timelock = "LoCoNsJFuhTkSQjfdDfn3yuwqhSYoPujmviRHVCzsqn"
diff --git a/chains/solana/contracts/Cargo.lock b/chains/solana/contracts/Cargo.lock
new file mode 100644
index 000000000..c0b1a372a
--- /dev/null
+++ b/chains/solana/contracts/Cargo.lock
@@ -0,0 +1,2759 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "access-controller"
+version = "1.0.1"
+dependencies = [
+ "anchor-lang",
+ "arrayvec 1.0.0",
+ "bytemuck",
+ "static_assertions",
+]
+
+[[package]]
+name = "aead"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+ "opaque-debug",
+]
+
+[[package]]
+name = "aes-gcm-siv"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "polyval",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "ahash"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom 0.2.12",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "ahash"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.2.12",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anchor-attribute-access-control"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e"
+dependencies = [
+ "anchor-syn",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "anchor-attribute-account"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400"
+dependencies = [
+ "anchor-syn",
+ "bs58 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "anchor-attribute-constant"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7"
+dependencies = [
+ "anchor-syn",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "anchor-attribute-error"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241"
+dependencies = [
+ "anchor-syn",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "anchor-attribute-event"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2"
+dependencies = [
+ "anchor-syn",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "anchor-attribute-program"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f"
+dependencies = [
+ "anchor-syn",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "anchor-derive-accounts"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c"
+dependencies = [
+ "anchor-syn",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "anchor-derive-serde"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe"
+dependencies = [
+ "anchor-syn",
+ "borsh-derive-internal 0.10.3",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "anchor-derive-space"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "anchor-lang"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad"
+dependencies = [
+ "anchor-attribute-access-control",
+ "anchor-attribute-account",
+ "anchor-attribute-constant",
+ "anchor-attribute-error",
+ "anchor-attribute-event",
+ "anchor-attribute-program",
+ "anchor-derive-accounts",
+ "anchor-derive-serde",
+ "anchor-derive-space",
+ "arrayref",
+ "base64 0.13.1",
+ "bincode",
+ "borsh 0.10.3",
+ "bytemuck",
+ "getrandom 0.2.12",
+ "solana-program",
+ "thiserror",
+]
+
+[[package]]
+name = "anchor-spl"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a"
+dependencies = [
+ "anchor-lang",
+ "solana-program",
+ "spl-associated-token-account",
+ "spl-token",
+ "spl-token-2022 0.9.0",
+]
+
+[[package]]
+name = "anchor-syn"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825"
+dependencies = [
+ "anyhow",
+ "bs58 0.5.0",
+ "heck",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2 0.10.8",
+ "syn 1.0.109",
+ "thiserror",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
+
+[[package]]
+name = "ark-bn254"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest 0.10.7",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest 0.10.7",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "arrayref"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
+name = "arrayvec"
+version = "1.0.0"
+
+[[package]]
+name = "assert_matches"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitmaps"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "blake3"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87"
+dependencies = [
+ "arrayref",
+ "arrayvec 0.7.4",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "block-padding",
+ "generic-array",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
+
+[[package]]
+name = "borsh"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa"
+dependencies = [
+ "borsh-derive 0.9.3",
+ "hashbrown 0.11.2",
+]
+
+[[package]]
+name = "borsh"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
+dependencies = [
+ "borsh-derive 0.10.3",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775"
+dependencies = [
+ "borsh-derive-internal 0.9.3",
+ "borsh-schema-derive-internal 0.9.3",
+ "proc-macro-crate 0.1.5",
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7"
+dependencies = [
+ "borsh-derive-internal 0.10.3",
+ "borsh-schema-derive-internal 0.10.3",
+ "proc-macro-crate 0.1.5",
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "borsh-derive-internal"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "borsh-derive-internal"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "borsh-schema-derive-internal"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "borsh-schema-derive-internal"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "bs58"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
+
+[[package]]
+name = "bs58"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "bv"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340"
+dependencies = [
+ "feature-probe",
+ "serde",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "ccip_invalid_receiver"
+version = "0.0.0-dev"
+dependencies = [
+ "anchor-lang",
+]
+
+[[package]]
+name = "ccip_receiver"
+version = "0.1.0-dev"
+dependencies = [
+ "anchor-lang",
+ "anchor-spl",
+ "solana-program",
+]
+
+[[package]]
+name = "ccip_router"
+version = "0.1.0-dev"
+dependencies = [
+ "anchor-lang",
+ "anchor-spl",
+ "bytemuck",
+ "hex",
+ "solana-program",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "cipher"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "console_log"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f"
+dependencies = [
+ "log",
+ "web-sys",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "ctr"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "3.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
+dependencies = [
+ "byteorder",
+ "digest 0.9.0",
+ "rand_core 0.5.1",
+ "serde",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "derivation-path"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0"
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer 0.10.4",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "ed25519"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7"
+dependencies = [
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "rand 0.7.3",
+ "serde",
+ "sha2 0.9.9",
+ "zeroize",
+]
+
+[[package]]
+name = "ed25519-dalek-bip32"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908"
+dependencies = [
+ "derivation-path",
+ "ed25519-dalek",
+ "hmac 0.12.1",
+ "sha2 0.10.8",
+]
+
+[[package]]
+name = "either"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
+
+[[package]]
+name = "env_logger"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "external-program-cpi-stub"
+version = "0.0.0-dev"
+dependencies = [
+ "anchor-lang",
+]
+
+[[package]]
+name = "feature-probe"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "serde",
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash 0.7.8",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
+dependencies = [
+ "crypto-mac",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "hmac-drbg"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1"
+dependencies = [
+ "digest 0.9.0",
+ "generic-array",
+ "hmac 0.8.1",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "im"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9"
+dependencies = [
+ "bitmaps",
+ "rand_core 0.6.4",
+ "rand_xoshiro",
+ "rayon",
+ "serde",
+ "sized-chunks",
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.3",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "jobserver"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
+dependencies = [
+ "cpufeatures",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "libsecp256k1"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73"
+dependencies = [
+ "arrayref",
+ "base64 0.12.3",
+ "digest 0.9.0",
+ "hmac-drbg",
+ "libsecp256k1-core",
+ "libsecp256k1-gen-ecmult",
+ "libsecp256k1-gen-genmult",
+ "rand 0.7.3",
+ "serde",
+ "sha2 0.9.9",
+ "typenum",
+]
+
+[[package]]
+name = "libsecp256k1-core"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80"
+dependencies = [
+ "crunchy",
+ "digest 0.9.0",
+ "subtle",
+]
+
+[[package]]
+name = "libsecp256k1-gen-ecmult"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3"
+dependencies = [
+ "libsecp256k1-core",
+]
+
+[[package]]
+name = "libsecp256k1-gen-genmult"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d"
+dependencies = [
+ "libsecp256k1-core",
+]
+
+[[package]]
+name = "light-poseidon"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee"
+dependencies = [
+ "ark-bn254",
+ "ark-ff",
+ "num-bigint",
+ "thiserror",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "mcm"
+version = "0.1.0-dev"
+dependencies = [
+ "anchor-lang",
+ "hex",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "memmap2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "merlin"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d"
+dependencies = [
+ "byteorder",
+ "keccak",
+ "rand_core 0.6.4",
+ "zeroize",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
+dependencies = [
+ "num_enum_derive 0.6.1",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
+dependencies = [
+ "num_enum_derive 0.7.2",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
+dependencies = [
+ "proc-macro-crate 3.1.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
+[[package]]
+name = "pbkdf2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd"
+dependencies = [
+ "crypto-mac",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+dependencies = [
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "polyval"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro-crate"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
+dependencies = [
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit 0.19.15",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
+dependencies = [
+ "toml_edit 0.21.0",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "qstring"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "qualifier_attr"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.12",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_xoshiro"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rayon"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "semver"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
+
+[[package]]
+name = "serde"
+version = "1.0.196"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_bytes"
+version = "0.11.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.196"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.113"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe"
+dependencies = [
+ "serde",
+ "serde_with_macros",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha3"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
+dependencies = [
+ "block-buffer 0.9.0",
+ "digest 0.9.0",
+ "keccak",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha3"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
+dependencies = [
+ "digest 0.10.7",
+ "keccak",
+]
+
+[[package]]
+name = "signature"
+version = "1.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
+
+[[package]]
+name = "sized-chunks"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
+dependencies = [
+ "bitmaps",
+ "typenum",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+
+[[package]]
+name = "solana-frozen-abi"
+version = "1.17.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de577bb681dfc3afeda6247dbc381f8c74a31eeed141883e6a9a36e93fdcf784"
+dependencies = [
+ "ahash 0.8.5",
+ "blake3",
+ "block-buffer 0.10.4",
+ "bs58 0.4.0",
+ "bv",
+ "byteorder",
+ "cc",
+ "either",
+ "generic-array",
+ "im",
+ "lazy_static",
+ "log",
+ "memmap2",
+ "rustc_version",
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "serde_json",
+ "sha2 0.10.8",
+ "solana-frozen-abi-macro",
+ "subtle",
+ "thiserror",
+]
+
+[[package]]
+name = "solana-frozen-abi-macro"
+version = "1.17.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6373184605334be54d85564b657e7b4d88bdf4e3c011abccce4fd2712c96caf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "solana-logger"
+version = "1.17.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6959774302d4407c77d5fbdd4d5e31c2696f5ac1c74bf0cdcac704b474bc6fd"
+dependencies = [
+ "env_logger",
+ "lazy_static",
+ "log",
+]
+
+[[package]]
+name = "solana-program"
+version = "1.17.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd3bcc37b433d7e8d45236a0f5aa68df462c4d5c6a709a6efd916988ce3ac08"
+dependencies = [
+ "ark-bn254",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "base64 0.21.7",
+ "bincode",
+ "bitflags 2.4.2",
+ "blake3",
+ "borsh 0.10.3",
+ "borsh 0.9.3",
+ "bs58 0.4.0",
+ "bv",
+ "bytemuck",
+ "cc",
+ "console_error_panic_hook",
+ "console_log",
+ "curve25519-dalek",
+ "getrandom 0.2.12",
+ "itertools",
+ "js-sys",
+ "lazy_static",
+ "libc",
+ "libsecp256k1",
+ "light-poseidon",
+ "log",
+ "memoffset",
+ "num-bigint",
+ "num-derive 0.3.3",
+ "num-traits",
+ "parking_lot",
+ "rand 0.8.5",
+ "rustc_version",
+ "rustversion",
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "serde_json",
+ "sha2 0.10.8",
+ "sha3 0.10.8",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-sdk-macro",
+ "thiserror",
+ "tiny-bip39",
+ "wasm-bindgen",
+ "zeroize",
+]
+
+[[package]]
+name = "solana-sdk"
+version = "1.17.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1de78b8c4fa09e4b90d720b2aa3ef3c80c4b956aa3d14616261a7f4bdf64c04"
+dependencies = [
+ "assert_matches",
+ "base64 0.21.7",
+ "bincode",
+ "bitflags 2.4.2",
+ "borsh 0.10.3",
+ "bs58 0.4.0",
+ "bytemuck",
+ "byteorder",
+ "chrono",
+ "derivation-path",
+ "digest 0.10.7",
+ "ed25519-dalek",
+ "ed25519-dalek-bip32",
+ "generic-array",
+ "hmac 0.12.1",
+ "itertools",
+ "js-sys",
+ "lazy_static",
+ "libsecp256k1",
+ "log",
+ "memmap2",
+ "num-derive 0.3.3",
+ "num-traits",
+ "num_enum 0.6.1",
+ "pbkdf2 0.11.0",
+ "qstring",
+ "qualifier_attr",
+ "rand 0.7.3",
+ "rand 0.8.5",
+ "rustc_version",
+ "rustversion",
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "serde_json",
+ "serde_with",
+ "sha2 0.10.8",
+ "sha3 0.10.8",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-logger",
+ "solana-program",
+ "solana-sdk-macro",
+ "thiserror",
+ "uriparse",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "solana-sdk-macro"
+version = "1.17.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b5055c4b785cf3e5f2f52d687bdd1a795755105fe4365182396bc8b6bb41cd5"
+dependencies = [
+ "bs58 0.4.0",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "solana-security-txt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183"
+
+[[package]]
+name = "solana-zk-token-sdk"
+version = "1.17.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9e0b222c3aad3df370ae87e993b32419906f00827a1677b7c814c65c9682909"
+dependencies = [
+ "aes-gcm-siv",
+ "base64 0.21.7",
+ "bincode",
+ "bytemuck",
+ "byteorder",
+ "curve25519-dalek",
+ "getrandom 0.1.16",
+ "itertools",
+ "lazy_static",
+ "merlin",
+ "num-derive 0.3.3",
+ "num-traits",
+ "rand 0.7.3",
+ "serde",
+ "serde_json",
+ "sha3 0.9.1",
+ "solana-program",
+ "solana-sdk",
+ "subtle",
+ "thiserror",
+ "zeroize",
+]
+
+[[package]]
+name = "spl-associated-token-account"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4414117bead33f2a5cf059cefac0685592bdd36f31f3caac49b89bff7f6bbf32"
+dependencies = [
+ "assert_matches",
+ "borsh 0.10.3",
+ "num-derive 0.4.2",
+ "num-traits",
+ "solana-program",
+ "spl-token",
+ "spl-token-2022 2.0.1",
+ "thiserror",
+]
+
+[[package]]
+name = "spl-discriminator"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daa600f2fe56f32e923261719bae640d873edadbc5237681a39b8e37bfd4d263"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator-derive",
+]
+
+[[package]]
+name = "spl-discriminator-derive"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93"
+dependencies = [
+ "quote",
+ "spl-discriminator-syn",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "spl-discriminator-syn"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sha2 0.10.8",
+ "syn 2.0.48",
+ "thiserror",
+]
+
+[[package]]
+name = "spl-math"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "102d87a7608c793497fa3e85e9d0e24a33be06a94fb029b2cd30d794295f8110"
+dependencies = [
+ "borsh 0.10.3",
+ "num-derive 0.3.3",
+ "num-traits",
+ "solana-program",
+ "thiserror",
+ "uint",
+]
+
+[[package]]
+name = "spl-memo"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58e9bae02de3405079a057fe244c867a08f92d48327d231fc60da831f94caf0a"
+dependencies = [
+ "solana-program",
+]
+
+[[package]]
+name = "spl-pod"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85a5db7e4efb1107b0b8e52a13f035437cdcb36ef99c58f6d467f089d9b2915a"
+dependencies = [
+ "borsh 0.10.3",
+ "bytemuck",
+ "solana-program",
+ "solana-zk-token-sdk",
+ "spl-program-error",
+]
+
+[[package]]
+name = "spl-program-error"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e0657b6490196971d9e729520ba934911ff41fbb2cb9004463dbe23cf8b4b4f"
+dependencies = [
+ "num-derive 0.4.2",
+ "num-traits",
+ "solana-program",
+ "spl-program-error-derive",
+ "thiserror",
+]
+
+[[package]]
+name = "spl-program-error-derive"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1845dfe71fd68f70382232742e758557afe973ae19e6c06807b2c30f5d5cb474"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sha2 0.10.8",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "spl-tlv-account-resolution"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-type-length-value",
+]
+
+[[package]]
+name = "spl-tlv-account-resolution"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f335787add7fa711819f9e7c573f8145a5358a709446fe2d24bf2a88117c90"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-type-length-value",
+]
+
+[[package]]
+name = "spl-token"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95ae123223633a389f95d1da9d49c2d0a50d499e7060b9624626a69e536ad2a4"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "num-derive 0.4.2",
+ "num-traits",
+ "num_enum 0.7.2",
+ "solana-program",
+ "thiserror",
+]
+
+[[package]]
+name = "spl-token-2022"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "num-derive 0.4.2",
+ "num-traits",
+ "num_enum 0.7.2",
+ "solana-program",
+ "solana-zk-token-sdk",
+ "spl-memo",
+ "spl-pod",
+ "spl-token",
+ "spl-token-metadata-interface",
+ "spl-transfer-hook-interface 0.3.0",
+ "spl-type-length-value",
+ "thiserror",
+]
+
+[[package]]
+name = "spl-token-2022"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9fec83597cf7be923c5c3bdfd2fcc08cdfacd2eeb6c4e413da06b6916f50827"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "num-derive 0.4.2",
+ "num-traits",
+ "num_enum 0.7.2",
+ "solana-program",
+ "solana-security-txt",
+ "solana-zk-token-sdk",
+ "spl-memo",
+ "spl-pod",
+ "spl-token",
+ "spl-token-group-interface",
+ "spl-token-metadata-interface",
+ "spl-transfer-hook-interface 0.5.1",
+ "spl-type-length-value",
+ "thiserror",
+]
+
+[[package]]
+name = "spl-token-group-interface"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eb67fbacd587377a400aba81718abe4424d0e9d5ea510034d3b7f130d102153"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+]
+
+[[package]]
+name = "spl-token-metadata-interface"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e16aa8f64b6e0eaab3f5034e84d867c8435d8216497b4543a4978a31f4b6e8a8"
+dependencies = [
+ "borsh 0.10.3",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-type-length-value",
+]
+
+[[package]]
+name = "spl-transfer-hook-interface"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-tlv-account-resolution 0.4.0",
+ "spl-type-length-value",
+]
+
+[[package]]
+name = "spl-transfer-hook-interface"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6dfe329fcff44cbe2eea994bd8f737f0b0a69faed39e56f9b6ee03badf7e14"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+ "spl-tlv-account-resolution 0.5.2",
+ "spl-type-length-value",
+]
+
+[[package]]
+name = "spl-type-length-value"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f9ebd75d29c5f48de5f6a9c114e08531030b75b8ac2c557600ac7da0b73b1e8"
+dependencies = [
+ "bytemuck",
+ "solana-program",
+ "spl-discriminator",
+ "spl-pod",
+ "spl-program-error",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "timelock"
+version = "0.0.1-dev"
+dependencies = [
+ "access-controller",
+ "anchor-lang",
+ "bytemuck",
+ "hex",
+]
+
+[[package]]
+name = "tiny-bip39"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d"
+dependencies = [
+ "anyhow",
+ "hmac 0.8.1",
+ "once_cell",
+ "pbkdf2 0.4.0",
+ "rand 0.7.3",
+ "rustc-hash",
+ "sha2 0.9.9",
+ "thiserror",
+ "unicode-normalization",
+ "wasm-bindgen",
+ "zeroize",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "token-pool"
+version = "0.1.0-dev"
+dependencies = [
+ "anchor-lang",
+ "anchor-spl",
+ "solana-program",
+ "spl-math",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "uint"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52"
+dependencies = [
+ "byteorder",
+ "crunchy",
+ "hex",
+ "static_assertions",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
+
+[[package]]
+name = "universal-hash"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "uriparse"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff"
+dependencies = [
+ "fnv",
+ "lazy_static",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
+
+[[package]]
+name = "web-sys"
+version = "0.3.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
diff --git a/chains/solana/contracts/Cargo.toml b/chains/solana/contracts/Cargo.toml
new file mode 100644
index 000000000..082093f25
--- /dev/null
+++ b/chains/solana/contracts/Cargo.toml
@@ -0,0 +1,18 @@
+[workspace]
+resolver = "2"
+members = [
+ "programs/*",
+ "crates/*",
+]
+
+[profile.dev]
+overflow-checks = true
+
+[profile.release]
+overflow-checks = true
+lto = "fat"
+codegen-units = 1
+# -Znew-llvm-pass-manager
+
+[profile.test]
+overflow-checks = true
diff --git a/chains/solana/contracts/Dockerfile b/chains/solana/contracts/Dockerfile
new file mode 100644
index 000000000..2ad1ddf22
--- /dev/null
+++ b/chains/solana/contracts/Dockerfile
@@ -0,0 +1,10 @@
+ARG ANCHOR_CLI
+
+FROM backpackapp/build:${ANCHOR_CLI}
+
+COPY . /contracts
+
+RUN cd /contracts && anchor build
+
+# only keep downloaded artifacts in /root/.cargo cached
+RUN rm -rf /contracts
diff --git a/chains/solana/contracts/README.md b/chains/solana/contracts/README.md
new file mode 100644
index 000000000..09242816e
--- /dev/null
+++ b/chains/solana/contracts/README.md
@@ -0,0 +1,97 @@
+# Chainlink Solana contracts (programs)
+
+## Prerequisites
+
+Install Rust, Solana & Anchor. See https://solana.com/docs/intro/installation
+
+## Build
+
+To build on the host:
+
+```
+anchor build
+```
+
+To build inside a docker environment:
+
+```bash
+anchor build --verifiable
+```
+
+To build for a specific network, specify via a cargo feature:
+
+```bash
+anchor build -- --features mainnet
+```
+
+Available networks with declared IDs:
+
+- mainnet
+- testnet
+- devnet
+- localnet (default)
+
+## Test
+
+Make sure to run `pnpm i` to fetch mocha and other test dependencies.
+
+Start a dockerized shell that contains Solana and Anchor:
+
+```bash
+./scripts/anchor-shell.sh
+```
+
+Next, generate a keypair for anchor:
+
+```bash
+solana-keygen new -o id.json
+```
+
+### Run anchor TypeScript tests (automatically tests against a local node)
+
+```bash
+anchor test
+```
+
+### Run GoLang tests (automatically tests against a local node)
+
+Pre-requisites:
+
+- Have the `solana-test-validator` command installed
+- Run `anchor build` if there have been any changes to the program under test.
+
+```bash
+make contracts-go-tests
+```
+
+#### `anchor-go` bindings generation
+
+Install `https://github.com/gagliardetto/anchor-go`
+
+Current version: [v0.2.3](https://github.com/gagliardetto/anchor-go/tree/v0.2.3)
+
+To install `anchor-go` locally so that you can use the `anchor-go` command globally, follow these steps:
+
+1. **Clone the repository:**
+
+ ```bash
+ git clone https://github.com/gagliardetto/anchor-go.git
+ cd anchor-go
+ git checkout v0.2.3
+ ```
+
+2. **Install the command globally:**
+
+ Run the following command to install the `anchor-go` command globally:
+
+ ```bash
+ go install
+ ```
+
+ This will install the `anchor-go` binary to your `$GOPATH/bin` directory. Make sure that this directory is included in your system's `PATH` environment variable.
+
+3. **Then run the following command to generate the Go bindings:**
+
+ ```bash
+ make anchor-go-gen
+ ```
diff --git a/chains/solana/contracts/crates/arrayvec/Cargo.toml b/chains/solana/contracts/crates/arrayvec/Cargo.toml
new file mode 100644
index 000000000..4e9ad51cc
--- /dev/null
+++ b/chains/solana/contracts/crates/arrayvec/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "arrayvec"
+version = "1.0.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/chains/solana/contracts/crates/arrayvec/src/lib.rs b/chains/solana/contracts/crates/arrayvec/src/lib.rs
new file mode 100644
index 000000000..dfc8a12e3
--- /dev/null
+++ b/chains/solana/contracts/crates/arrayvec/src/lib.rs
@@ -0,0 +1,165 @@
+//! This is a tiny arrayvec implementation that efficiently implements a few common operations
+//! We're able to simplify the code significantly due to the elements being Pod/Zeroable.
+
+// use anchor_lang::prelude::*;
+// #[zero_copy]
+// pub struct $name {
+// pub xs: [$ty; $capacity],
+// pub len: $capacity_ty,
+// }
+// impl Default for $name {
+// pub fn default() -> Self {
+// Self {
+// len: 0,
+// xs: [<$ty>::default(); $capacity],
+// }
+// }
+// }
+
+#[macro_export]
+macro_rules! arrayvec {
+ ($name:ident, $ty:ty, $capacity_ty:ty) => {
+ #[allow(unused)]
+ impl $name {
+ #[inline(always)]
+ pub fn len(&self) -> usize {
+ self.len as usize
+ }
+
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+
+ #[inline(always)]
+ pub fn capacity(&self) -> usize {
+ self.xs.len()
+ }
+
+ // remaining_capacity
+ #[inline]
+ pub fn remaining_capacity(&self) -> usize {
+ self.capacity() - self.len()
+ }
+
+ pub fn push(&mut self, element: $ty) {
+ assert!(self.len() < self.capacity());
+ self.xs[self.len as usize] = element;
+ self.len += 1;
+ }
+
+ pub fn clear(&mut self) {
+ self.len = 0;
+ // TODO: we can also zero out the array for safety
+ // self.xs = [<$ty>::default(); capacity];
+ }
+
+ pub fn remove(&mut self, index: usize) -> $ty {
+ debug_assert!(index < self.len()); // this will also be asserted by xs[]
+ let element = self.xs[index];
+ // move index+1..len back by one
+ self.xs.copy_within(index + 1..self.len as usize, index);
+ // TODO: clear out the last element for safety?
+ self.len -= 1;
+ element
+ }
+
+ pub fn insert(&mut self, index: usize, element: $ty) {
+ assert!(self.len() < self.capacity());
+ debug_assert!(index <= self.len());
+
+ // move index..len forward by one
+ self.xs.copy_within(index..self.len as usize, index + 1);
+ self.len += 1;
+ self.xs[index] = element;
+ }
+
+ #[inline]
+ pub fn as_slice(&self) -> &[$ty] {
+ &self.xs[..self.len as usize]
+ }
+
+ #[inline]
+ pub fn as_mut_slice(&mut self) -> &mut [$ty] {
+ &mut self.xs[..self.len as usize]
+ }
+
+ pub fn extend(&mut self, data: &[$ty]) {
+ let len = data.len();
+ let offset = self.len();
+ self.xs[offset..offset + len].copy_from_slice(&data);
+ self.len += len as $capacity_ty;
+ }
+ }
+
+ impl std::ops::Deref for $name {
+ type Target = [$ty];
+
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ self.as_slice()
+ }
+ }
+
+ impl std::ops::DerefMut for $name {
+ #[inline]
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.as_mut_slice()
+ }
+ }
+ };
+}
+
+#[cfg(test)]
+mod tests {
+ pub struct ArrayVec {
+ pub xs: [u8; 3],
+ pub len: u32,
+ }
+ impl ArrayVec {
+ pub fn new() -> Self {
+ Self {
+ len: 0,
+ xs: [u8::default(); 3],
+ }
+ }
+ }
+ arrayvec!(ArrayVec, u8, u32);
+
+ #[test]
+ fn remove() {
+ let mut vec = ArrayVec::new();
+ vec.push(1);
+ vec.push(2);
+ vec.push(3);
+
+ let el = vec.remove(2);
+ assert_eq!(el, 3);
+ assert_eq!(vec.len(), 2);
+ assert_eq!(vec.as_slice(), &[1, 2]);
+
+ let el = vec.remove(0);
+ assert_eq!(el, 1);
+ assert_eq!(vec.len(), 1);
+ assert_eq!(vec.as_slice(), &[2]);
+ }
+
+ #[test]
+ fn insert() {
+ let mut vec = ArrayVec::new();
+ vec.insert(0, 3);
+ vec.insert(0, 1);
+ vec.insert(1, 2);
+ assert_eq!(vec.as_slice(), &[1, 2, 3]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn insert_overflow() {
+ let mut vec = ArrayVec::new();
+ vec.push(1);
+ vec.push(2);
+ vec.push(3);
+ vec.insert(3, 4);
+ }
+}
diff --git a/chains/solana/contracts/programs/access-controller/Cargo.toml b/chains/solana/contracts/programs/access-controller/Cargo.toml
new file mode 100644
index 000000000..58d72b8dc
--- /dev/null
+++ b/chains/solana/contracts/programs/access-controller/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "access-controller"
+version = "1.0.1"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "access_controller"
+
+[features]
+no-entrypoint = []
+no-idl = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.29.0"
+bytemuck = { version = "1.4.0", features = ["derive", "min_const_generics"]}
+static_assertions = "1.1.0"
+arrayvec = { version = "1.0.0", path = "../../crates/arrayvec" }
diff --git a/chains/solana/contracts/programs/access-controller/Xargo.toml b/chains/solana/contracts/programs/access-controller/Xargo.toml
new file mode 100644
index 000000000..475fb71ed
--- /dev/null
+++ b/chains/solana/contracts/programs/access-controller/Xargo.toml
@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []
diff --git a/chains/solana/contracts/programs/access-controller/src/lib.rs b/chains/solana/contracts/programs/access-controller/src/lib.rs
new file mode 100644
index 000000000..e2fc584e0
--- /dev/null
+++ b/chains/solana/contracts/programs/access-controller/src/lib.rs
@@ -0,0 +1,141 @@
+use anchor_lang::prelude::*;
+use static_assertions::const_assert;
+use std::mem;
+
+use arrayvec::arrayvec;
+
+declare_id!("9xi644bRR8birboDGdTiwBq3C7VEeR7VuamRYYXCubUW");
+
+#[error_code]
+pub enum ErrorCode {
+ #[msg("Unauthorized")]
+ Unauthorized = 0,
+
+ #[msg("Invalid input")]
+ InvalidInput = 1,
+
+ #[msg("Access list is full")]
+ Full = 2,
+}
+
+#[constant]
+pub const MAX_ADDRS: usize = 64;
+
+#[zero_copy]
+pub struct AccessList {
+ xs: [Pubkey; MAX_ADDRS],
+ len: u64,
+}
+arrayvec!(AccessList, Pubkey, u64);
+const_assert!(
+ mem::size_of::() == mem::size_of::() + mem::size_of::() * MAX_ADDRS
+);
+
+#[account(zero_copy)]
+pub struct AccessController {
+ pub owner: Pubkey,
+ pub proposed_owner: Pubkey,
+ pub access_list: AccessList,
+}
+
+#[program]
+pub mod access_controller {
+ use super::*;
+ pub fn initialize(ctx: Context) -> Result<()> {
+ let mut state = ctx.accounts.state.load_init()?;
+ state.owner = ctx.accounts.owner.key();
+ Ok(())
+ }
+
+ pub fn transfer_ownership(
+ ctx: Context,
+ proposed_owner: Pubkey,
+ ) -> Result<()> {
+ let state = &mut *ctx.accounts.state.load_mut()?;
+ state.proposed_owner = proposed_owner;
+ Ok(())
+ }
+
+ pub fn accept_ownership(ctx: Context) -> Result<()> {
+ let state = &mut *ctx.accounts.state.load_mut()?;
+ state.owner = std::mem::take(&mut state.proposed_owner);
+ Ok(())
+ }
+
+ pub fn add_access(ctx: Context) -> Result<()> {
+ let mut state = ctx.accounts.state.load_mut()?;
+ // if the len reaches array len, we're at capacity
+ require!(state.access_list.remaining_capacity() > 0, ErrorCode::Full);
+
+ let address = ctx.accounts.address.key();
+
+ match state.access_list.binary_search(&address) {
+ // already present
+ Ok(_i) => (),
+ // not found, insert
+ Err(i) => state.access_list.insert(i, address),
+ }
+ Ok(())
+ }
+
+ pub fn remove_access(ctx: Context) -> Result<()> {
+ let mut state = ctx.accounts.state.load_mut()?;
+ let address = ctx.accounts.address.key();
+
+ let index = state.access_list.binary_search(&address);
+ if let Ok(index) = index {
+ state.access_list.remove(index);
+ // we don't need to sort again since the list is still sorted
+ }
+ Ok(())
+ }
+}
+
+/// Check if `address` is on the access control list.
+pub fn has_access(loader: &AccountLoader, address: &Pubkey) -> Result {
+ let state = loader.load()?;
+ Ok(state.access_list.binary_search(address).is_ok())
+}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+ #[account(zero)]
+ pub state: AccountLoader<'info, AccessController>,
+ pub owner: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct TransferOwnership<'info> {
+ #[account(mut)]
+ pub state: AccountLoader<'info, AccessController>,
+ #[account(address = state.load()?.owner @ ErrorCode::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct AcceptOwnership<'info> {
+ #[account(mut)]
+ pub state: AccountLoader<'info, AccessController>,
+ #[account(address = state.load()?.proposed_owner @ ErrorCode::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct AddAccess<'info> {
+ #[account(mut, has_one = owner)]
+ pub state: AccountLoader<'info, AccessController>,
+ #[account(address = state.load()?.owner @ ErrorCode::Unauthorized)]
+ pub owner: Signer<'info>,
+ /// CHECK: We don't impose any limits since this could be any signer.
+ pub address: UncheckedAccount<'info>,
+}
+
+#[derive(Accounts)]
+pub struct RemoveAccess<'info> {
+ #[account(mut, has_one = owner)]
+ pub state: AccountLoader<'info, AccessController>,
+ #[account(address = state.load()?.owner @ ErrorCode::Unauthorized)]
+ pub owner: Signer<'info>,
+ /// CHECK: We don't impose any limits since this could be any signer.
+ pub address: UncheckedAccount<'info>,
+}
diff --git a/chains/solana/contracts/programs/ccip-invalid-receiver/Cargo.toml b/chains/solana/contracts/programs/ccip-invalid-receiver/Cargo.toml
new file mode 100644
index 000000000..104ef5f34
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-invalid-receiver/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "ccip_invalid_receiver"
+version = "0.0.0-dev"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "ccip_invalid_receiver"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.29.0"
diff --git a/chains/solana/contracts/programs/ccip-invalid-receiver/Xargo.toml b/chains/solana/contracts/programs/ccip-invalid-receiver/Xargo.toml
new file mode 100644
index 000000000..475fb71ed
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-invalid-receiver/Xargo.toml
@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []
diff --git a/chains/solana/contracts/programs/ccip-invalid-receiver/src/lib.rs b/chains/solana/contracts/programs/ccip-invalid-receiver/src/lib.rs
new file mode 100644
index 000000000..290e7b232
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-invalid-receiver/src/lib.rs
@@ -0,0 +1,62 @@
+/**
+ * This program an example of a Invalid CCIP Receiver Program.
+ * Used to test CCIP Router execute and check that it fails
+ */
+use anchor_lang::prelude::*;
+
+declare_id!("9Vjda3WU2gsJgE4VdU6QuDw8rfHLyigfFyWs3XDPNUn8");
+
+#[program]
+pub mod ccip_invalid_receiver {
+ use super::*;
+
+ pub fn ccip_receive(ctx: Context, _message: Any2SolanaMessage) -> Result<()> {
+ msg!("Not reachable due to uninitialized accounts");
+
+ let counter = &mut ctx.accounts.counter;
+ counter.value = 1;
+
+ Ok(())
+ }
+}
+
+const ANCHOR_DISCRIMINATOR: usize = 8;
+
+#[derive(Accounts, Debug)]
+pub struct Initialize<'info> {
+ // router CPI signer must be first
+ #[account(mut)]
+ pub authority: Signer<'info>,
+
+ #[account(
+ init,
+ seeds = [b"counter"],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + Counter::INIT_SPACE,
+ )]
+ pub counter: Account<'info, Counter>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[account]
+#[derive(InitSpace, Debug)]
+pub struct Counter {
+ pub value: u8,
+}
+
+#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct Any2SolanaMessage {
+ pub message_id: [u8; 32],
+ pub source_chain_selector: u64,
+ pub sender: Vec,
+ pub data: Vec,
+ pub token_amounts: Vec,
+}
+
+#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, Default)]
+pub struct SolanaTokenAmount {
+ pub token: Pubkey,
+ pub amount: u64, // TODO: EVM uses u256
+}
diff --git a/chains/solana/contracts/programs/ccip-receiver/Cargo.toml b/chains/solana/contracts/programs/ccip-receiver/Cargo.toml
new file mode 100644
index 000000000..10fc7883a
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-receiver/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "ccip_receiver"
+version = "0.1.0-dev"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "ccip_receiver"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+solana-program = "1.17.25" # pin solana to 1.17
+anchor-lang = { version = "0.29.0", features = [] }
+anchor-spl = "0.29.0"
diff --git a/chains/solana/contracts/programs/ccip-receiver/Xargo.toml b/chains/solana/contracts/programs/ccip-receiver/Xargo.toml
new file mode 100644
index 000000000..475fb71ed
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-receiver/Xargo.toml
@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []
diff --git a/chains/solana/contracts/programs/ccip-receiver/src/lib.rs b/chains/solana/contracts/programs/ccip-receiver/src/lib.rs
new file mode 100644
index 000000000..9ddd5b1e4
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-receiver/src/lib.rs
@@ -0,0 +1,197 @@
+use anchor_lang::prelude::*;
+use anchor_spl::{token_interface::Mint, token_interface::TokenAccount};
+use solana_program::pubkey;
+declare_id!("CtEVnHsQzhTNWav8skikiV2oF6Xx7r7uGGa8eCDQtTjH");
+
+pub const EXTERNAL_EXECUTION_CONFIG_SEED: &[u8] = b"external_execution_config";
+
+/// This program an example of a CCIP Receiver Program.
+/// Used to test CCIP Router execute.
+#[program]
+pub mod ccip_receiver {
+ use solana_program::{instruction::Instruction, program::invoke_signed};
+
+ use super::*;
+
+ /// The initialization is responsibility of the External User, CCIP is not handling initialization of Accounts
+ pub fn initialize(ctx: Context) -> Result<()> {
+ msg!("Called `initialize` {:?}", ctx);
+ ctx.accounts.counter.value = 0;
+ Ok(())
+ }
+
+ /// This function is called by the CCIP Router to execute the CCIP message.
+ /// The method name needs to be ccip_receive with Anchor encoding,
+ /// if not using Anchor the discriminator needs to be [0x0b, 0xf4, 0x09, 0xf9, 0x2c, 0x53, 0x2f, 0xf5]
+ /// You can send as many accounts as you need, specifying if mutable or not.
+ /// But none of them could be an init, realloc or close.
+ /// In this case, it increments the counter value by 1 and logs the parsed message.
+ pub fn ccip_receive(ctx: Context, message: Any2SolanaMessage) -> Result<()> {
+ msg!("Called `ccip_receive` with message {:?}", message);
+
+ let counter = &mut ctx.accounts.counter;
+ counter.value += 1;
+
+ // additional accounts trigger additional CPI call
+ if !ctx.remaining_accounts.is_empty() {
+ let external_execution_config = &ctx.accounts.external_execution_config;
+ let (target_program, acc_infos) = ctx.remaining_accounts.split_at(1);
+
+ let acc_metas: Vec = acc_infos
+ .to_vec()
+ .iter()
+ .flat_map(|acc_info| {
+ // Check signer from PDA External Execution config
+ let is_signer = acc_info.key() == external_execution_config.key();
+ acc_info.to_account_metas(Some(is_signer))
+ })
+ .collect();
+
+ let instruction = Instruction {
+ program_id: target_program[0].key(),
+ accounts: acc_metas,
+ data: message.data,
+ };
+
+ let seeds = &[
+ EXTERNAL_EXECUTION_CONFIG_SEED,
+ &[ctx.bumps.external_execution_config],
+ ];
+ let signer = &[&seeds[..]];
+
+ invoke_signed(&instruction, acc_infos, signer)?;
+ };
+
+ Ok(())
+ }
+
+ // these functions are called by CCIP token pools that wrap non-token programs
+ // if not using Anchor, the discriminator must be:
+ // const RELEASE_MINT: [u8; 8] = [0x14, 0x94, 0x71, 0xc6, 0xe5, 0xaa, 0x47, 0x30];
+ // const LOCK_BURN: [u8; 8] = [0xc8, 0x0e, 0x32, 0x09, 0x2c, 0x5b, 0x79, 0x25];
+ pub fn ccip_token_release_mint(
+ _ctx: Context,
+ input: ReleaseOrMintInV1,
+ ) -> Result<()> {
+ msg!("Called `ccip_token_release_mint` with message {:?}", input);
+ Ok(())
+ }
+ pub fn ccip_token_lock_burn(_ctx: Context, input: LockOrBurnInV1) -> Result<()> {
+ msg!("Called `ccip_token_lock_burn` with message {:?}", input);
+ Ok(())
+ }
+}
+
+const ANCHOR_DISCRIMINATOR: usize = 8;
+
+#[derive(Accounts, Debug)]
+pub struct Initialize<'info> {
+ #[account(
+ init,
+ seeds = [b"counter"],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + Counter::INIT_SPACE,
+ )]
+ pub counter: Account<'info, Counter>,
+
+ #[account(
+ init,
+ seeds = [EXTERNAL_EXECUTION_CONFIG_SEED],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + ExternalExecutionConfig::INIT_SPACE,
+ )]
+ pub external_execution_config: Account<'info, ExternalExecutionConfig>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts, Debug)]
+pub struct SetData<'info> {
+ // router CPI signer must be first
+ #[account(owner = CCIP_ROUTER)]
+ pub authority: Signer<'info>,
+ // ccip router expects "receiver" to be second
+ /// CHECK: Using this to sign
+ #[account(mut, seeds = [EXTERNAL_EXECUTION_CONFIG_SEED], bump)]
+ pub external_execution_config: Account<'info, ExternalExecutionConfig>,
+ #[account(
+ mut,
+ seeds = [b"counter"],
+ bump,
+ )]
+ pub counter: Account<'info, Counter>,
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts, Debug)]
+pub struct TokenPool<'info> {
+ // token pool CPI signer
+ pub authority: Signer<'info>,
+ #[account(mut)]
+ pub pool_token_account: InterfaceAccount<'info, TokenAccount>,
+ pub mint: InterfaceAccount<'info, Mint>,
+ /// CHECK: CPI to underlying token program
+ pub token_program: UncheckedAccount<'info>,
+ // remaining accounts
+ // [0] receiver_token_account // only included for release_mint
+ // [...] extra passed accounts
+}
+
+#[account]
+#[derive(InitSpace, Debug)]
+pub struct Counter {
+ pub value: u8,
+}
+
+#[account]
+#[derive(InitSpace, Debug)]
+pub struct ExternalExecutionConfig {}
+
+// TODO: Import types and constants from CCIP Router, it should be an imported crate
+// But for now, we are copying the structs here as the final design of the messages is not done.
+
+const CCIP_ROUTER: Pubkey = pubkey!("C8WSPj3yyus1YN3yNB6YA5zStYtbjQWtpmKadmvyUXq8");
+
+#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct Any2SolanaMessage {
+ pub message_id: [u8; 32],
+ pub source_chain_selector: u64,
+ pub sender: Vec,
+ pub data: Vec,
+ pub token_amounts: Vec,
+}
+
+#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, Default)]
+pub struct SolanaTokenAmount {
+ pub token: Pubkey,
+ pub amount: u64, // solana local token amount
+}
+
+#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct LockOrBurnInV1 {
+ receiver: Vec, // The recipient of the tokens on the destination chain, abi encoded
+ remote_chain_selector: u64, // The chain ID of the destination chain
+ original_sender: Pubkey, // The original sender of the tx on the source chain
+ amount: u64, // local solana amount to lock/burn, The amount of tokens to lock or burn, denominated in the source token's decimals
+ local_token: Pubkey, // The address on this chain of the token to lock or burn
+}
+
+#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct ReleaseOrMintInV1 {
+ original_sender: Vec, // The original sender of the tx on the source chain
+ remote_chain_selector: u64, // ─╮ The chain ID of the source chain
+ receiver: Pubkey, // ───────────╯ The recipient of the tokens on the destination chain.
+ amount: [u8; 32], // u256, incoming cross-chain amount - The amount of tokens to release or mint, denominated in the source token's decimals
+ local_token: Pubkey, // The address on this chain of the token to release or mint
+ /// @dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the
+ /// expected pool address for the given remoteChainSelector.
+ source_pool_address: Vec, // The address of the source pool, abi encoded in the case of EVM chains
+ source_pool_data: Vec, // The data received from the source pool to process the release or mint
+ /// @dev WARNING: offchainTokenData is untrusted data.
+ offchain_token_data: Vec, // The offchain data to process the release or mint
+}
diff --git a/chains/solana/contracts/programs/ccip-router/Cargo.toml b/chains/solana/contracts/programs/ccip-router/Cargo.toml
new file mode 100644
index 000000000..f3b26d424
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "ccip_router"
+version = "0.1.0-dev"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "ccip_router"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+solana-program = "1.17.25" # pin solana to 1.17
+anchor-lang = { version = "0.29.0", features = ["init-if-needed"] }
+anchor-spl = "0.29.0"
+bytemuck = "1.7"
+
+[dev-dependencies]
+hex = "0.4.3"
diff --git a/chains/solana/contracts/programs/ccip-router/README.md b/chains/solana/contracts/programs/ccip-router/README.md
new file mode 100644
index 000000000..716eac297
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/README.md
@@ -0,0 +1,117 @@
+# CCIP Router implementation in Solana
+
+Collapsed Router + OnRamp + OffRamp Contracts Implementation for CCIP in Solana.
+
+## Messages
+
+### Initialization
+
+`initialize`
+
+1. Initialize the Config PDA with the Solana Chain Selector, the Default Extra Args and the Supported Destination Chain Selectors and the Sequence Number with 0.
+
+#### Initialization Accounts
+
+1. `PDA("config", program_id)`: **Init**, it is used to store the default configuration for Extra Args, the Solana Chain Selector and the supported list of Destination Chain Selectors.
+1. `signer`: The sender of the message.
+
+### Update Config
+
+`add_chain_selector`, `remove_chain_selector`, `update_solana_chain_selector`, `update_default_gas_limit` & `update_default_allow_out_of_order_execution`
+
+1. Update the Config PDA with the Solana Chain Selector.
+1. Update the Config PDA with the Default Extra Args.
+1. Init/Close the Chain State PDA with the Supported Destination Chain Selectors (add/remove).
+
+#### Update Config Accounts
+
+1. `PDA("config", program_id)`: **Mut**, it is used to store the default configuration for Extra Args, the Solana Chain Selector and the supported list of Destination Chain Selectors.
+1. `PDA("chain_state", chain_selector, program_id)`: **Init/Close**, it stores the latest sequence number per destination chain [only for `add_chain_selector` & `remove_chain_selector`].
+1. `signer`: The sender of the message.
+
+### Send Message
+
+`ccipSend`
+
+1. Check if the Destination Chain Selector is supported (enforced by the `chain_state` account).
+1. Emit `CCIPMessageSent` event.
+1. Update the Sequence Number.
+
+#### Send Message Accounts
+
+1. `PDA("config", program_id)`: **Read only**, it is used to read the default configuration for Extra Args, the Solana Chain Selector and the supported list of Destination Chain Selectors.
+1. `PDA("chain_state", chain_selector, program_id)`: **Mut**, increases the latest sequence number per destination chain.
+1. `signer`: The sender of the message.
+
+### Commit Report
+
+`commit`
+
+1. Check if the Source Chain Selector is supported.
+1. Check if the Interval is valid.
+1. Check if the Merkle Root is not empty and is new.
+1. Update the Source Chain Reports with the new report.
+1. Emit `CommitReportAccepted` event.
+1. Emit `Transmitted` event.
+
+#### Commit Report Accounts
+
+1. `PDA("chain_state", chain_selector, program_id)`: **Read only**, checks if the Source Chain Selector is supported.
+1. `PDA("commit_report", chain_selector, merkle_root, program_id)`: **Init**, stores the Merkle Root for the new Report.
+1. `signer`: The sender of the message.
+
+### Execute Report
+
+`executeReport`
+
+1. Validates that the report was commited
+1. Validates that the report was not executed (if not emit `SkippedAlreadyExecutedMessage` or `AlreadyAttempted` events)
+1. Validates that the Merkle Proof is correct
+1. Executes the message
+1. Emit `ExecutionStateChanged` event
+
+#### Execute Report Accounts
+
+1. `PDA("config", program_id)`: **Read only**, checks if the Chain Selectors are supported.
+1. `PDA("commit_report", chain_selector, merkle_root, program_id)`: **Mut**, verifies the Merkle Root for the Report and updates state.
+1. `signer`: The sender of the message.
+
+## Future Work
+
+- _EMIT_: When Emitting `ExecutionStateChanged` events in the execute report, there are two values that are not being correctly populated: `message_id` & `new_state`.
+- _EXTRA ARGS_:
+ - What should we do when they are empty? Not serialize them in the hash? Now it's not allowed to be empty in the client.
+ - Fix override for extra args: now it only works for `gasLimit`, it should work for `allowOutOfOrderExecution` too.
+ - Decide if it makes sense to have a param named `gasLimit`
+- _TYPES_
+ - [blocked] Use `pub type Selector = u64;` instead of just `u64` in `ccip_send` args. It seems like there are some issues when parsing that in the Typescript tests, not only as a param in messages but also inside the Message struct.
+ - Anchor v0.29.0 bug that requires parameter naming to match type - https://github.com/coral-xyz/anchor/issues/2820
+ - Anchor-Go does not support code generation for aliased types
+ - Attempted changes ([#92](https://github.com/smartcontractkit/chainlink-internal-integrations/pull/92/commits/2c700c430a78f3e63831d7cd0565bcc7206a1eeb)) for future reference
+ - Use `[u128; 2]` to store the `Interval` in the `commit` message, this way we will be able to handle a maximum of 128 messages per report to be compatible with EVM.
+- _ENABLE/DISABLE CHAINS_: Currently you can add/remove chain selectors, but maybe we need a way to enable/disable them (and only as destination or source).
+- _UNIFY ERRORS_: Review the type of errors, and decide if we should have more granularity of them.
+- _EXTERNAL EXECUTION_: Understand and documents the limitations over the external execution of the messages.
+
+## Future Work (Production Readiness)
+
+- _FEES_: Add fees for the OnRamp flow [WIP]
+- _TOKEN POOLS_: Add the flow related to sending tokens and burning/minting in the Token Pools
+- _RMN_: Add the flow related to RMN and cursed/blessed lanes or messages
+- _RATE LIMIT_: Add rate limit for the ccipSend/executeReports messages
+- _NONCES_: Use nonces for the ccipSend message per user, so they can execute ordered transactions on destination chain.
+- _ORDERED EXECUTION_: Validate nonces when executing reports in the Off Ramp.
+
+## Testing
+
+- The Rust Tests execute logic specific to the program, to run them use the command
+
+ ```bash
+ cargo test
+ ```
+
+- The Anchor Tests are in the `tests` folder, written in Typescript and use the `@project-serum/solana-web3` library. The tests are run in a local network, so the tests are fast and don't require any real Solana network. To run them, use the command
+
+ ```bash
+ anchor test
+ ```
diff --git a/chains/solana/contracts/programs/ccip-router/Xargo.toml b/chains/solana/contracts/programs/ccip-router/Xargo.toml
new file mode 100644
index 000000000..475fb71ed
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/Xargo.toml
@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []
diff --git a/chains/solana/contracts/programs/ccip-router/src/context.rs b/chains/solana/contracts/programs/ccip-router/src/context.rs
new file mode 100644
index 000000000..205ab14cb
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/context.rs
@@ -0,0 +1,648 @@
+use anchor_lang::{prelude::*, Ids};
+use anchor_spl::associated_token::AssociatedToken;
+use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
+use solana_program::sysvar::instructions;
+
+use crate::ocr3base::Ocr3Report;
+use crate::program::CcipRouter;
+use crate::state::{ChainState, CommitReport, Config, ExternalExecutionConfig, Nonce};
+use crate::{
+ BillingTokenConfig, BillingTokenConfigWrapper, CcipRouterError, ExecutionReportSingleChain,
+ ReportContext,
+};
+
+pub const ANCHOR_DISCRIMINATOR: usize = 8;
+
+// track state versions
+pub const MAX_CONFIG_V: u8 = 1;
+pub const MAX_CHAINSTATE_V: u8 = 1;
+pub const MAX_NONCE_V: u8 = 1;
+pub const MAX_COMMITREPORT_V: u8 = 1;
+pub const MAX_TOKEN_REGISTRY_V: u8 = 1;
+pub const MAX_TOKEN_AND_CHAIN_CONFIG_V: u8 = 1;
+
+// valid_version validates that the passed in version is not 0 (uninitialized)
+// and it is within the expected maximum supported version bounds
+pub fn valid_version(v: u8, max_v: u8) -> bool {
+ v != 0 && v <= max_v
+}
+pub fn uninitialized(v: u8) -> bool {
+ v == 0
+}
+
+// Fixed seeds - different contexts must use different PDA seeds
+pub const CHAIN_STATE_SEED: &[u8] = b"chain_state";
+pub const COMMIT_REPORT_SEED: &[u8] = b"commit_report";
+pub const NONCE_SEED: &[u8] = b"nonce";
+pub const CONFIG_SEED: &[u8] = b"config";
+pub const EXTERNAL_EXECUTION_CONFIG_SEED: &[u8] = b"external_execution_config"; // arbitrary messaging signer
+pub const EXTERNAL_TOKEN_POOL_SEED: &[u8] = b"external_token_pools_signer"; // token pool interaction signer
+pub const FEE_BILLING_SIGNER_SEEDS: &[u8] = b"fee_billing_signer"; // signer for billing fee token transfer
+pub const FEE_BILLING_TOKEN_CONFIG: &[u8] = b"fee_billing_token_config";
+
+// Token
+pub const TOKEN_ADMIN_REGISTRY_SEED: &[u8] = b"token_admin_registry";
+pub const CCIP_TOKENPOOL_CONFIG: &[u8] = b"ccip_tokenpool_config";
+pub const CCIP_TOKENPOOL_SIGNER: &[u8] = b"ccip_tokenpool_signer";
+pub const TOKEN_POOL_BILLING_SEED: &[u8] = b"ccip_tokenpool_billing";
+pub const TOKEN_POOL_CONFIG_SEED: &[u8] = b"ccip_tokenpool_chainconfig";
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct CommitInput {
+ pub price_updates: PriceUpdates,
+ pub merkle_root: MerkleRoot,
+ // pub rmn_signatures: Vec<[u8; 65]>, // r = 32, s = 32, v = 1; placeholder: RMN not enabled
+}
+
+// A collection of token price and gas price updates.
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct PriceUpdates {
+ pub token_price_updates: Vec,
+ pub gas_price_updates: Vec,
+}
+
+// Token price in USD.
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct TokenPriceUpdate {
+ pub source_token: Pubkey, // Source token. It is the mint, but called "token" for EVM compatibility.
+ pub usd_per_token: [u8; 28], // EVM uses u224, 1e18 USD per 1e18 of the smallest token denomination.
+}
+
+// Gas price for a given chain in USD, its value may contain tightly packed fields.
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct GasPriceUpdate {
+ pub dest_chain_selector: u64, // Destination chain selector
+ pub usd_per_unit_gas: [u8; 28], // EVM uses u224, 1e18 USD per smallest unit (e.g. wei) of destination chain gas
+}
+
+impl Ocr3Report for CommitInput {
+ fn hash(&self, ctx: &ReportContext) -> [u8; 32] {
+ use anchor_lang::solana_program::hash;
+ let mut buffer: Vec = Vec::new();
+ self.serialize(&mut buffer).unwrap();
+ let report_len = self.len() as u16; // u16 > max tx size, u8 may have overflow
+ hash::hashv(&[&report_len.to_le_bytes(), &buffer, &ctx.as_bytes()]).to_bytes()
+ }
+
+ fn len(&self) -> usize {
+ 4 + (32 + 28) * self.price_updates.token_price_updates.len() + // token_price_updates
+ 4 + (8 + 28) * self.price_updates.gas_price_updates.len() + // gas_price_updates
+ self.merkle_root.len()
+ // + 4 + 65 * self.rmn_signatures.len()
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+// Struct to hold a merkle root and an interval for a source chain
+pub struct MerkleRoot {
+ pub source_chain_selector: u64, // Remote source chain selector that the Merkle Root is scoped to
+ pub on_ramp_address: Vec, // Generic onramp address, to support arbitrary sources
+ pub min_seq_nr: u64, // Minimum sequence number, inclusive
+ pub max_seq_nr: u64, // Maximum sequence number, inclusive
+ pub merkle_root: [u8; 32], // Merkle root covering the interval & source chain messages
+}
+
+impl MerkleRoot {
+ fn len(&self) -> usize {
+ 8 + // source chain selector
+ 4 + self.on_ramp_address.len() + // on ramp address
+ 8 + // min msg nr
+ 8 + // max msg nr
+ 32 // root
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct Solana2AnyMessage {
+ pub receiver: Vec,
+ pub data: Vec,
+ pub token_amounts: Vec,
+ pub fee_token: Pubkey, // pass zero address if native SOL
+ pub extra_args: ExtraArgsInput,
+
+ // solana specific parameter for mapping tokens to set of accounts
+ pub token_indexes: Vec,
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, Default)]
+pub struct SolanaTokenAmount {
+ pub token: Pubkey,
+ pub amount: u64, // u64 - amount local to solana
+}
+
+#[derive(Clone, Copy, AnchorSerialize, AnchorDeserialize)]
+pub struct ExtraArgsInput {
+ pub gas_limit: Option,
+ pub allow_out_of_order_execution: Option,
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct Any2SolanaMessage {
+ pub message_id: [u8; 32],
+ pub source_chain_selector: u64,
+ pub sender: Vec,
+ pub data: Vec,
+ pub token_amounts: Vec,
+}
+
+#[derive(Accounts)]
+#[instruction(destination_chain_selector: u64, message: Solana2AnyMessage)]
+pub struct GetFee<'info> {
+ #[account(
+ seeds = [CHAIN_STATE_SEED, destination_chain_selector.to_le_bytes().as_ref()],
+ bump,
+ constraint = valid_version(chain_state.version, MAX_CHAINSTATE_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub chain_state: Account<'info, ChainState>,
+
+ #[account(
+ seeds = [FEE_BILLING_TOKEN_CONFIG, message.fee_token.as_ref()],
+ bump,
+ )]
+ pub billing_token_config: Account<'info, BillingTokenConfigWrapper>,
+}
+
+#[derive(Accounts)]
+pub struct InitializeCCIPRouter<'info> {
+ #[account(
+ init,
+ seeds = [CONFIG_SEED],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + Config::INIT_SPACE,
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(mut)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+ #[account(constraint = program.programdata_address()? == Some(program_data.key()))]
+ pub program: Program<'info, CcipRouter>,
+ #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()) @ CcipRouterError::Unauthorized)]
+ // initialization only allowed by program upgrade authority
+ pub program_data: Account<'info, ProgramData>,
+ #[account(
+ init,
+ seeds = [EXTERNAL_EXECUTION_CONFIG_SEED],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + ExternalExecutionConfig::INIT_SPACE,
+ )]
+ pub external_execution_config: Account<'info, ExternalExecutionConfig>, // messaging CPI signer initialization
+ #[account(
+ init,
+ seeds = [EXTERNAL_TOKEN_POOL_SEED],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + ExternalExecutionConfig::INIT_SPACE,
+ )]
+ pub token_pools_signer: Account<'info, ExternalExecutionConfig>, // token pool CPI signer initialization
+}
+
+#[derive(Accounts)]
+pub struct TransferOwnership<'info> {
+ #[account(
+ mut,
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(address = config.load()?.owner @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct AcceptOwnership<'info> {
+ #[account(
+ mut,
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(address = config.load()?.proposed_owner @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(new_chain_selector: u64)]
+pub struct AddChainSelector<'info> {
+ #[account(
+ init,
+ seeds = [CHAIN_STATE_SEED, new_chain_selector.to_le_bytes().as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + ChainState::INIT_SPACE,
+ constraint = uninitialized(chain_state.version) @ CcipRouterError::InvalidInputs, // validate uninitialized
+ )]
+ pub chain_state: Account<'info, ChainState>,
+ #[account(
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(mut, address = config.load()?.owner @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(new_chain_selector: u64)]
+pub struct UpdateChainSelectorConfig<'info> {
+ #[account(
+ mut,
+ seeds = [CHAIN_STATE_SEED, new_chain_selector.to_le_bytes().as_ref()],
+ bump,
+ constraint = valid_version(chain_state.version, MAX_CHAINSTATE_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub chain_state: Account<'info, ChainState>,
+ #[account(
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(mut, address = config.load()?.owner @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct UpdateConfigCCIPRouter<'info> {
+ #[account(
+ mut,
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(address = config.load()?.owner @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct UpdateSupportedChainsConfigCCIPRouter<'info> {
+ #[account(
+ mut,
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(address = config.load()?.owner @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct SetOcrConfig<'info> {
+ #[account(
+ mut,
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(address = config.load()?.owner @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(token_config: BillingTokenConfig)]
+pub struct AddBillingTokenConfig<'info> {
+ #[account(
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+
+ #[account(
+ init,
+ seeds = [FEE_BILLING_TOKEN_CONFIG, token_config.mint.key().as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + BillingTokenConfigWrapper::INIT_SPACE,
+ )]
+ pub billing_token_config: Account<'info, BillingTokenConfigWrapper>,
+
+ /// CHECK: This is the token program OR the token-2022 program. Given that there are 2 options, this can't have the
+ /// type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+ /// with a constraint enforcing that it is one of the two allowed programs.
+ #[account(
+ constraint = TokenInterface::ids().contains(&token_program.key()) @ CcipRouterError::InvalidInputs,
+ )]
+ pub token_program: UncheckedAccount<'info>,
+
+ #[account(
+ owner = token_program.key() @ CcipRouterError::InvalidInputs,
+ constraint = token_config.mint == fee_token_mint.key() @ CcipRouterError::InvalidInputs,
+ )]
+ pub fee_token_mint: InterfaceAccount<'info, Mint>,
+
+ #[account(
+ init,
+ payer = authority,
+ associated_token::mint = fee_token_mint,
+ associated_token::authority = fee_billing_signer, // use the signer account as the authority // TODO discuss?
+ associated_token::token_program = token_program,
+ )]
+ pub fee_token_receiver: InterfaceAccount<'info, TokenAccount>,
+
+ #[account(
+ mut,
+ address = config.load()?.owner @ CcipRouterError::Unauthorized
+ )]
+ pub authority: Signer<'info>,
+
+ /// CHECK: This is the signer for the billing CPIs, used here to close the receiver token account
+ #[account(
+ seeds = [FEE_BILLING_SIGNER_SEEDS],
+ bump
+ )]
+ pub fee_billing_signer: UncheckedAccount<'info>,
+
+ pub associated_token_program: Program<'info, AssociatedToken>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(token_config: BillingTokenConfig)]
+pub struct UpdateBillingTokenConfig<'info> {
+ #[account(
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(
+ mut,
+ seeds = [FEE_BILLING_TOKEN_CONFIG, token_config.mint.key().as_ref()],
+ bump,
+ )]
+ pub billing_token_config: Account<'info, BillingTokenConfigWrapper>,
+ #[account(
+ address = config.load()?.owner @ CcipRouterError::Unauthorized
+ )]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct RemoveBillingTokenConfig<'info> {
+ #[account(
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+
+ #[account(
+ mut,
+ close = authority,
+ seeds = [FEE_BILLING_TOKEN_CONFIG, fee_token_mint.key().as_ref()],
+ bump,
+ )]
+ pub billing_token_config: Account<'info, BillingTokenConfigWrapper>,
+
+ /// CHECK: This is the token program OR the token-2022 program. Given that there are 2 options, this can't have the
+ /// type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+ /// with a constraint enforcing that it is one of the two allowed programs.
+ #[account(
+ constraint = TokenInterface::ids().contains(&token_program.key()) @ CcipRouterError::InvalidInputs,
+ )]
+ pub token_program: UncheckedAccount<'info>,
+
+ #[account(
+ owner = token_program.key() @ CcipRouterError::InvalidInputs,
+ )]
+ pub fee_token_mint: InterfaceAccount<'info, Mint>,
+
+ #[account(
+ mut,
+ associated_token::mint = fee_token_mint,
+ associated_token::authority = fee_billing_signer, // use the config account as the authority // TODO discuss?
+ associated_token::token_program = token_program,
+ constraint = fee_token_receiver.amount == 0 @ CcipRouterError::InvalidInputs, // ensure the account is empty // TODO improve error
+ )]
+ pub fee_token_receiver: InterfaceAccount<'info, TokenAccount>,
+
+ /// CHECK: This is the signer for the billing CPIs, used here to close the receiver token account
+ #[account(
+ mut,
+ seeds = [FEE_BILLING_SIGNER_SEEDS],
+ bump
+ )]
+ pub fee_billing_signer: UncheckedAccount<'info>,
+
+ #[account(
+ mut,
+ address = config.load()?.owner @ CcipRouterError::Unauthorized
+ )]
+ pub authority: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(destination_chain_selector: u64, message: Solana2AnyMessage)]
+pub struct CcipSend<'info> {
+ #[account(
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(
+ mut,
+ seeds = [CHAIN_STATE_SEED, destination_chain_selector.to_le_bytes().as_ref()],
+ bump,
+ constraint = valid_version(chain_state.version, MAX_CHAINSTATE_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub chain_state: Account<'info, ChainState>,
+ #[account(
+ init_if_needed,
+ seeds = [NONCE_SEED, destination_chain_selector.to_le_bytes().as_ref(), authority.key().as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + Nonce::INIT_SPACE,
+ constraint = uninitialized(nonce.version) || valid_version(nonce.version, MAX_NONCE_V) @ CcipRouterError::InvalidInputs, // if initialized (v != 0), validate state version
+ )]
+ pub nonce: Account<'info, Nonce>,
+ #[account(mut)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+
+ ///////////////////
+ // billing token //
+ ///////////////////
+ // TODO improve all usages of CcipRouterError::InvalidInputs to be more specific
+ /// CHECK: This is the token program OR the token-2022 program. Given that there are 2 options, this can't have the
+ /// type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+ /// with a constraint enforcing that it is one of the two allowed programs.
+ #[account(
+ constraint = TokenInterface::ids().contains(&fee_token_program.key()) @ CcipRouterError::InvalidInputs,
+ )]
+ pub fee_token_program: UncheckedAccount<'info>,
+
+ #[account(
+ owner = fee_token_program.key() @ CcipRouterError::InvalidInputs,
+ constraint = message.fee_token.key() == fee_token_mint.key() @ CcipRouterError::InvalidInputs,
+ )]
+ pub fee_token_mint: InterfaceAccount<'info, Mint>,
+
+ #[account(
+ // `message.fee_token` would ideally be named `message.fee_mint` in Solana,
+ // but using the `token` nomenclature is more compatible with EVM
+ seeds = [FEE_BILLING_TOKEN_CONFIG, message.fee_token.as_ref()], // the arg would ideally be named mint, but message.fee_token was set for EVM consistency
+ bump,
+ )]
+ pub fee_token_config: Account<'info, BillingTokenConfigWrapper>, // pass wSOL config if using native SOL
+
+ #[account(
+ mut,
+ owner = fee_token_program.key() @ CcipRouterError::InvalidInputs,
+ constraint = fee_token_mint.key() == fee_token_user_associated_account.mint.key() @ CcipRouterError::InvalidInputs,
+ constraint = authority.key() == fee_token_user_associated_account.owner @ CcipRouterError::InvalidInputs,
+ )]
+ pub fee_token_user_associated_account: InterfaceAccount<'info, TokenAccount>, // pass zero address is using native SOL // TODO check this is possible or alternatives
+
+ #[account(
+ mut,
+ associated_token::mint = fee_token_mint,
+ associated_token::authority = fee_billing_signer, // use the config account as the authority // TODO discuss?
+ associated_token::token_program = fee_token_program,
+ )]
+ pub fee_token_receiver: InterfaceAccount<'info, TokenAccount>, // pass wSOL config if using native SOL
+
+ /// CHECK: This is the signer for the billing transfer CPI. // TODO improve comment
+ #[account(
+ seeds = [FEE_BILLING_SIGNER_SEEDS],
+ bump
+ )]
+ pub fee_billing_signer: UncheckedAccount<'info>,
+
+ // CPI signers
+ // optional if no tokens are being transferred
+ /// CHECK: Using this to sign
+ #[account(mut, seeds = [EXTERNAL_TOKEN_POOL_SEED], bump)]
+ pub token_pools_signer: Account<'info, ExternalExecutionConfig>,
+ // /// CHECK: Using this to sign
+ // #[account(mut, seeds = [BILLING_EXECUTION_SEED], bump)]
+ // pub billing_signer: Account<'info, ExternalExecutionConfig>,
+ // pub fee_token_billing_signer_associated_account: Account<'info, ?>, // pass wSOL associated address if using native SOL
+
+ // remaining accounts (not explicitly listed)
+ // [
+ // user/sender token account (must be associated token account - derivable PDA [wallet_addr, token_program, mint])
+ // ccip pool chain config (ccip: billing, ccip admin controlled - derivable PDA [chain_selector, mint])
+ // pool chain config (pool: custom configs that may include rate limits & remote chain configs, pool admin controlled - derivable [chain_selector, mint])
+ // token pool lookup table
+ // token registry PDA
+ // pool program
+ // pool config
+ // pool token account (must be associated token account - derivable PDA [wallet_addr, token_program, mint])
+ // pool signer
+ // token program
+ // token mint
+ // ...additional accounts for pool config
+ // ] x N tokens
+}
+
+#[derive(Accounts)]
+#[instruction(report_context: ReportContext, report: CommitInput)]
+pub struct CommitReportContext<'info> {
+ #[account(
+ mut,
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(
+ mut,
+ seeds = [CHAIN_STATE_SEED, report.merkle_root.source_chain_selector.to_le_bytes().as_ref()],
+ bump,
+ constraint = valid_version(chain_state.version, MAX_CHAINSTATE_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub chain_state: Account<'info, ChainState>,
+ #[account(
+ init,
+ seeds = [COMMIT_REPORT_SEED, report.merkle_root.source_chain_selector.to_le_bytes().as_ref(), report.merkle_root.merkle_root.as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + CommitReport::INIT_SPACE,
+ constraint = uninitialized(commit_report.version) @ CcipRouterError::InvalidInputs, // validate uninitialized
+ )]
+ pub commit_report: Account<'info, CommitReport>,
+ #[account(mut)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+ /// CHECK: This is the sysvar instructions account
+ #[account(address = instructions::ID @ CcipRouterError::InvalidInputs)]
+ pub sysvar_instructions: UncheckedAccount<'info>,
+ // remaining accounts
+ // [...billingTokenConfig accounts]
+ // [...chainConfig accounts]
+}
+
+#[derive(Accounts)]
+#[instruction(report: ExecutionReportSingleChain)]
+pub struct ExecuteReportContext<'info> {
+ #[account(
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(
+ seeds = [CHAIN_STATE_SEED, report.source_chain_selector.to_le_bytes().as_ref()],
+ bump,
+ constraint = valid_version(chain_state.version, MAX_CHAINSTATE_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub chain_state: Account<'info, ChainState>,
+ #[account(
+ mut,
+ seeds = [COMMIT_REPORT_SEED, report.source_chain_selector.to_le_bytes().as_ref(), report.root.as_ref()],
+ bump,
+ constraint = valid_version(commit_report.version, MAX_COMMITREPORT_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub commit_report: Account<'info, CommitReport>,
+ /// CHECK: Using this to sign
+ #[account(seeds = [EXTERNAL_EXECUTION_CONFIG_SEED], bump)]
+ pub external_execution_config: Account<'info, ExternalExecutionConfig>,
+ #[account(mut)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+ /// CHECK: This is a sysvar account
+ #[account(address = instructions::ID @ CcipRouterError::InvalidInputs)]
+ pub sysvar_instructions: AccountInfo<'info>,
+ // CPI signers
+ /// CHECK: Using this to sign
+ #[account(seeds = [EXTERNAL_TOKEN_POOL_SEED], bump)]
+ pub token_pools_signer: Account<'info, ExternalExecutionConfig>,
+ // remaining accounts
+ // [receiver_program, receiver_account, ...user specified accounts from message data for arbitrary messaging]
+ // +
+ // [
+ // user/sender token account (must be associated token account - derivable PDA [wallet_addr, token_program, mint])
+ // per chain per token config (ccip: billing, ccip admin controlled - derivable PDA [chain_selector, mint])
+ // pool chain config (pool: custom configs that may include rate limits & remote chain configs, pool admin controlled - derivable [chain_selector, mint])
+ // token pool lookup table
+ // token registry PDA
+ // pool program
+ // pool config
+ // pool token account (must be associated token account - derivable PDA [wallet_addr, token_program, mint])
+ // pool signer
+ // token program
+ // token mint
+ // ...additional accounts for pool config
+ // ] x N tokens
+}
+
+#[derive(Copy, Clone, AnchorSerialize, AnchorDeserialize)]
+pub enum OcrPluginType {
+ Commit,
+ Execution,
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/event.rs b/chains/solana/contracts/programs/ccip-router/src/event.rs
new file mode 100644
index 000000000..7242201f8
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/event.rs
@@ -0,0 +1,138 @@
+use anchor_lang::prelude::*;
+
+use crate::{
+ context::MerkleRoot, DestChainConfig, MessageExecutionState, PriceUpdates,
+ Solana2AnyRampMessage, SourceChainConfig, TokenBilling,
+};
+
+#[event]
+pub struct CCIPMessageSent {
+ pub dest_chain_selector: u64,
+ pub sequence_number: u64,
+ pub message: Solana2AnyRampMessage,
+}
+
+#[event]
+pub struct CommitReportAccepted {
+ pub merkle_root: MerkleRoot,
+ pub price_updates: PriceUpdates,
+}
+
+#[event]
+pub struct SkippedAlreadyExecutedMessage {
+ pub source_chain_selector: u64,
+ pub sequence_number: u64,
+}
+
+#[event]
+pub struct AlreadyAttempted {
+ pub source_chain_selector: u64,
+ pub sequence_number: u64,
+}
+
+#[event]
+pub struct ExecutionStateChanged {
+ pub source_chain_selector: u64,
+ pub sequence_number: u64,
+ pub message_id: [u8; 32],
+ pub message_hash: [u8; 32],
+ pub state: MessageExecutionState,
+}
+
+#[event]
+pub struct PoolSet {
+ pub token: Pubkey,
+ pub previous_pool_lookup_table: Pubkey,
+ pub new_pool_lookup_table: Pubkey,
+}
+
+#[event]
+pub struct AdministratorTransferRequested {
+ pub token: Pubkey,
+ pub current_admin: Pubkey,
+ pub new_admin: Pubkey,
+}
+
+#[event]
+pub struct AdministratorTransferred {
+ pub token: Pubkey,
+ pub new_admin: Pubkey,
+}
+
+#[event]
+pub struct FeeTokenAdded {
+ pub fee_token: Pubkey,
+ pub enabled: bool,
+}
+
+#[event]
+pub struct FeeTokenEnabled {
+ pub fee_token: Pubkey,
+}
+
+#[event]
+pub struct FeeTokenDisabled {
+ pub fee_token: Pubkey,
+}
+
+#[event]
+pub struct FeeTokenRemoved {
+ pub fee_token: Pubkey,
+}
+
+#[event]
+pub struct UsdPerUnitGasUpdated {
+ pub dest_chain: u64,
+ pub value: [u8; 28], // EVM uses u256 here
+ pub timestamp: i64, // EVM uses u256 here
+}
+
+#[event]
+pub struct UsdPerTokenUpdated {
+ pub token: Pubkey,
+ pub value: [u8; 28], // EVM uses u256 here
+ pub timestamp: i64, // EVM uses u256 here
+}
+
+#[event]
+pub struct TokenTransferFeeConfigUpdated {
+ pub dest_chain_selector: u64,
+ pub token: Pubkey,
+ pub token_transfer_fee_config: TokenBilling,
+}
+
+#[event]
+pub struct PremiumMultiplierWeiPerEthUpdated {
+ pub token: Pubkey,
+ pub premium_multiplier_wei_per_eth: u64,
+}
+
+#[event]
+pub struct SourceChainConfigUpdated {
+ pub source_chain_selector: u64,
+ pub source_chain_config: SourceChainConfig,
+}
+
+#[event]
+pub struct SourceChainAdded {
+ pub source_chain_selector: u64,
+ pub source_chain_config: SourceChainConfig,
+}
+
+#[event]
+pub struct DestChainConfigUpdated {
+ pub dest_chain_selector: u64,
+ pub dest_chain_config: DestChainConfig,
+}
+
+#[event]
+pub struct DestChainAdded {
+ pub dest_chain_selector: u64,
+ pub dest_chain_config: DestChainConfig,
+}
+
+#[event]
+pub struct AdministratorRegistered {
+ pub token_mint: Pubkey,
+ pub administrator: Pubkey,
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/fee_quoter.rs b/chains/solana/contracts/programs/ccip-router/src/fee_quoter.rs
new file mode 100644
index 000000000..0a97c2939
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/fee_quoter.rs
@@ -0,0 +1,141 @@
+use anchor_lang::prelude::*;
+use anchor_spl::token_interface;
+use bytemuck::Zeroable;
+use spl_token_2022::native_mint;
+use token_interface::spl_token_2022;
+
+use crate::{
+ BillingTokenConfig, CcipRouterError, DestChain, Solana2AnyMessage, SolanaTokenAmount,
+ FEE_BILLING_SIGNER_SEEDS,
+};
+
+// TODO change args and implement
+pub fn fee_for_msg(
+ _dest_chain_selector: u64,
+ message: Solana2AnyMessage,
+ dest_chain: &DestChain,
+ token_config: &BillingTokenConfig,
+) -> Result {
+ let token = if message.fee_token == Pubkey::zeroed() {
+ native_mint::ID // Wrapped SOL
+ } else {
+ message.fee_token
+ };
+
+ require!(
+ dest_chain.config.is_enabled,
+ CcipRouterError::DestinationChainDisabled
+ );
+ require!(token_config.enabled, CcipRouterError::FeeTokenDisabled);
+
+ // TODO validate that dest_chain_selector is whitelisted and not cursed
+
+ //? /Users/tobi/dev/work/ccip/contracts/src/v0.8/ccip/FeeQuoter.sol:518
+ //? 1. Get dest chain config
+ //? 1. validate message
+ //? 1. retrieve fee token's current price
+ //? 1. retrieve gas price on the dest chain
+ //? 1. get cost of transfer _of each token in the message_ to the dest chain, including premium, gas, and bytes overhead
+ //? 1. get data availability cost on the dest chain
+ //? 1. Calculate and return the total fee
+
+ // TODO un-hardcode
+ Ok(SolanaTokenAmount { amount: 1, token })
+}
+
+pub fn transfer_fee<'info>(
+ fee: SolanaTokenAmount,
+ token_program: AccountInfo<'info>,
+ transfer: token_interface::TransferChecked<'info>,
+ decimals: u8,
+ signer_bump: u8,
+) -> Result<()> {
+ require!(
+ fee.token == transfer.mint.key(),
+ CcipRouterError::InvalidInputs
+ ); // TODO use more specific error
+
+ let seeds = &[FEE_BILLING_SIGNER_SEEDS, &[signer_bump]];
+ let signer_seeds = &[&seeds[..]];
+ let cpi_ctx = CpiContext::new_with_signer(token_program, transfer, signer_seeds);
+ token_interface::transfer_checked(cpi_ctx, fee.amount, decimals)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::DestChain;
+
+ #[test]
+ fn fees_not_returned_for_disabled_destination_chain() {
+ let mut chain = sample_dest_chain();
+ chain.config.is_enabled = false;
+
+ assert!(fee_for_msg(0, sample_message(), &chain, &sample_billing_config()).is_err());
+ }
+
+ #[test]
+ fn fees_not_returned_for_disabled_token() {
+ let mut billing_config = sample_billing_config();
+ billing_config.enabled = false;
+
+ assert!(fee_for_msg(0, sample_message(), &sample_dest_chain(), &billing_config).is_err());
+ }
+
+ fn sample_message() -> Solana2AnyMessage {
+ Solana2AnyMessage {
+ receiver: vec![],
+ data: vec![],
+ token_amounts: vec![],
+ fee_token: Pubkey::new_unique(),
+ extra_args: crate::ExtraArgsInput {
+ gas_limit: None,
+ allow_out_of_order_execution: None,
+ },
+ token_indexes: vec![],
+ }
+ }
+
+ fn sample_billing_config() -> BillingTokenConfig {
+ BillingTokenConfig {
+ enabled: true,
+ mint: Pubkey::new_unique(),
+ usd_per_token: crate::TimestampedPackedU224 {
+ value: [0; 28],
+ timestamp: 0,
+ },
+ premium_multiplier_wei_per_eth: 0,
+ }
+ }
+
+ fn sample_dest_chain() -> DestChain {
+ DestChain {
+ state: crate::DestChainState {
+ sequence_number: 0,
+ usd_per_unit_gas: crate::TimestampedPackedU224 {
+ value: [0; 28],
+ timestamp: 0,
+ },
+ },
+ config: crate::DestChainConfig {
+ is_enabled: true,
+ max_number_of_tokens_per_msg: 0,
+ max_data_bytes: 0,
+ max_per_msg_gas_limit: 0,
+ dest_gas_overhead: 0,
+ dest_gas_per_payload_byte: 0,
+ dest_data_availability_overhead_gas: 0,
+ dest_gas_per_data_availability_byte: 0,
+ dest_data_availability_multiplier_bps: 0,
+ default_token_fee_usdcents: 0,
+ default_token_dest_gas_overhead: 0,
+ default_tx_gas_limit: 0,
+ gas_multiplier_wei_per_eth: 0,
+ network_fee_usdcents: 0,
+ gas_price_staleness_threshold: 0,
+ enforce_out_of_order: false,
+ chain_family_selector: [0; 4],
+ },
+ }
+ }
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/lib.rs b/chains/solana/contracts/programs/ccip-router/src/lib.rs
new file mode 100644
index 000000000..8077d1f82
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/lib.rs
@@ -0,0 +1,1620 @@
+use std::cell::Ref;
+
+use anchor_lang::error_code;
+use anchor_lang::prelude::*;
+use anchor_spl::token_interface;
+use solana_program::{instruction::Instruction, program::invoke_signed};
+
+mod context;
+use crate::context::*;
+
+mod token_context;
+use crate::token_context::*;
+
+mod state;
+use crate::state::*;
+
+mod event;
+use crate::event::*;
+
+mod merkle;
+use crate::merkle::*;
+
+mod messages;
+use crate::messages::*;
+
+mod pools;
+use crate::pools::*;
+
+mod ocr3base;
+use crate::ocr3base::*;
+
+mod fee_quoter;
+use crate::fee_quoter::*;
+
+// Anchor discriminators for CPI calls
+const CCIP_RECEIVE_DISCRIMINATOR: [u8; 8] = [0x0b, 0xf4, 0x09, 0xf9, 0x2c, 0x53, 0x2f, 0xf5]; // ccip_receive
+const TOKENPOOL_LOCK_OR_BURN_DISCRIMINATOR: [u8; 8] =
+ [0x72, 0xa1, 0x5e, 0x1d, 0x93, 0x19, 0xe8, 0xbf]; // lock_or_burn_tokens
+const TOKENPOOL_RELEASE_OR_MINT_DISCRIMINATOR: [u8; 8] =
+ [0x5c, 0x64, 0x96, 0xc6, 0xfc, 0x3f, 0xa4, 0xe4]; // release_or_mint_tokens
+
+declare_id!("C8WSPj3yyus1YN3yNB6YA5zStYtbjQWtpmKadmvyUXq8");
+
+#[program]
+/// The `ccip_router` module contains the implementation of the Cross-Chain Interoperability Protocol (CCIP) Router.
+///
+/// This is the Collapsed Router Program for CCIP.
+/// As it's upgradable persisting the same program id, there is no need to have an indirection of a Proxy Program.
+/// This Router handles both the OnRamp and OffRamp flow of the CCIP Messages.
+pub mod ccip_router {
+ #![warn(missing_docs)]
+
+ use super::*;
+
+ /// Initializes the CCIP Router.
+ ///
+ /// The initialization of the Router is responsibility of Admin, nothing more than calling this method should be done first.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for initialization.
+ /// * `solana_chain_selector` - The chain selector for Solana.
+ /// * `default_gas_limit` - The default gas limit for other destination chains.
+ /// * `default_allow_out_of_order_execution` - Whether out-of-order execution is allowed by default for other destination chains.
+ /// * `enable_execution_after` - The minimum amount of time required between a message has been committed and can be manually executed.
+ pub fn initialize(
+ ctx: Context,
+ solana_chain_selector: u64,
+ default_gas_limit: u128,
+ default_allow_out_of_order_execution: bool,
+ enable_execution_after: i64,
+ ) -> Result<()> {
+ let mut config = ctx.accounts.config.load_init()?;
+ require!(config.version == 0, CcipRouterError::InvalidInputs); // assert uninitialized state - AccountLoader doesn't work with constraint
+ config.version = 1;
+ config.solana_chain_selector = solana_chain_selector;
+ config.default_gas_limit = default_gas_limit;
+ config.enable_manual_execution_after = enable_execution_after;
+
+ if default_allow_out_of_order_execution {
+ config.default_allow_out_of_order_execution = 1;
+ }
+
+ config.owner = ctx.accounts.authority.key();
+
+ config.ocr3 = [
+ Ocr3Config::new(OcrPluginType::Commit as u8),
+ Ocr3Config::new(OcrPluginType::Execution as u8),
+ ];
+
+ config.latest_price_sequence_number = 0;
+
+ Ok(())
+ }
+
+ /// Transfers the ownership of the router to a new proposed owner.
+ ///
+ /// Shared func signature with other programs
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for the transfer.
+ /// * `proposed_owner` - The public key of the new proposed owner.
+ pub fn transfer_ownership(
+ ctx: Context,
+ proposed_owner: Pubkey,
+ ) -> Result<()> {
+ let mut config = ctx.accounts.config.load_mut()?;
+ require!(
+ proposed_owner != config.owner,
+ CcipRouterError::InvalidInputs
+ );
+ config.proposed_owner = proposed_owner;
+ Ok(())
+ }
+
+ /// Accepts the ownership of the router by the proposed owner.
+ ///
+ /// Shared func signature with other programs
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for accepting ownership.
+ /// The new owner must be a signer of the transaction.
+ pub fn accept_ownership(ctx: Context) -> Result<()> {
+ let mut config = ctx.accounts.config.load_mut()?;
+ config.owner = std::mem::take(&mut config.proposed_owner);
+ config.proposed_owner = Pubkey::new_from_array([0; 32]);
+ Ok(())
+ }
+
+ /// Adds a new chain selector to the router.
+ ///
+ /// The Admin needs to add any new chain supported (this means both OnRamp and OffRamp).
+ /// When adding a new chain, the Admin needs to specify if it's enabled or not.
+ /// They may enable only source, or only destination, or neither, or both.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for adding the chain selector.
+ /// * `new_chain_selector` - The new chain selector to be added.
+ /// * `source_chain_config` - The configuration for the chain as source.
+ /// * `dest_chain_config` - The configuration for the chain as destination.
+ pub fn add_chain_selector(
+ ctx: Context,
+ new_chain_selector: u64,
+ source_chain_config: SourceChainConfig,
+ dest_chain_config: DestChainConfig,
+ ) -> Result<()> {
+ let chain_state = &mut ctx.accounts.chain_state;
+ chain_state.version = 1;
+
+ // Set source chain config & state
+ validate_source_chain_config(new_chain_selector, &source_chain_config)?;
+ chain_state.source_chain.config = source_chain_config.clone();
+ chain_state.source_chain.state = SourceChainState { min_seq_nr: 1 };
+
+ // Set dest chain config & state
+ validate_dest_chain_config(new_chain_selector, &dest_chain_config)?;
+ chain_state.dest_chain.config = dest_chain_config.clone();
+ chain_state.dest_chain.state = DestChainState {
+ sequence_number: 0,
+ usd_per_unit_gas: TimestampedPackedU224 {
+ value: [0; 28],
+ timestamp: 0,
+ },
+ };
+
+ emit!(SourceChainAdded {
+ source_chain_selector: new_chain_selector,
+ source_chain_config,
+ });
+ emit!(DestChainAdded {
+ dest_chain_selector: new_chain_selector,
+ dest_chain_config,
+ });
+
+ Ok(())
+ }
+
+ /// Disables the source chain selector.
+ ///
+ /// The Admin is the only one able to disable the chain selector as source. This method is thought of as an emergency kill-switch.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for disabling the chain selector.
+ /// * `source_chain_selector` - The source chain selector to be disabled.
+ pub fn disable_source_chain_selector(
+ ctx: Context,
+ source_chain_selector: u64,
+ ) -> Result<()> {
+ let chain_state = &mut ctx.accounts.chain_state;
+
+ chain_state.source_chain.config.is_enabled = false;
+
+ emit!(SourceChainConfigUpdated {
+ source_chain_selector,
+ source_chain_config: chain_state.source_chain.config.clone(),
+ });
+
+ Ok(())
+ }
+
+ /// Disables the destination chain selector.
+ ///
+ /// The Admin is the only one able to disable the chain selector as destination. This method is thought of as an emergency kill-switch.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for disabling the chain selector.
+ /// * `dest_chain_selector` - The destination chain selector to be disabled.
+ pub fn disable_dest_chain_selector(
+ ctx: Context,
+ dest_chain_selector: u64,
+ ) -> Result<()> {
+ let chain_state = &mut ctx.accounts.chain_state;
+
+ chain_state.dest_chain.config.is_enabled = false;
+
+ emit!(DestChainConfigUpdated {
+ dest_chain_selector,
+ dest_chain_config: chain_state.dest_chain.config.clone(),
+ });
+
+ Ok(())
+ }
+
+ /// Updates the configuration of the source chain selector.
+ ///
+ /// The Admin is the only one able to update the source chain config.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for updating the chain selector.
+ /// * `source_chain_selector` - The source chain selector to be updated.
+ /// * `source_chain_config` - The new configuration for the source chain.
+ pub fn update_source_chain_config(
+ ctx: Context,
+ source_chain_selector: u64,
+ source_chain_config: SourceChainConfig,
+ ) -> Result<()> {
+ validate_source_chain_config(source_chain_selector, &source_chain_config)?;
+
+ ctx.accounts.chain_state.source_chain.config = source_chain_config.clone();
+
+ emit!(SourceChainConfigUpdated {
+ source_chain_selector,
+ source_chain_config,
+ });
+ Ok(())
+ }
+
+ /// Updates the configuration of the destination chain selector.
+ ///
+ /// The Admin is the only one able to update the destination chain config.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for updating the chain selector.
+ /// * `dest_chain_selector` - The destination chain selector to be updated.
+ /// * `dest_chain_config` - The new configuration for the destination chain.
+ pub fn update_dest_chain_config(
+ ctx: Context,
+ dest_chain_selector: u64,
+ dest_chain_config: DestChainConfig,
+ ) -> Result<()> {
+ validate_dest_chain_config(dest_chain_selector, &dest_chain_config)?;
+
+ ctx.accounts.chain_state.dest_chain.config = dest_chain_config.clone();
+
+ emit!(DestChainConfigUpdated {
+ dest_chain_selector,
+ dest_chain_config,
+ });
+ Ok(())
+ }
+
+ /// Updates the Solana chain selector in the router configuration.
+ ///
+ /// This method should only be used if there was an error with the initial configuration or if the solana chain selector changes.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for updating the configuration.
+ /// * `new_chain_selector` - The new chain selector for Solana.
+ pub fn update_solana_chain_selector(
+ ctx: Context,
+ new_chain_selector: u64,
+ ) -> Result<()> {
+ let mut config = ctx.accounts.config.load_mut()?;
+
+ config.solana_chain_selector = new_chain_selector;
+
+ Ok(())
+ }
+
+ /// Updates the default gas limit in the router configuration.
+ ///
+ /// This change affects the default value for gas limit on every other destination chain.
+ /// The Admin is the only one able to update the default gas limit.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for updating the configuration.
+ /// * `new_gas_limit` - The new default gas limit.
+ pub fn update_default_gas_limit(
+ ctx: Context,
+ new_gas_limit: u128,
+ ) -> Result<()> {
+ let mut config = ctx.accounts.config.load_mut()?;
+
+ config.default_gas_limit = new_gas_limit;
+
+ Ok(())
+ }
+
+ /// Updates the default setting for allowing out-of-order execution for other destination chains.
+ /// The Admin is the only one able to update this config.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for updating the configuration.
+ /// * `new_allow_out_of_order_execution` - The new setting for allowing out-of-order execution.
+ pub fn update_default_allow_out_of_order_execution(
+ ctx: Context,
+ new_allow_out_of_order_execution: bool,
+ ) -> Result<()> {
+ let mut config = ctx.accounts.config.load_mut()?;
+
+ let mut v = 0_u8;
+ if new_allow_out_of_order_execution {
+ v = 1;
+ }
+ config.default_allow_out_of_order_execution = v;
+
+ Ok(())
+ }
+
+ /// Updates the minimum amount of time required between a message being committed and when it can be manually executed.
+ ///
+ /// This is part of the OffRamp Configuration for Solana.
+ /// The Admin is the only one able to update this config.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for updating the configuration.
+ /// * `new_enable_manual_execution_after` - The new minimum amount of time required.
+ pub fn update_enable_manual_execution_after(
+ ctx: Context,
+ new_enable_manual_execution_after: i64,
+ ) -> Result<()> {
+ let mut config = ctx.accounts.config.load_mut()?;
+
+ config.enable_manual_execution_after = new_enable_manual_execution_after;
+
+ Ok(())
+ }
+
+ /// Registers the Token Admin Registry via the CCIP Admin
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for registration.
+ /// * `mint` - The public key of the token mint.
+ /// * `token_admin_registry_admin` - The public key of the token admin registry admin.
+ pub fn register_token_admin_registry_via_get_ccip_admin(
+ ctx: Context,
+ mint: Pubkey, // should we validate that this is a real token program?
+ token_admin_registry_admin: Pubkey,
+ ) -> Result<()> {
+ let token_admin_registry = &mut ctx.accounts.token_admin_registry;
+ token_admin_registry.version = 1;
+ token_admin_registry.administrator = token_admin_registry_admin;
+ token_admin_registry.pending_administrator = Pubkey::new_from_array([0; 32]);
+ token_admin_registry.lookup_table = Pubkey::new_from_array([0; 32]);
+
+ emit!(AdministratorRegistered {
+ token_mint: mint,
+ administrator: token_admin_registry_admin,
+ });
+
+ Ok(())
+ }
+
+ /// Registers the Token Admin Registry via the token owner.
+ ///
+ /// The Authority of the Mint Token can claim the registry of the token.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for registration.
+ pub fn register_token_admin_registry_via_owner(
+ ctx: Context,
+ ) -> Result<()> {
+ let token_mint = ctx.accounts.mint.key().to_owned();
+ let mint_authority = ctx.accounts.mint.mint_authority.to_owned();
+
+ require!(
+ mint_authority.is_some(),
+ CcipRouterError::InvalidInputsPoolAccounts
+ );
+
+ let administrator = mint_authority.unwrap();
+
+ let token_admin_registry = &mut ctx.accounts.token_admin_registry;
+ token_admin_registry.version = 1;
+ token_admin_registry.administrator = administrator;
+ token_admin_registry.pending_administrator = Pubkey::new_from_array([0; 32]);
+ token_admin_registry.lookup_table = Pubkey::new_from_array([0; 32]);
+
+ emit!(AdministratorRegistered {
+ token_mint,
+ administrator,
+ });
+
+ Ok(())
+ }
+
+ /// Sets the pool lookup table for a given token mint.
+ ///
+ /// The administrator of the token admin registry can set the pool lookup table for a given token mint.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for setting the pool.
+ /// * `mint` - The public key of the token mint.
+ /// * `pool_lookup_table` - The public key of the pool lookup table, this address will be used for validations when interacting with the pool.
+ pub fn set_pool(
+ ctx: Context,
+ mint: Pubkey,
+ pool_lookup_table: Pubkey,
+ ) -> Result<()> {
+ let token_admin_registry = &mut ctx.accounts.token_admin_registry;
+ let previous_pool = token_admin_registry.lookup_table;
+ token_admin_registry.lookup_table = pool_lookup_table;
+
+ // TODO: Validate here that the lookup table has everything
+
+ emit!(PoolSet {
+ token: mint,
+ previous_pool_lookup_table: previous_pool,
+ new_pool_lookup_table: pool_lookup_table,
+ });
+
+ Ok(())
+ }
+
+ /// Transfers the admin role of the token admin registry to a new admin.
+ ///
+ /// Only the Admin can transfer the Admin Role of the Token Admin Registry, this setups the Pending Admin and then it's their responsibility to accept the role.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for the transfer.
+ /// * `mint` - The public key of the token mint.
+ /// * `new_admin` - The public key of the new admin.
+ pub fn transfer_admin_role_token_admin_registry(
+ ctx: Context,
+ mint: Pubkey,
+ new_admin: Pubkey,
+ ) -> Result<()> {
+ let token_admin_registry = &mut ctx.accounts.token_admin_registry;
+
+ if new_admin == Pubkey::new_from_array([0; 32]) {
+ token_admin_registry.pending_administrator = Pubkey::new_from_array([0; 32]);
+ } else {
+ token_admin_registry.pending_administrator = new_admin;
+ }
+
+ emit!(AdministratorTransferRequested {
+ token: mint,
+ current_admin: token_admin_registry.administrator,
+ new_admin,
+ });
+
+ Ok(())
+ }
+
+ /// Accepts the admin role of the token admin registry.
+ ///
+ /// The Pending Admin must call this function to accept the admin role of the Token Admin Registry.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for accepting the admin role.
+ /// * `mint` - The public key of the token mint.
+ pub fn accept_admin_role_token_admin_registry(
+ ctx: Context,
+ mint: Pubkey,
+ ) -> Result<()> {
+ let token_admin_registry = &mut ctx.accounts.token_admin_registry;
+ let new_admin = token_admin_registry.pending_administrator;
+ token_admin_registry.administrator = new_admin;
+ token_admin_registry.pending_administrator = Pubkey::new_from_array([0; 32]);
+
+ emit!(AdministratorTransferred {
+ token: mint,
+ new_admin,
+ });
+
+ Ok(())
+ }
+
+ /// Sets the token billing configuration.
+ ///
+ /// Only CCIP Admin can set the token billing configuration.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for setting the token billing configuration.
+ /// * `_chain_selector` - The chain selector.
+ /// * `_mint` - The public key of the token mint.
+ /// * `cfg` - The token billing configuration.
+ pub fn set_token_billing(
+ ctx: Context,
+ _chain_selector: u64,
+ _mint: Pubkey,
+ cfg: TokenBilling,
+ ) -> Result<()> {
+ ctx.accounts.per_chain_per_token_config.billing = cfg;
+ Ok(())
+ }
+
+ /// Sets the OCR configuration.
+ /// Only CCIP Admin can set the OCR configuration.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for setting the OCR configuration.
+ /// * `plugin_type` - The type of OCR plugin [0: Commit, 1: Execution].
+ /// * `config_info` - The OCR configuration information.
+ /// * `signers` - The list of signers.
+ /// * `transmitters` - The list of transmitters.
+ pub fn set_ocr_config(
+ ctx: Context,
+ plugin_type: u8, // OcrPluginType, u8 used because anchor tests did not work with an enum
+ config_info: Ocr3ConfigInfo,
+ signers: Vec<[u8; 20]>,
+ transmitters: Vec,
+ ) -> Result<()> {
+ require!(plugin_type < 2, CcipRouterError::InvalidInputs);
+ let mut config = ctx.accounts.config.load_mut()?;
+
+ let is_commit = plugin_type == OcrPluginType::Commit as u8;
+
+ config.ocr3[plugin_type as usize].set(
+ plugin_type,
+ Ocr3ConfigInfo {
+ config_digest: config_info.config_digest,
+ f: config_info.f,
+ n: signers.len() as u8,
+ is_signature_verification_enabled: if is_commit { 1 } else { 0 },
+ },
+ signers,
+ transmitters,
+ )?;
+
+ if is_commit {
+ // When the OCR config changes, we reset the sequence number since it is scoped per config digest.
+ // Note that s_minSeqNr/roots do not need to be reset as the roots persist
+ // across reconfigurations and are de-duplicated separately.
+ config.latest_price_sequence_number = 0;
+ }
+
+ Ok(())
+ }
+
+ /// Adds a billing token configuration.
+ /// Only CCIP Admin can add a billing token configuration.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for adding the billing token configuration.
+ /// * `config` - The billing token configuration to be added.
+ pub fn add_billing_token_config(
+ ctx: Context,
+ config: BillingTokenConfig,
+ ) -> Result<()> {
+ emit!(FeeTokenAdded {
+ fee_token: config.mint,
+ enabled: config.enabled
+ });
+ ctx.accounts.billing_token_config.version = 1; // update this if we change the account struct
+ ctx.accounts.billing_token_config.config = config;
+ Ok(())
+ }
+
+ /// Updates the billing token configuration.
+ /// Only CCIP Admin can update a billing token configuration.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for updating the billing token configuration.
+ /// * `config` - The new billing token configuration.
+ pub fn update_billing_token_config(
+ ctx: Context,
+ config: BillingTokenConfig,
+ ) -> Result<()> {
+ if config.enabled != ctx.accounts.billing_token_config.config.enabled {
+ // enabled/disabled status has changed
+ match config.enabled {
+ true => emit!(FeeTokenEnabled {
+ fee_token: config.mint
+ }),
+ false => emit!(FeeTokenDisabled {
+ fee_token: config.mint
+ }),
+ }
+ }
+ // TODO should we emit an event if the config has changed regardless of the enabled/disabled?
+
+ ctx.accounts.billing_token_config.version = 1; // update this if we change the account struct
+ ctx.accounts.billing_token_config.config = config;
+ Ok(())
+ }
+
+ /// Removes the billing token configuration.
+ /// Only CCIP Admin can remove a billing token configuration.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for removing the billing token configuration.
+ pub fn remove_billing_token_config(ctx: Context) -> Result<()> {
+ // Close the receiver token account
+ // The context constraints already enforce that it holds 0 balance of the target SPL token
+ let cpi_accounts = token_interface::CloseAccount {
+ account: ctx.accounts.fee_token_receiver.to_account_info(),
+ destination: ctx.accounts.authority.to_account_info(),
+ authority: ctx.accounts.fee_billing_signer.to_account_info(),
+ };
+ let cpi_program = ctx.accounts.token_program.to_account_info();
+ let seeds = &[FEE_BILLING_SIGNER_SEEDS, &[ctx.bumps.fee_billing_signer]];
+ let signer_seeds = &[&seeds[..]];
+ let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);
+
+ token_interface::close_account(cpi_ctx)?;
+
+ emit!(FeeTokenRemoved {
+ fee_token: ctx.accounts.fee_token_mint.key()
+ });
+ Ok(())
+ }
+
+ /// Calculates the fee for sending a message to the destination chain.
+ ///
+ /// # Arguments
+ ///
+ /// * `_ctx` - The context containing the accounts required for the fee calculation.
+ /// * `dest_chain_selector` - The chain selector for the destination chain.
+ /// * `message` - The message to be sent.
+ ///
+ /// # Returns
+ ///
+ /// The fee amount in u64.
+ pub fn get_fee(
+ ctx: Context,
+ dest_chain_selector: u64,
+ message: Solana2AnyMessage,
+ ) -> Result {
+ Ok(fee_for_msg(
+ dest_chain_selector,
+ message,
+ &ctx.accounts.chain_state.dest_chain,
+ &ctx.accounts.billing_token_config.config,
+ )?
+ .amount)
+ }
+
+ /// ON RAMP FLOW
+ /// Sends a message to the destination chain.
+ ///
+ /// Request a message to be sent to the destination chain.
+ /// The method name needs to be ccip_send with Anchor encoding.
+ /// This function is called by the CCIP Sender Contract (or final user) to send a message to the CCIP Router.
+ /// The message will be sent to the receiver on the destination chain selector.
+ /// This message emits the event CCIPSendRequested with all the necessary data to be retrieved by the OffChain Code
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for sending the message.
+ /// * `dest_chain_selector` - The chain selector for the destination chain.
+ /// * `message` - The message to be sent. The size limit of data is 256 bytes.
+ pub fn ccip_send<'info>(
+ ctx: Context<'_, '_, 'info, 'info, CcipSend<'info>>,
+ dest_chain_selector: u64,
+ message: Solana2AnyMessage,
+ ) -> Result<()> {
+ // TODO: Limit send size data to 256
+
+ let fee = fee_for_msg(
+ dest_chain_selector,
+ message.clone(),
+ &ctx.accounts.chain_state.dest_chain,
+ &ctx.accounts.fee_token_config.config,
+ )?;
+
+ let transfer = token_interface::TransferChecked {
+ from: ctx
+ .accounts
+ .fee_token_user_associated_account
+ .to_account_info(),
+ to: ctx.accounts.fee_token_receiver.to_account_info(),
+ mint: ctx.accounts.fee_token_mint.to_account_info(),
+ authority: ctx.accounts.fee_billing_signer.to_account_info(),
+ };
+
+ transfer_fee(
+ fee,
+ ctx.accounts.fee_token_program.to_account_info(),
+ transfer,
+ ctx.accounts.fee_token_mint.decimals,
+ ctx.bumps.fee_billing_signer,
+ )?;
+
+ let nonce_counter_account = &mut ctx.accounts.nonce;
+
+ // Avoid Re-initialization attack
+ if nonce_counter_account.version == 0 {
+ // The authority must be the owner of the PDA, as it's their Public Key in the seed
+ // If the account is not initialized, initialize it
+ nonce_counter_account.version = 1;
+ nonce_counter_account.counter = 0;
+ }
+
+ // The Config Account stores the default values for the Router, the Solana Chain Selector, the Default Gas Limit and the Default Allow Out Of Order Execution and Admin Ownership
+ let config = ctx.accounts.config.load()?;
+
+ // The Chain State Account stores onramp and offramp information of other chains:
+ // - for the Destination Chain Selector: the latest sequence number sent from Solana to that Lane
+ // - for the Source Chain Selector: the latest sequence number received from that Lane to Solana
+ let dest_chain = &mut ctx.accounts.chain_state.dest_chain;
+
+ let overflow_add = dest_chain.state.sequence_number.checked_add(1);
+
+ require!(
+ overflow_add.is_some(),
+ CcipRouterError::ReachedMaxSequenceNumber // TODO: Can this really happen? Should we manage it differently?
+ );
+
+ dest_chain.state.sequence_number = overflow_add.unwrap();
+
+ let sender = ctx.accounts.authority.key.to_owned();
+ let source_chain_selector = config.solana_chain_selector;
+ let final_extra_args = calculate_extra_args_from(config, message.extra_args);
+
+ let mut final_nonce = 0;
+ if !final_extra_args.allow_out_of_order_execution {
+ let nonce = &mut ctx.accounts.nonce;
+ nonce.counter = nonce.counter.checked_add(1).unwrap();
+ final_nonce = nonce.counter;
+ }
+
+ let token_count = message.token_amounts.len();
+ let mut new_message: Solana2AnyRampMessage = Solana2AnyRampMessage {
+ sender,
+ receiver: message.receiver.clone(),
+ data: message.data,
+ header: RampMessageHeader {
+ message_id: [0; 32],
+ source_chain_selector,
+ dest_chain_selector,
+ sequence_number: dest_chain.state.sequence_number,
+ nonce: final_nonce,
+ },
+ extra_args: final_extra_args,
+ fee_token: message.fee_token,
+ token_amounts: vec![Solana2AnyTokenTransfer::default(); token_count],
+ };
+
+ require!(
+ message.token_indexes.len() == message.token_amounts.len(),
+ CcipRouterError::InvalidInputs,
+ );
+ let seeds = &[EXTERNAL_TOKEN_POOL_SEED, &[ctx.bumps.token_pools_signer]];
+ for (i, token_amount) in message.token_amounts.iter().enumerate() {
+ require!(
+ token_amount.amount != 0,
+ CcipRouterError::InvalidInputsTokenAmount
+ );
+
+ let (start, end) = calculate_token_pool_account_indices(
+ i,
+ &message.token_indexes,
+ ctx.remaining_accounts.len(),
+ )?;
+ let acc_list = &ctx.remaining_accounts[start..end];
+ let accs = parse_token_accounts(
+ ctx.accounts.authority.key(),
+ dest_chain_selector,
+ ctx.program_id.key(),
+ acc_list,
+ )?;
+ let router_token_pool_signer = &ctx.accounts.token_pools_signer;
+
+ // CPI: transfer token + amount from user to token pool
+ transfer_token(
+ token_amount.amount,
+ accs.token_program,
+ accs.mint,
+ accs.user_token_account,
+ accs.pool_token_account,
+ router_token_pool_signer,
+ seeds,
+ )?;
+
+ // CPI: call lockOrBurn on token pool
+ {
+ let lock_or_burn = LockOrBurnInV1 {
+ receiver: message.receiver.clone(),
+ remote_chain_selector: dest_chain_selector,
+ original_sender: ctx.accounts.authority.key(),
+ amount: token_amount.amount,
+ local_token: token_amount.token,
+ };
+ let mut acc_infos = router_token_pool_signer.to_account_infos();
+ acc_infos.extend_from_slice(&[
+ accs.pool_config.to_account_info(),
+ accs.token_program.to_account_info(),
+ accs.mint.to_account_info(),
+ accs.pool_signer.to_account_info(),
+ accs.pool_token_account.to_account_info(),
+ accs.pool_chain_config.to_account_info(),
+ ]);
+ acc_infos.extend_from_slice(accs.remaining_accounts);
+ let return_data = interact_with_pool(
+ accs.pool_program.key(),
+ router_token_pool_signer.key(),
+ acc_infos,
+ lock_or_burn,
+ seeds,
+ )?;
+
+ let data = LockOrBurnOutV1::try_from_slice(&return_data)?;
+ new_message.token_amounts[i] = Solana2AnyTokenTransfer {
+ source_pool_address: accs.pool_config.key(),
+ dest_token_address: data.dest_token_address,
+ extra_data: data.dest_pool_data,
+ amount: u64_to_le_u256(token_amount.amount), // pool on receiver chain handles decimals
+ dest_exec_data: vec![0; 0], // TODO: part of fee quoter
+ };
+ }
+ }
+
+ let message_id = &new_message.hash();
+ new_message.header.message_id.clone_from(message_id);
+
+ emit!(CCIPMessageSent {
+ dest_chain_selector,
+ sequence_number: new_message.header.sequence_number,
+ message: new_message,
+ });
+
+ Ok(())
+ }
+
+ /// OFF RAMP FLOW
+ /// Commits a report to the router.
+ ///
+ /// The method name needs to be commit with Anchor encoding.
+ ///
+ /// This function is called by the OffChain when committing one Report to the Solana Router.
+ /// In this Flow only one report is sent, the Commit Report. This is different as EVM does,
+ /// this is because here all the chain state is stored in one account per Merkle Tree Root.
+ /// So, to avoid having to send a dynamic size array of accounts, in this message only one Commit Report Account is sent.
+ /// This message validates the signatures of the report and stores the Merkle Root in the Commit Report Account.
+ /// The Report must contain an interval of messages, and the min of them must be the next sequence number expected.
+ /// The max size of the interval is 64.
+ /// This message emits two events: CommitReportAccepted and Transmitted.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for the commit.
+ /// * `report_context_byte_words` - consists of:
+ /// * report_context_byte_words[0]: ConfigDigest
+ /// * report_context_byte_words[1]: 24 byte padding, 8 byte sequence number
+ /// * report_context_byte_words[2]: ExtraHash
+ /// * `report` - The commit input report, single merkle root with RMN signatures and price updates
+ /// * `signatures` - The list of signatures. v0.29.0 - anchor idl does not build with ocr3base::SIGNATURE_LENGTH
+ pub fn commit<'info>(
+ ctx: Context<'_, '_, 'info, 'info, CommitReportContext<'info>>,
+ report_context_byte_words: [[u8; 32]; 3],
+ report: CommitInput,
+ signatures: Vec<[u8; 65]>,
+ ) -> Result<()> {
+ let report_context = ReportContext::from_byte_words(report_context_byte_words);
+
+ // The Config Account stores the default values for the Router, the Solana Chain Selector, the Default Gas Limit and the Default Allow Out Of Order Execution and Admin Ownership
+ let mut config = ctx.accounts.config.load_mut()?;
+
+ // Check if the report contains price updates
+ let has_token_price_updates = !report.price_updates.token_price_updates.is_empty();
+ let has_gas_price_updates = !report.price_updates.gas_price_updates.is_empty();
+ if has_token_price_updates || has_gas_price_updates {
+ let ocr_sequence_number = report_context.sequence_number();
+ if config.latest_price_sequence_number < ocr_sequence_number {
+ // Update the persisted sequence number
+ config.latest_price_sequence_number = ocr_sequence_number;
+ } else {
+ // TODO check if this is really necessary. EVM has this validation checking that the
+ // array of merkle roots in the report is not empty. But here, considering we only have 1 root per report,
+ // this check is just validating that the root is not zeroed
+ // (which should never happen anyway, so it may be redundant).
+ require!(
+ report.merkle_root.source_chain_selector > 0,
+ CcipRouterError::StaleCommitReport
+ );
+ }
+
+ // Remaining account represent the accounts to update (BillingTokenConfig for token price & ChainState for gas price).
+ // They must be in order, first all token accounts, then all gas accounts, matching the order of the price updates in the CommitInput.
+ // They must also all be writable so they can be updated.
+ require!(
+ ctx.remaining_accounts.len()
+ >= report.price_updates.token_price_updates.len()
+ + report.price_updates.gas_price_updates.len(),
+ CcipRouterError::InvalidInputs
+ ); // TODO consider requiring exact length match, if this is the only source of dynamic account length
+
+ // For each token price update, unpack the corresponding remaining_account and update the price.
+ // Keep in mind that the remaining_accounts are sorted in the same order as tokens and gas price updates in the report.
+ for (i, update) in report.price_updates.token_price_updates.iter().enumerate() {
+ apply_token_price_update(update, &ctx.remaining_accounts[i])?;
+ }
+
+ let offset = report.price_updates.token_price_updates.len();
+
+ // Do the same for gas price updates
+ for (i, update) in report.price_updates.gas_price_updates.iter().enumerate() {
+ apply_gas_price_update(
+ update,
+ &ctx.remaining_accounts[i + offset],
+ &mut ctx.accounts.chain_state,
+ )?;
+ }
+ }
+
+ // The Config and State for the Source Chain, containing if it is enabled, the on ramp address and the min sequence number expected for future messages
+ let source_chain_state = &mut ctx.accounts.chain_state;
+
+ require!(
+ source_chain_state.source_chain.config.is_enabled,
+ CcipRouterError::UnsupportedSourceChainSelector
+ );
+
+ // The Commit Report Account stores the information of 1 Commit Report:
+ // - Merkle Root
+ // - Timestamp of the Commit Report
+ // - Interval of Messages: The min and max seq num of the messages in the Merkle Tree
+ // - Execution State per each Message: 0 for Untouched, 1 for InProgress, 2 for Success and 3 for Failure
+ let commit_report = &mut ctx.accounts.commit_report;
+ let root = &report.merkle_root;
+
+ require!(
+ root.min_seq_nr <= root.max_seq_nr,
+ CcipRouterError::InvalidSequenceInterval
+ );
+ require!(
+ root.max_seq_nr
+ .to_owned()
+ .checked_sub(root.min_seq_nr)
+ .map_or_else(|| false, |seq_size| seq_size <= 64),
+ CcipRouterError::InvalidSequenceInterval
+ ); // As we have 64 slots to store the execution state
+ require!(
+ source_chain_state.source_chain.state.min_seq_nr == root.min_seq_nr,
+ CcipRouterError::InvalidSequenceInterval
+ );
+ require!(root.merkle_root != [0; 32], CcipRouterError::InvalidProof);
+ require!(
+ commit_report.timestamp == 0,
+ CcipRouterError::ExistingMerkleRoot
+ );
+
+ let next_seq_nr = root.max_seq_nr.checked_add(1);
+
+ require!(
+ next_seq_nr.is_some(),
+ CcipRouterError::ReachedMaxSequenceNumber
+ );
+
+ source_chain_state.source_chain.state.min_seq_nr = next_seq_nr.unwrap();
+
+ let clock: Clock = Clock::get()?;
+ commit_report.version = 1;
+ commit_report.timestamp = clock.unix_timestamp;
+ commit_report.execution_states = 0;
+ commit_report.min_msg_nr = root.min_seq_nr;
+ commit_report.max_msg_nr = root.max_seq_nr;
+
+ emit!(CommitReportAccepted {
+ merkle_root: root.clone(),
+ price_updates: report.price_updates.clone(),
+ });
+
+ config.ocr3[OcrPluginType::Commit as usize].transmit(
+ &ctx.accounts.sysvar_instructions,
+ ctx.accounts.authority.key(),
+ OcrPluginType::Commit as u8,
+ report_context,
+ &report,
+ &signatures,
+ )?;
+
+ Ok(())
+ }
+
+ /// OFF RAMP FLOW
+ /// Executes a message on the destination chain.
+ ///
+ /// The method name needs to be execute with Anchor encoding.
+ ///
+ /// This function is called by the OffChain when executing one Report to the Solana Router.
+ /// In this Flow only one message is sent, the Execution Report. This is different as EVM does,
+ /// this is because there is no try/catch mechanism to allow batch execution.
+ /// This message validates that the Merkle Tree Proof of the given message is correct and is stored in the Commit Report Account.
+ /// The message must be untouched to be executed.
+ /// This message emits the event ExecutionStateChanged with the new state of the message.
+ /// Finally, executes the CPI instruction to the receiver program in the ccip_receive message.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for the execute.
+ /// * `execution_report` - the execution report containing only one message and proofs
+ /// * `report_context_byte_words` - report_context after execution_report to match context for manually execute (proper decoding order)
+ /// * consists of:
+ /// * report_context_byte_words[0]: ConfigDigest
+ /// * report_context_byte_words[1]: 24 byte padding, 8 byte sequence number
+ /// * report_context_byte_words[2]: ExtraHash
+ pub fn execute<'info>(
+ ctx: Context<'_, '_, 'info, 'info, ExecuteReportContext<'info>>,
+ execution_report: ExecutionReportSingleChain,
+ report_context_byte_words: [[u8; 32]; 3],
+ ) -> Result<()> {
+ let report_context = ReportContext::from_byte_words(report_context_byte_words);
+ // limit borrowing of ctx
+ {
+ let config = ctx.accounts.config.load()?;
+ config.ocr3[OcrPluginType::Execution as usize].transmit(
+ &ctx.accounts.sysvar_instructions,
+ ctx.accounts.authority.key(),
+ OcrPluginType::Execution as u8,
+ report_context,
+ &execution_report,
+ &[],
+ )?;
+ }
+
+ internal_execute(ctx, execution_report)
+ }
+
+ /// Manually executes a report to the router.
+ ///
+ /// When a message is not being executed, then the user can trigger the execution manually.
+ /// No verification over the transmitter, but the message needs to be in some commit report.
+ /// It validates that the required time has passed since the commit and then executes the report.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The context containing the accounts required for the execution.
+ /// * `execution_report` - The execution report containing the message and proofs.
+ pub fn manually_execute<'info>(
+ ctx: Context<'_, '_, 'info, 'info, ExecuteReportContext<'info>>,
+ execution_report: ExecutionReportSingleChain,
+ ) -> Result<()> {
+ // limit borrowing of ctx
+ {
+ let config = ctx.accounts.config.load()?;
+
+ // validate time has passed
+ let clock: Clock = Clock::get()?;
+ let current_timestamp = clock.unix_timestamp;
+ require!(
+ current_timestamp - ctx.accounts.commit_report.timestamp
+ > config.enable_manual_execution_after,
+ CcipRouterError::ManualExecutionNotAllowed
+ );
+ }
+ internal_execute(ctx, execution_report)
+ }
+}
+
+fn apply_token_price_update<'info>(
+ token_update: &TokenPriceUpdate,
+ token_config_account_info: &'info AccountInfo<'info>,
+) -> Result<()> {
+ let (expected, _) = Pubkey::find_program_address(
+ &[FEE_BILLING_TOKEN_CONFIG, token_update.source_token.as_ref()],
+ &crate::ID,
+ );
+ require_keys_eq!(
+ token_config_account_info.key(),
+ expected,
+ CcipRouterError::InvalidInputs
+ );
+
+ require!(
+ token_config_account_info.is_writable,
+ CcipRouterError::InvalidInputs
+ );
+
+ let token_config_account: &mut Account =
+ &mut Account::try_from(token_config_account_info)?;
+
+ require!(
+ token_config_account.version == 1,
+ CcipRouterError::InvalidInputs
+ );
+
+ token_config_account.config.usd_per_token = TimestampedPackedU224 {
+ value: token_update.usd_per_token,
+ timestamp: Clock::get()?.unix_timestamp,
+ };
+
+ emit!(UsdPerTokenUpdated {
+ token: token_config_account.config.mint,
+ value: token_config_account.config.usd_per_token.value,
+ timestamp: token_config_account.config.usd_per_token.timestamp,
+ });
+
+ // As the account is manually loaded from the AccountInfo, it also needs to be manually
+ // written back to so the changes are persisted.
+ token_config_account.exit(&crate::ID)
+}
+
+fn apply_gas_price_update<'info>(
+ gas_update: &GasPriceUpdate,
+ chain_state_account_info: &'info AccountInfo<'info>,
+ current_chain_state: &mut Account,
+) -> Result<()> {
+ let (expected, _) = Pubkey::find_program_address(
+ &[
+ CHAIN_STATE_SEED,
+ gas_update.dest_chain_selector.to_le_bytes().as_ref(),
+ ],
+ &crate::ID,
+ );
+ require_keys_eq!(
+ chain_state_account_info.key(),
+ expected,
+ CcipRouterError::InvalidInputs
+ );
+
+ require!(
+ chain_state_account_info.is_writable,
+ CcipRouterError::InvalidInputs
+ );
+
+ if chain_state_account_info.key() == current_chain_state.key() {
+ // The passed-in chain_state account info via remaining_accounts may already be the same one as in the context.
+ // If that's the case, we have to just use the one in the context to avoid overwriting one with the other.
+ update_chain_state_gas_price(current_chain_state, gas_update)?;
+ } else {
+ // if updating a different chain's state, then Anchor won't automatically (de)serialize the account
+ // as it is not the one in the context, so we have to do it manually load it and write it back
+ let chain_state_account = &mut Account::try_from(chain_state_account_info)?;
+ update_chain_state_gas_price(chain_state_account, gas_update)?;
+ chain_state_account.exit(&crate::ID)?;
+ };
+ Ok(())
+}
+
+fn update_chain_state_gas_price(
+ chain_state_account: &mut Account,
+ gas_update: &GasPriceUpdate,
+) -> Result<()> {
+ require!(
+ chain_state_account.version == 1,
+ CcipRouterError::InvalidInputs
+ );
+
+ chain_state_account.dest_chain.state.usd_per_unit_gas = TimestampedPackedU224 {
+ value: gas_update.usd_per_unit_gas,
+ timestamp: Clock::get()?.unix_timestamp,
+ };
+
+ emit!(UsdPerUnitGasUpdated {
+ dest_chain: gas_update.dest_chain_selector,
+ value: chain_state_account.dest_chain.state.usd_per_unit_gas.value,
+ timestamp: chain_state_account
+ .dest_chain
+ .state
+ .usd_per_unit_gas
+ .timestamp,
+ });
+
+ Ok(())
+}
+
+// internal_execute is the base execution logic without any additional validation
+fn internal_execute<'info>(
+ ctx: Context<'_, '_, 'info, 'info, ExecuteReportContext<'info>>,
+ execution_report: ExecutionReportSingleChain,
+) -> Result<()> {
+ // TODO: Limit send size data to 256
+
+ // The Config Account stores the default values for the Router, the Solana Chain Selector, the Default Gas Limit and the Default Allow Out Of Order Execution and Admin Ownership
+ let config = ctx.accounts.config.load()?;
+ let solana_chain_selector = config.solana_chain_selector;
+
+ // The Config and State for the Source Chain, containing if it is enabled, the on ramp address and the min sequence number expected for future messages
+ let source_chain_state = &ctx.accounts.chain_state;
+ let on_ramp_address = &source_chain_state.source_chain.config.on_ramp;
+
+ // The Commit Report Account stores the information of 1 Commit Report:
+ // - Merkle Root
+ // - Timestamp of the Commit Report
+ // - Interval of Messages: The min and max seq num of the messages in the Merkle Tree
+ // - Execution State per each Message: 0 for Untouched, 1 for InProgress, 2 for Success and 3 for Failure
+ let commit_report = &mut ctx.accounts.commit_report;
+
+ let message_header = execution_report.message.header;
+
+ validate_execution_report(
+ &execution_report,
+ source_chain_state,
+ commit_report,
+ &message_header,
+ solana_chain_selector,
+ )?;
+
+ let original_state = commit_report.get_state(message_header.sequence_number);
+
+ if original_state == MessageExecutionState::Success {
+ emit!(SkippedAlreadyExecutedMessage {
+ source_chain_selector: message_header.source_chain_selector,
+ sequence_number: message_header.sequence_number,
+ });
+ return Ok(());
+ }
+
+ let hashed_leaf = verify_merkle_root(&execution_report, on_ramp_address)?;
+
+ // send tokens any -> SOL
+ require!(
+ execution_report.token_indexes.len() == execution_report.message.token_amounts.len()
+ && execution_report.token_indexes.len() == execution_report.offchain_token_data.len(),
+ CcipRouterError::InvalidInputs,
+ );
+ let seeds = &[EXTERNAL_TOKEN_POOL_SEED, &[ctx.bumps.token_pools_signer]];
+ let mut token_amounts =
+ vec![SolanaTokenAmount::default(); execution_report.token_indexes.len()];
+
+ // handle tokens
+ // note: indexes are used instead of counts in case more accounts need to be passed in remaining_accounts before token accounts
+ // token_indexes = [2, 4] where remaining_accounts is [custom_account, custom_account, token1_account1, token1_account2, token2_account1, token2_account2] for example
+ for (i, token_amount) in execution_report.message.token_amounts.iter().enumerate() {
+ let (start, end) = calculate_token_pool_account_indices(
+ i,
+ &execution_report.token_indexes,
+ ctx.remaining_accounts.len(),
+ )?;
+ let acc_list = &ctx.remaining_accounts[start..end];
+ let accs = parse_token_accounts(
+ execution_report.message.receiver,
+ execution_report.message.header.source_chain_selector,
+ ctx.program_id.key(),
+ acc_list,
+ )?;
+ let router_token_pool_signer = &ctx.accounts.token_pools_signer;
+
+ let init_bal = get_balance(accs.user_token_account)?;
+
+ // CPI: call lockOrBurn on token pool
+ let release_or_mint = ReleaseOrMintInV1 {
+ original_sender: execution_report.message.sender.clone(),
+ receiver: execution_report.message.receiver,
+ amount: token_amount.amount,
+ local_token: token_amount.dest_token_address,
+ remote_chain_selector: execution_report.message.header.source_chain_selector,
+ source_pool_address: token_amount.source_pool_address.clone(),
+ source_pool_data: token_amount.extra_data.clone(),
+ offchain_token_data: execution_report.offchain_token_data[i].clone(),
+ };
+ let mut acc_infos = router_token_pool_signer.to_account_infos();
+ acc_infos.extend_from_slice(&[
+ accs.pool_config.to_account_info(),
+ accs.token_program.to_account_info(),
+ accs.mint.to_account_info(),
+ accs.pool_signer.to_account_info(),
+ accs.pool_token_account.to_account_info(),
+ accs.pool_chain_config.to_account_info(),
+ accs.user_token_account.to_account_info(),
+ ]);
+ acc_infos.extend_from_slice(accs.remaining_accounts);
+ let return_data = interact_with_pool(
+ accs.pool_program.key(),
+ router_token_pool_signer.key(),
+ acc_infos,
+ release_or_mint,
+ seeds,
+ )?;
+
+ require!(
+ return_data.len() == CCIP_POOL_V1_RET_BYTES,
+ CcipRouterError::OfframpInvalidDataLength
+ );
+
+ token_amounts[i] = SolanaTokenAmount {
+ token: accs.mint.key(),
+ amount: ReleaseOrMintOutV1::try_from_slice(&return_data)?.destination_amount,
+ };
+
+ let post_bal = get_balance(accs.user_token_account)?;
+ require!(
+ post_bal >= init_bal && post_bal - init_bal == token_amounts[i].amount,
+ CcipRouterError::OfframpReleaseMintBalanceMismatch
+ );
+ }
+
+ let message = Any2SolanaMessage {
+ message_id: execution_report.message.header.message_id,
+ source_chain_selector: execution_report.source_chain_selector,
+ sender: execution_report.message.sender,
+ data: execution_report.message.data,
+ token_amounts,
+ };
+
+ // handle CPI call if there are extra accounts
+ // case: no tokens, but there are remaining_accounts passed in
+ // case: tokens, but the first token has a non-zero index (indicating extra accounts before token accounts)
+ if should_execute_messaging(
+ &execution_report.token_indexes,
+ ctx.remaining_accounts.is_empty(),
+ ) {
+ let (msg_program, msg_accounts) = parse_messaging_accounts(
+ &execution_report.token_indexes,
+ execution_report.message.receiver,
+ execution_report.message.extra_args.accounts,
+ ctx.remaining_accounts,
+ )?;
+
+ // The External Execution Config Account is used to sign the CPI instruction
+ let external_execution_config = &ctx.accounts.external_execution_config;
+
+ // The accounts of the user that will be used in the CPI instruction, none of them are signers
+ // They need to specify if mutable or not, but none of them is allowed to init, realloc or close
+ // note: CPI signer is always first account
+ let mut acc_infos = external_execution_config.to_account_infos();
+ acc_infos.extend_from_slice(msg_accounts);
+
+ let acc_metas: Vec = acc_infos
+ .to_vec()
+ .iter()
+ .flat_map(|acc_info| {
+ // Check signer from PDA External Execution config
+ let is_signer = acc_info.key() == external_execution_config.key();
+ acc_info.to_account_metas(Some(is_signer))
+ })
+ .collect();
+
+ let data = build_receiver_discriminator_and_data(message)?;
+
+ let instruction = Instruction {
+ program_id: msg_program.key(), // The receiver Program Id that will handle the ccip_receive message
+ accounts: acc_metas,
+ data,
+ };
+
+ let seeds = &[
+ EXTERNAL_EXECUTION_CONFIG_SEED,
+ &[ctx.bumps.external_execution_config],
+ ];
+ let signer = &[&seeds[..]];
+
+ invoke_signed(&instruction, &acc_infos, signer)?;
+ }
+
+ let new_state = MessageExecutionState::Success;
+ commit_report.set_state(message_header.sequence_number, new_state.to_owned());
+
+ emit!(ExecutionStateChanged {
+ source_chain_selector: message_header.source_chain_selector,
+ sequence_number: message_header.sequence_number,
+ message_id: message_header.message_id, // Unique identifier for the message, generated with the source chain's encoding scheme
+ message_hash: hashed_leaf, // Hash of the message using Solana encoding
+ state: new_state,
+ });
+
+ Ok(())
+}
+
+// should_execute_messaging checks if there remaining_accounts that are not being used for token pools
+// case: no tokens, but there are remaining_accounts passed in
+// case: tokens, but the first token has a non-zero index (indicating extra accounts before token accounts)
+fn should_execute_messaging(token_indexes: &[u8], remaining_accounts_empty: bool) -> bool {
+ (token_indexes.is_empty() && !remaining_accounts_empty)
+ || (!token_indexes.is_empty() && token_indexes[0] != 0)
+}
+
+/// parse_message_accounts returns all the accounts needed to execute the CPI instruction
+/// It also validates that the accounts sent in the message match the ones sent in the source chain
+///
+/// # Arguments
+/// * `token_indexes` - start indexes of token pool accounts, used to determine ending index for arbitrary messaging accounts
+/// * `receiver` - receiver address from x-chain message, used to validate `accounts`
+/// * `source_accounts` - arbitrary messaging accounts from the x-chain message, used to validate `accounts`. expected order is: [program, ...additional message accounts]
+/// * `accounts` - accounts passed via `ctx.remaining_accounts`. expected order is: [program, receiver, ...additional message accounts]
+fn parse_messaging_accounts<'info>(
+ token_indexes: &[u8],
+ receiver: Pubkey,
+ source_accounts: Vec,
+ accounts: &'info [AccountInfo<'info>],
+) -> Result<(&'info AccountInfo<'info>, &'info [AccountInfo<'info>])> {
+ let end_ind = if token_indexes.is_empty() {
+ accounts.len()
+ } else {
+ token_indexes[0] as usize
+ };
+
+ let msg_program = &accounts[0];
+ let msg_accounts = &accounts[1..end_ind];
+
+ let source_program = &source_accounts[0];
+ let source_msg_accounts = &source_accounts[1..source_accounts.len()];
+
+ require!(
+ source_program.pubkey == msg_program.key(),
+ CcipRouterError::InvalidInputs,
+ );
+
+ require!(
+ msg_accounts[0].key() == receiver,
+ CcipRouterError::InvalidInputs
+ );
+
+ // assert same number of accounts passed from message and transaction (not including program)
+ // source_msg_accounts + 1 to account for separately passed receiver address
+ require!(
+ source_msg_accounts.len() + 1 == msg_accounts.len(),
+ CcipRouterError::InvalidInputs
+ );
+
+ // Validate the addresses of all the accounts match the ones in source chain
+ if msg_accounts.len() > 1 {
+ // Ignore the first account as it's the receiver
+ let accounts_to_validate = &msg_accounts[1..msg_accounts.len()];
+ require!(
+ accounts_to_validate.len() == source_msg_accounts.len(),
+ CcipRouterError::InvalidInputs
+ );
+ for (i, acc) in source_msg_accounts.iter().enumerate() {
+ let current_acc = &msg_accounts[i + 1];
+ require!(
+ acc.pubkey == current_acc.key(),
+ CcipRouterError::InvalidInputs
+ );
+ require!(
+ acc.is_writable == current_acc.is_writable,
+ CcipRouterError::InvalidInputs
+ );
+ }
+ }
+
+ Ok((msg_program, msg_accounts))
+}
+
+fn calculate_extra_args_from(
+ default_config: Ref,
+ extra_args: ExtraArgsInput,
+) -> EvmExtraArgs {
+ let mut result_args = EvmExtraArgs {
+ gas_limit: default_config.default_gas_limit.to_owned(),
+ allow_out_of_order_execution: default_config.default_allow_out_of_order_execution != 0,
+ };
+
+ if let Some(gas_limit) = extra_args.gas_limit {
+ gas_limit.clone_into(&mut result_args.gas_limit)
+ }
+
+ if let Some(allow_out_of_order_execution) = extra_args.allow_out_of_order_execution {
+ allow_out_of_order_execution.clone_into(&mut result_args.allow_out_of_order_execution)
+ }
+
+ result_args
+}
+
+/// Build the instruction data (discriminator + any other data)
+fn build_receiver_discriminator_and_data(ramp_message: Any2SolanaMessage) -> Result> {
+ let m: std::result::Result, std::io::Error> = ramp_message.try_to_vec();
+ require!(m.is_ok(), CcipRouterError::InvalidMessage);
+ let message = m.unwrap();
+
+ let mut data = Vec::with_capacity(8);
+ data.extend_from_slice(&CCIP_RECEIVE_DISCRIMINATOR);
+ data.extend_from_slice(&message);
+
+ Ok(data)
+}
+
+fn validate_source_chain_config(
+ _source_chain_selector: u64,
+ _config: &SourceChainConfig,
+) -> Result<()> {
+ // As of now, the config has very few properties and there is nothing to validate yet.
+ // This is a placeholder to add validations as that config object grows.
+ Ok(())
+}
+
+fn validate_dest_chain_config(dest_chain_selector: u64, config: &DestChainConfig) -> Result<()> {
+ // TODO improve errors
+ require!(dest_chain_selector != 0, CcipRouterError::InvalidInputs);
+ require!(
+ config.default_tx_gas_limit != 0,
+ CcipRouterError::InvalidInputs
+ );
+ require!(
+ config.default_tx_gas_limit <= config.max_per_msg_gas_limit,
+ CcipRouterError::InvalidInputs
+ );
+ require!(
+ config.chain_family_selector != [0; 4],
+ CcipRouterError::InvalidInputs
+ );
+ Ok(())
+}
+
+#[error_code]
+
+pub enum CcipRouterError {
+ #[msg("The given sequence interval is invalid")]
+ InvalidSequenceInterval,
+ #[msg("The given Merkle Root is missing")]
+ RootNotCommitted,
+ #[msg("The given Merkle Root is already committed")]
+ ExistingMerkleRoot,
+ #[msg("The signer is unauthorized")]
+ Unauthorized,
+ #[msg("Invalid inputs")]
+ InvalidInputs,
+ #[msg("Source chain selector not supported")]
+ UnsupportedSourceChainSelector,
+ #[msg("Destination chain selector not supported")]
+ UnsupportedDestinationChainSelector,
+ #[msg("Invalid Proof for Merkle Root")]
+ InvalidProof,
+ #[msg("Invalid message format")]
+ InvalidMessage,
+ #[msg("Reached max sequence number")]
+ ReachedMaxSequenceNumber,
+ #[msg("Manual execution not allowed")]
+ ManualExecutionNotAllowed,
+ #[msg("Invalid pool account account indices")]
+ InvalidInputsTokenIndices,
+ #[msg("Invalid pool accounts")]
+ InvalidInputsPoolAccounts,
+ #[msg("Invalid token accounts")]
+ InvalidInputsTokenAccounts,
+ #[msg("Invalid config account")]
+ InvalidInputsConfigAccounts,
+ #[msg("Invalid Token Admin Registry account")]
+ InvalidInputsTokenAdminRegistryAccounts,
+ #[msg("Invalid LookupTable account")]
+ InvalidInputsLookupTableAccounts,
+ #[msg("Cannot send zero tokens")]
+ InvalidInputsTokenAmount,
+ #[msg("Release or mint balance mismatch")]
+ OfframpReleaseMintBalanceMismatch,
+ #[msg("Invalid data length")]
+ OfframpInvalidDataLength,
+ #[msg("Stale commit report")]
+ StaleCommitReport,
+ #[msg("Destination chain disabled")]
+ DestinationChainDisabled,
+ #[msg("Fee token disabled")]
+ FeeTokenDisabled,
+}
+
+pub fn validate_execution_report<'info>(
+ execution_report: &ExecutionReportSingleChain,
+ source_chain_state: &Account<'info, ChainState>,
+ commit_report: &Account<'info, CommitReport>,
+ message_header: &RampMessageHeader,
+ solana_chain_selector: u64,
+) -> Result<()> {
+ require!(
+ execution_report.message.header.nonce == 0,
+ CcipRouterError::InvalidInputs
+ );
+
+ require!(
+ source_chain_state.source_chain.config.is_enabled,
+ CcipRouterError::UnsupportedSourceChainSelector
+ );
+
+ require!(
+ execution_report.message.header.sequence_number >= commit_report.min_msg_nr
+ && execution_report.message.header.sequence_number <= commit_report.max_msg_nr,
+ CcipRouterError::InvalidSequenceInterval
+ );
+
+ require!(
+ message_header.source_chain_selector == execution_report.source_chain_selector,
+ CcipRouterError::UnsupportedSourceChainSelector
+ );
+ require!(
+ message_header.dest_chain_selector == solana_chain_selector,
+ CcipRouterError::UnsupportedDestinationChainSelector
+ );
+ require!(
+ commit_report.timestamp != 0,
+ CcipRouterError::RootNotCommitted
+ );
+
+ Ok(())
+}
+
+pub fn verify_merkle_root(
+ execution_report: &ExecutionReportSingleChain,
+ on_ramp_address: &[u8],
+) -> Result<[u8; 32]> {
+ let hashed_leaf = execution_report.message.hash(on_ramp_address);
+ let verified_root: std::result::Result<[u8; 32], MerkleError> =
+ calculate_merkle_root(hashed_leaf, execution_report.proofs.clone());
+ require!(
+ verified_root.is_ok() && verified_root.unwrap() == execution_report.root,
+ CcipRouterError::InvalidProof
+ );
+ Ok(hashed_leaf)
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/merkle.rs b/chains/solana/contracts/programs/ccip-router/src/merkle.rs
new file mode 100644
index 000000000..f3d7eca0b
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/merkle.rs
@@ -0,0 +1,138 @@
+use anchor_lang::solana_program::hash;
+
+const MAX_NUM_HASHES: usize = 128; // TODO: Change this to 256 when supporting commit reports with 256 messages
+
+#[derive(Debug)]
+pub enum MerkleError {
+ InvalidProof,
+}
+
+fn hash_pair(hash1: &[u8; 32], hash2: &[u8; 32]) -> [u8; 32] {
+ if hash1 < hash2 {
+ hash::hashv(&[hash1, hash2]).to_bytes()
+ } else {
+ hash::hashv(&[hash2, hash1]).to_bytes()
+ }
+}
+
+pub fn calculate_merkle_root(
+ hashed_leaf: [u8; 32],
+ proofs: Vec<[u8; 32]>,
+) -> Result<[u8; 32], MerkleError> {
+ let proofs_len = proofs.len();
+
+ if proofs_len > MAX_NUM_HASHES {
+ return Err(MerkleError::InvalidProof);
+ }
+
+ let mut hash = hashed_leaf;
+
+ for p in proofs {
+ hash = hash_pair(&hash, &p);
+ }
+
+ Ok(hash)
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+
+ #[test]
+ fn test_calculate_merkle_root_valid() {
+ let hexa_leaf = "7e1ff3c10bacb7a70bd9dbaa1b2ddeb4c860c6db3c3557d31baff96222505e2a";
+ let hashed_leaf: [u8; 32] = hex::decode(hexa_leaf)
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap();
+
+ let proofs =
+ vec![
+ hex::decode("22ae9b57dfb3f830622fb5ee07a795961532dc9ab7f641271ac7cf1b89cb39f6")
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap(),
+ ];
+ let expected_root: [u8; 32] =
+ hex::decode("d23481665c993b84af89b0fec64bc789ba1d39b97e2e947f550e69a0eef3cf5c")
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap();
+
+ let result = calculate_merkle_root(hashed_leaf, proofs);
+ assert!(result.is_ok());
+ assert_eq!(expected_root, result.unwrap());
+ }
+
+ #[test]
+ fn test_calculate_merkle_root_from_size_1() {
+ let hexa_leaf = "7e1ff3c10bacb7a70bd9dbaa1b2ddeb4c860c6db3c3557d31baff96222505e2a";
+ let hashed_leaf: [u8; 32] = hex::decode(hexa_leaf)
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap();
+
+ let proofs = vec![];
+ let expected_root: [u8; 32] =
+ hex::decode("7e1ff3c10bacb7a70bd9dbaa1b2ddeb4c860c6db3c3557d31baff96222505e2a")
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap();
+
+ let result = calculate_merkle_root(hashed_leaf, proofs);
+ assert!(result.is_ok());
+ assert_eq!(expected_root, result.unwrap());
+ }
+
+ #[test]
+ fn test_calculate_merkle_root_invalid_proof() {
+ let hexa_leaf = "7e1ff3c10bacb7a70bd9dbaa1b2ddeb4c860c6db3c3557d31baff96222505e2a";
+ let hashed_leaf: [u8; 32] = hex::decode(hexa_leaf)
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap();
+ let proofs = vec![[0x44; 32]; 129]; // Array size greater than 128
+
+ let result = calculate_merkle_root(hashed_leaf, proofs);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_create_merkle_validate_proof() {
+ let a = hex::decode("1ac2f192702849e03dfe5c31ec66a4f6408b5eb16cc02f1583ce713b22be92ed")
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap();
+ let proofs = vec![
+ hex::decode("81caa6284d8f53a5cd06190bd30c33c4622e6dde8b1204e953f98d768eeab615")
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap(),
+ hex::decode("16e4eb7487ed6b9476a0aca9294a0d5e6c7fe7bf7d5ca71908d2e46802843135")
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap(),
+ ];
+
+ let expected_root: [u8; 32] =
+ hex::decode("00e9612b8588dc36a210ac439ba6569ca5263a98b3f9c2da5b342dc7925d3393")
+ .unwrap()
+ .to_owned()
+ .try_into()
+ .unwrap();
+
+ let result = calculate_merkle_root(a, proofs);
+ assert!(result.is_ok());
+ assert_eq!(expected_root, result.unwrap());
+ }
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/messages.rs b/chains/solana/contracts/programs/ccip-router/src/messages.rs
new file mode 100644
index 000000000..42eb826d2
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/messages.rs
@@ -0,0 +1,346 @@
+use crate::{ReportContext, TOKENPOOL_RELEASE_OR_MINT_DISCRIMINATOR};
+use anchor_lang::prelude::*;
+
+use crate::{ocr3base::Ocr3Report, ToTxData, TOKENPOOL_LOCK_OR_BURN_DISCRIMINATOR};
+
+#[derive(Clone, Copy, AnchorSerialize, AnchorDeserialize)]
+// Family-agnostic header for OnRamp & OffRamp messages.
+// The messageId is not expected to match hash(message), since it may originate from another ramp family
+pub struct RampMessageHeader {
+ pub message_id: [u8; 32], // Unique identifier for the message, generated with the source chain's encoding scheme
+ pub source_chain_selector: u64, // the chain selector of the source chain, note: not chainId
+ pub dest_chain_selector: u64, // the chain selector of the destination chain, note: not chainId
+ pub sequence_number: u64, // sequence number, not unique across lanes
+ pub nonce: u64, // nonce for this lane for this sender, not unique across senders/lanes
+}
+
+impl RampMessageHeader {
+ pub fn len(&self) -> usize {
+ 32 // message_id
+ + 8 // source_chain
+ + 8 // dest_chain
+ + 8 // sequence
+ + 8 // nonce
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+/// Report that is submitted by the execution DON at the execution phase. (including chain selector data)
+pub struct ExecutionReportSingleChain {
+ pub source_chain_selector: u64,
+ pub message: Any2SolanaRampMessage,
+ pub offchain_token_data: Vec>, // https://github.com/smartcontractkit/chainlink/blob/885baff9479e935e0fc34d9f52214a32c158eac5/contracts/src/v0.8/ccip/libraries/Internal.sol#L72
+ pub root: [u8; 32],
+ pub proofs: Vec<[u8; 32]>,
+
+ // NOT HASHED
+ pub token_indexes: Vec, // outside of message because this is not available during commit stage
+}
+
+impl Ocr3Report for ExecutionReportSingleChain {
+ fn hash(&self, _: &ReportContext) -> [u8; 32] {
+ [0; 32] // not needed, this report is not hashed for signing
+ }
+ fn len(&self) -> usize {
+ let offchain_token_data_len = self
+ .offchain_token_data
+ .iter()
+ .fold(0, |acc, e| acc + 4 + e.len());
+
+ 8 // source chain selector
+ + self.message.len() // ccip message
+ + 4 + offchain_token_data_len// offchain_token_data
+ + 32 // root
+ + 4 + self.proofs.len() * 32 // count + proofs
+ + 4 + self.token_indexes.len() // token_indexes
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, InitSpace)]
+pub struct SolanaAccountMeta {
+ pub pubkey: Pubkey,
+ pub is_writable: bool,
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct SolanaExtraArgs {
+ pub compute_units: u32,
+ pub accounts: Vec,
+}
+
+impl SolanaExtraArgs {
+ pub fn len(&self) -> usize {
+ 4 // compute units
+ + 4 + self.accounts.len() * SolanaAccountMeta::INIT_SPACE // additional accounts
+ }
+}
+
+#[derive(Clone, Copy, AnchorSerialize, AnchorDeserialize)]
+pub struct EvmExtraArgs {
+ pub gas_limit: u128,
+ pub allow_out_of_order_execution: bool,
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct Any2SolanaRampMessage {
+ pub header: RampMessageHeader,
+ pub sender: Vec,
+ pub data: Vec,
+ // receiver is used as the target for the two main functionalities
+ // token transfers: recipient of token transfers (associated token addresses are validated against this address)
+ // arbitrary messaging: expected account in the declared arbitrary messaging accounts (2nd in the list of the accounts)
+ pub receiver: Pubkey,
+ pub token_amounts: Vec,
+ pub extra_args: SolanaExtraArgs,
+}
+
+impl Any2SolanaRampMessage {
+ pub fn hash(&self, on_ramp_address: &[u8]) -> [u8; 32] {
+ use anchor_lang::solana_program::hash;
+
+ // Calculate vectors size to ensure that the hash is unique
+ let sender_size = [self.sender.len() as u8];
+ let on_ramp_address_size = [on_ramp_address.len() as u8];
+ let data_size = self.data.len() as u16; // u16 > maximum transaction size, u8 may have overflow
+
+ // RampMessageHeader struct
+ let header_source_chain_selector = self.header.source_chain_selector.to_be_bytes();
+ let header_dest_chain_selector = self.header.dest_chain_selector.to_be_bytes();
+ let header_sequence_number = self.header.sequence_number.to_be_bytes();
+ let header_nonce = self.header.nonce.to_be_bytes();
+
+ // Extra Args struct
+ let extra_args_compute_units = self.extra_args.compute_units.to_be_bytes();
+ let extra_args_accounts_len = [self.extra_args.accounts.len() as u8];
+ let extra_args_accounts = self.extra_args.accounts.try_to_vec().unwrap();
+
+ // TODO: Hash token amounts
+
+ // NOTE: calling hash::hashv is orders of magnitude cheaper than using Hasher::hashv
+ // As similar as https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/ccip/offRamp/OffRamp.sol#L402
+ let result = hash::hashv(&[
+ "Any2SolanaMessageHashV1".as_bytes(),
+ &header_source_chain_selector,
+ &header_dest_chain_selector,
+ &on_ramp_address_size,
+ on_ramp_address,
+ &self.header.message_id,
+ &self.receiver.to_bytes(),
+ &header_sequence_number,
+ &extra_args_compute_units,
+ &extra_args_accounts_len,
+ &extra_args_accounts,
+ &header_nonce,
+ &sender_size,
+ &self.sender,
+ &data_size.to_be_bytes(),
+ &self.data,
+ ]);
+
+ result.to_bytes()
+ }
+
+ pub fn len(&self) -> usize {
+ let token_len = self.token_amounts.iter().fold(0, |acc, e| acc + e.len());
+
+ self.header.len() // header
+ + 4 + self.sender.len() // sender
+ + 4 + self.data.len() // data
+ + 32 // receiver
+ + 4 + token_len // token_amount
+ + self.extra_args.len() // extra_args
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+// Family-agnostic message emitted from the OnRamp
+// Note: hash(Any2SolanaRampMessage) != hash(Solana2AnyRampMessage) due to encoding & parameter differences
+// messageId = hash(Solana2AnyRampMessage) using the source EVM chain's encoding format
+pub struct Solana2AnyRampMessage {
+ pub header: RampMessageHeader, // Message header
+ pub sender: Pubkey, // sender address on the source chain
+ pub data: Vec, // arbitrary data payload supplied by the message sender
+ pub receiver: Vec, // receiver address on the destination chain
+ pub extra_args: EvmExtraArgs, // destination-chain specific extra args, such as the gasLimit for EVM chains
+ pub fee_token: Pubkey,
+ pub token_amounts: Vec,
+}
+
+impl Solana2AnyRampMessage {
+ pub fn hash(&self) -> [u8; 32] {
+ use anchor_lang::solana_program::hash;
+
+ // Push Data Size to ensure that the hash is unique
+ let data_size = self.data.len() as u16; // u16 > maximum transaction size, u8 may have overflow
+
+ // RampMessageHeader struct
+ let header_source_chain_selector = self.header.source_chain_selector.to_be_bytes();
+ let header_dest_chain_selector = self.header.dest_chain_selector.to_be_bytes();
+ let header_sequence_number = self.header.sequence_number.to_be_bytes();
+ let header_nonce = self.header.nonce.to_be_bytes();
+
+ // Extra Args struct
+ let extra_args_gas_limit = self.extra_args.gas_limit.to_be_bytes();
+ let extra_args_allow_out_of_order_execution =
+ [self.extra_args.allow_out_of_order_execution as u8];
+
+ // NOTE: calling hash::hashv is orders of magnitude cheaper than using Hasher::hashv
+ let result = hash::hashv(&[
+ &self.sender.to_bytes(),
+ &self.receiver,
+ &data_size.to_be_bytes(),
+ &self.data,
+ &header_source_chain_selector,
+ &header_dest_chain_selector,
+ &header_sequence_number,
+ &header_nonce,
+ &extra_args_gas_limit,
+ &extra_args_allow_out_of_order_execution,
+ ]);
+
+ result.to_bytes()
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, Default)]
+pub struct Solana2AnyTokenTransfer {
+ // The source pool address. This value is trusted as it was obtained through the onRamp. It can be relied
+ // upon by the destination pool to validate the source pool.
+ pub source_pool_address: Pubkey,
+ // The address of the destination token.
+ // This value is UNTRUSTED as any pool owner can return whatever value they want.
+ pub dest_token_address: Vec,
+ // Optional pool data to be transferred to the destination chain. By default this is capped at
+ // CCIP_LOCK_OR_BURN_V1_RET_BYTES bytes. If more data is required, the TokenTransferFeeConfig.destBytesOverhead
+ // has to be set for the specific token.
+ pub extra_data: Vec,
+ pub amount: [u8; 32], // LE encoded u256 - cross-chain token amount is always u256
+ // Destination chain data used to execute the token transfer on the destination chain. For an EVM destination, it
+ // consists of the amount of gas available for the releaseOrMint and transfer calls made by the offRamp.
+ pub dest_exec_data: Vec,
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct Any2SolanaTokenTransfer {
+ // The source pool address encoded to bytes. This value is trusted as it is obtained through the onRamp. It can be
+ // relied upon by the destination pool to validate the source pool.
+ pub source_pool_address: Vec,
+ pub dest_token_address: Pubkey, // Address of destination token
+ pub dest_gas_amount: u32, // The amount of gas available for the releaseOrMint and transfer calls on the offRamp.
+ // Optional pool data to be transferred to the destination chain. Be default this is capped at
+ // CCIP_LOCK_OR_BURN_V1_RET_BYTES bytes. If more data is required, the TokenTransferFeeConfig.destBytesOverhead
+ // has to be set for the specific token.
+ pub extra_data: Vec,
+ pub amount: [u8; 32], // LE encoded u256, any cross-chain token amounts are u256
+}
+
+impl Any2SolanaTokenTransfer {
+ pub fn len(&self) -> usize {
+ 4 + self.source_pool_address.len() // source_pool
+ + 32 // token_address
+ + 4 // gas_amount
+ + 4 + self.extra_data.len() // extra_data
+ + 32 // amount
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct LockOrBurnInV1 {
+ pub receiver: Vec, // The recipient of the tokens on the destination chain
+ pub remote_chain_selector: u64, // The chain ID of the destination chain
+ pub original_sender: Pubkey, // The original sender of the tx on the source chain
+ pub amount: u64, // solana-specific amount uses u64 - the amount of tokens to lock or burn, denominated in the source token's decimals
+ pub local_token: Pubkey, // The address on this chain of the mint account to lock or burn
+}
+
+impl ToTxData for LockOrBurnInV1 {
+ fn to_tx_data(&self) -> Vec {
+ let mut data = Vec::new();
+ data.extend_from_slice(&TOKENPOOL_LOCK_OR_BURN_DISCRIMINATOR);
+ data.extend_from_slice(&self.try_to_vec().unwrap());
+ data
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct ReleaseOrMintInV1 {
+ pub original_sender: Vec, // The original sender of the tx on the source chain
+ pub remote_chain_selector: u64, // ─╮ The chain ID of the source chain
+ pub receiver: Pubkey, // ───────────╯ The Token Associated Account that will receive the tokens on the destination chain.
+ pub amount: [u8; 32], // LE u256 amount - The amount of tokens to release or mint, denominated in the source token's decimals, pool expected to handle conversation to solana token specifics
+ pub local_token: Pubkey, // The address of the Token Mint Account on Solana
+ /// @dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the
+ /// expected pool address for the given remoteChainSelector.
+ pub source_pool_address: Vec, // The address bytes of the source pool
+ pub source_pool_data: Vec, // The data received from the source pool to process the release or mint
+ /// @dev WARNING: offchainTokenData is untrusted data.
+ pub offchain_token_data: Vec, // The offchain data to process the release or mint
+}
+
+impl ToTxData for ReleaseOrMintInV1 {
+ fn to_tx_data(&self) -> Vec {
+ let mut data = Vec::new();
+ data.extend_from_slice(&TOKENPOOL_RELEASE_OR_MINT_DISCRIMINATOR);
+ data.extend_from_slice(&self.try_to_vec().unwrap());
+ data
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct LockOrBurnOutV1 {
+ pub dest_token_address: Vec,
+ pub dest_pool_data: Vec,
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct ReleaseOrMintOutV1 {
+ pub destination_amount: u64, // TODO: u256 on EVM?
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+ use anchor_lang::solana_program::pubkey::Pubkey;
+
+ /// Builds a message and hash it, it's compared with a known hash
+ #[test]
+ fn test_hash() {
+ let message = Any2SolanaRampMessage {
+ sender: [
+ 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ ]
+ .to_vec(),
+ receiver: Pubkey::try_from("DS2tt4BX7YwCw7yrDNwbAdnYrxjeCPeGJbHmZEYC8RTb").unwrap(),
+ data: vec![4, 5, 6],
+ header: RampMessageHeader {
+ message_id: [
+ 8, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ ],
+ source_chain_selector: 67,
+ dest_chain_selector: 78,
+ sequence_number: 89,
+ nonce: 90,
+ },
+ token_amounts: [].to_vec(), // TODO: hash token amounts
+ extra_args: SolanaExtraArgs {
+ compute_units: 1000,
+ accounts: vec![SolanaAccountMeta {
+ pubkey: Pubkey::try_from("DS2tt4BX7YwCw7yrDNwbAdnYrxjeCPeGJbHmZEYC8RTb")
+ .unwrap(),
+ is_writable: true,
+ }],
+ },
+ };
+
+ let on_ramp_address = &[1, 2, 3].to_vec();
+ let hash_result = message.hash(on_ramp_address);
+
+ assert_eq!(
+ "03da97f96c82237d8a8ab0f68d4f7ba02afe188b4a876f348278fbf2226312ed",
+ hex::encode(hash_result)
+ );
+ }
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/ocr3base.rs b/chains/solana/contracts/programs/ccip-router/src/ocr3base.rs
new file mode 100644
index 000000000..5c851be20
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/ocr3base.rs
@@ -0,0 +1,312 @@
+use anchor_lang::prelude::*;
+use anchor_lang::solana_program::sysvar;
+use anchor_lang::solana_program::{keccak, secp256k1_recover::*};
+
+#[constant]
+pub const MAX_ORACLES: usize = 16; // can set a maximum of 16 transmitters + 16 signers simultaneously in a single set config tx
+pub const MAX_SIGNERS: usize = MAX_ORACLES;
+pub const MAX_TRANSMITTERS: usize = MAX_ORACLES;
+
+pub const SIGNATURE_LENGTH: usize = SECP256K1_SIGNATURE_LENGTH + 1; // signature + recovery ID
+
+pub const TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT_NO_SIGNATURES: u128 = 8 // anchor discriminator
+ + 3 * 32; // report context
+pub const TRANSMIT_MSGDATA_EXTRA_CONSTANT_LENGTH_COMPONENT_FOR_SIGNATURES: u128 = 4; // u32 length of signatures vec (borsh serialization)
+
+#[zero_copy]
+#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Default)]
+pub struct ReportContext {
+ // byte_words consists of:
+ // [0]: ConfigDigest
+ // [1]: 24 byte padding, 8 byte sequence number
+ // [2]: ExtraHash
+ byte_words: [[u8; 32]; 3], // private, define methods to use it
+}
+
+impl ReportContext {
+ pub fn sequence_number(&self) -> u64 {
+ let sequence_bytes: [u8; 8] = self.byte_words[1][24..].try_into().unwrap();
+ u64::from_be_bytes(sequence_bytes)
+ }
+
+ pub fn from_byte_words(byte_words: [[u8; 32]; 3]) -> Self {
+ Self { byte_words }
+ }
+
+ pub fn as_bytes(&self) -> [u8; 32 * 3] {
+ self.byte_words.concat().as_slice().try_into().unwrap()
+ }
+}
+
+#[zero_copy]
+#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Default)]
+pub struct Ocr3Config {
+ pub plugin_type: u8, // plugin identifier for validation (example: ccip:commit = 0, ccip:execute = 1)
+ pub config_info: Ocr3ConfigInfo,
+ pub signers: [[u8; 20]; 16], // v0.29.0 - anchor IDL does not build with MAX_SIGNERS
+ pub transmitters: [[u8; 32]; 16], // v0.29.0 - anchor IDL does not build with MAX_TRANSMITTERS
+}
+
+pub trait Ocr3Report {
+ fn hash(&self, ctx: &ReportContext) -> [u8; 32];
+ fn len(&self) -> usize;
+}
+
+// TODO: do we need to verify signers and transmitters are different? (between the two groups)
+// signers: pubkey is 20-byte address, secp256k1 curve ECDSA
+// transmitters: 32-byte pubkey, ed25519
+
+#[zero_copy]
+#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Default)]
+pub struct Ocr3ConfigInfo {
+ pub config_digest: [u8; 32], // 32-byte hash of configuration
+ pub f: u8, // f+1 = number of signatures per report
+ pub n: u8, // number of signers
+ pub is_signature_verification_enabled: u8, // bool -> bytemuck::Pod compliant required for zero_copy
+}
+
+impl Ocr3Config {
+ pub fn new(plugin_type: u8) -> Self {
+ Self {
+ plugin_type,
+ ..Default::default()
+ }
+ }
+
+ pub fn set(
+ &mut self,
+ plugin_type: u8,
+ cfg: Ocr3ConfigInfo,
+ signers: Vec<[u8; 20]>, // 20-byte EVM address
+ transmitters: Vec, // 32-byte solana pubkey
+ ) -> Result<()> {
+ require!(
+ plugin_type == self.plugin_type,
+ Ocr3Error::InvalidPluginType
+ );
+ require!(cfg.f != 0, Ocr3Error::InvalidConfigFMustBePositive);
+
+ // If F is 0, then the config is not yet set
+ if self.config_info.f == 0 {
+ self.config_info.is_signature_verification_enabled =
+ cfg.is_signature_verification_enabled;
+ } else {
+ require!(
+ self.config_info.is_signature_verification_enabled
+ == cfg.is_signature_verification_enabled,
+ Ocr3Error::StaticConfigCannotBeChanged
+ )
+ };
+
+ require!(
+ transmitters.len() <= MAX_TRANSMITTERS,
+ Ocr3Error::InvalidConfigTooManyTransmitters
+ );
+
+ self.clear_transmitters();
+
+ if cfg.is_signature_verification_enabled != 0 {
+ self.clear_signers();
+ require!(
+ signers.len() <= MAX_SIGNERS,
+ Ocr3Error::InvalidConfigTooManySigners
+ );
+ require!(
+ signers.len() > 3 * cfg.f as usize,
+ Ocr3Error::InvalidConfigFIsTooHigh
+ );
+
+ self.config_info.n = signers.len() as u8;
+ assign_oracles(&mut signers.clone(), &mut self.signers)?;
+ }
+
+ let transmitter_keys: Vec<[u8; 32]> = transmitters.iter().map(|t| t.to_bytes()).collect();
+ assign_oracles(&mut transmitter_keys.clone(), &mut self.transmitters)?;
+
+ // set remaining config
+ self.config_info.f = cfg.f;
+ self.config_info.config_digest = cfg.config_digest;
+
+ emit!(ConfigSet {
+ ocr_plugin_type: self.plugin_type,
+ config_digest: self.config_info.config_digest,
+ signers,
+ transmitters,
+ f: self.config_info.f,
+ });
+
+ Ok(())
+ }
+
+ fn clear_signers(&mut self) {
+ self.signers = [[0; 20]; MAX_SIGNERS]
+ }
+
+ fn clear_transmitters(&mut self) {
+ self.transmitters = [[0; 32]; MAX_TRANSMITTERS]
+ }
+
+ pub fn transmit(
+ &self,
+ instruction_sysvar: &AccountInfo<'_>,
+ transmitter: Pubkey,
+ plugin_type: u8,
+ report_context: ReportContext,
+ report: &R,
+ signatures: &[[u8; SIGNATURE_LENGTH]],
+ ) -> Result<()> {
+ require!(
+ plugin_type == self.plugin_type,
+ Ocr3Error::InvalidPluginType
+ );
+
+ // validate raw message length
+ let mut expected_data_len: u128 = TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT_NO_SIGNATURES
+ .saturating_add(report.len() as u128);
+ if self.config_info.is_signature_verification_enabled != 0 {
+ expected_data_len = expected_data_len
+ .saturating_add(TRANSMIT_MSGDATA_EXTRA_CONSTANT_LENGTH_COMPONENT_FOR_SIGNATURES);
+ expected_data_len =
+ expected_data_len.saturating_add((signatures.len() * SIGNATURE_LENGTH) as u128);
+ }
+ // validates instruction sysvar is correct address
+ let instruction_index =
+ sysvar::instructions::load_current_index_checked(instruction_sysvar)?;
+ let tx = sysvar::instructions::load_instruction_at_checked(
+ instruction_index as usize,
+ instruction_sysvar,
+ )?;
+
+ require!(
+ tx.data.len() as u128 == expected_data_len,
+ Ocr3Error::WrongMessageLength
+ );
+
+ require!(
+ self.config_info.config_digest == report_context.byte_words[0],
+ Ocr3Error::ConfigDigestMismatch
+ );
+
+ // TODO: chain fork check - is this even possible?
+
+ // validate transmitter
+ self.transmitters
+ .binary_search_by(|probe| transmitter.to_bytes().cmp(probe))
+ .map_err(|_| Ocr3Error::UnauthorizedTransmitter)?;
+
+ if self.config_info.is_signature_verification_enabled != 0 {
+ require!(
+ signatures.len() == (self.config_info.f + 1) as usize,
+ Ocr3Error::WrongNumberOfSignatures
+ );
+
+ self.verify_signatures(report.hash(&report_context), signatures)?;
+ }
+
+ emit!(Transmitted {
+ ocr_plugin_type: self.plugin_type,
+ config_digest: self.config_info.config_digest,
+ sequence_number: report_context.sequence_number(),
+ });
+
+ Ok(())
+ }
+
+ fn verify_signatures(
+ &self,
+ hashed_report: [u8; 32],
+ signatures: &[[u8; SIGNATURE_LENGTH]],
+ ) -> Result<()> {
+ let mut uniques: u16 = 0;
+ assert!(uniques.count_ones() + uniques.count_zeros() >= MAX_SIGNERS as u32); // ensure MAX_SIGNERS fit in the bits of uniques
+
+ for raw_sig in signatures {
+ let (recovery_id, signature) =
+ raw_sig.split_first().ok_or(Ocr3Error::InvalidSignature)?;
+
+ let signer = secp256k1_recover(&hashed_report, *recovery_id, signature)
+ .map_err(|_| Ocr3Error::InvalidSignature)?;
+ // convert to a raw 20 byte Ethereum address
+ let address: [u8; 20] = keccak::hash(&signer.0).to_bytes()[12..32]
+ .try_into()
+ .map_err(|_| Ocr3Error::UnauthorizedSigner)?;
+ let index = self
+ .signers
+ .binary_search_by(|probe| address.cmp(probe))
+ .map_err(|_| Ocr3Error::UnauthorizedSigner)?;
+
+ uniques |= 1 << index;
+ }
+
+ require!(
+ uniques.count_ones() as usize == signatures.len(),
+ Ocr3Error::NonUniqueSignatures
+ );
+
+ Ok(())
+ }
+}
+
+// assign_oracles (generic) takes a vector of byte arrays sorts (for binary searching), validates, and writes it to `location`
+fn assign_oracles(oracles: &mut [[u8; A]], location: &mut [[u8; A]]) -> Result<()> {
+ // sort + verify not duplicated
+ oracles.sort_unstable_by(|a, b| b.cmp(a)); // reverse sort to ensure appended 0-values in correct
+ let duplicate = oracles.windows(2).any(|pair| pair[0] == pair[1]);
+ require!(!duplicate, Ocr3Error::InvalidConfigRepeatedOracle);
+
+ for (i, o) in oracles.iter().enumerate() {
+ require!(*o != [0; A], Ocr3Error::OracleCannotBeZeroAddress);
+ location[i] = *o;
+ }
+ Ok(())
+}
+
+#[error_code]
+pub enum Ocr3Error {
+ #[msg("Invalid config: F must be positive")]
+ InvalidConfigFMustBePositive,
+ #[msg("Invalid config: Too many transmitters")]
+ InvalidConfigTooManyTransmitters,
+ #[msg("Invalid config: Too many signers")]
+ InvalidConfigTooManySigners,
+ #[msg("Invalid config: F is too high")]
+ InvalidConfigFIsTooHigh,
+ #[msg("Invalid config: Repeated oracle address")]
+ InvalidConfigRepeatedOracle,
+ #[msg("Wrong message length")]
+ WrongMessageLength,
+ #[msg("Config digest mismatch")]
+ ConfigDigestMismatch,
+ #[msg("Wrong number signatures")]
+ WrongNumberOfSignatures,
+ #[msg("Unauthorized transmitter")]
+ UnauthorizedTransmitter,
+ #[msg("Unauthorized signer")]
+ UnauthorizedSigner,
+ #[msg("Non unique signatures")]
+ NonUniqueSignatures,
+ #[msg("Oracle cannot be zero address")]
+ OracleCannotBeZeroAddress,
+ #[msg("Static config cannot be changed")]
+ StaticConfigCannotBeChanged,
+ #[msg("Incorrect plugin type")]
+ InvalidPluginType,
+ #[msg("Invalid signature")]
+ InvalidSignature,
+}
+
+#[event]
+pub struct ConfigSet {
+ pub ocr_plugin_type: u8,
+ pub config_digest: [u8; 32],
+ pub signers: Vec<[u8; 20]>,
+ pub transmitters: Vec,
+ pub f: u8,
+}
+
+#[event]
+pub struct Transmitted {
+ pub ocr_plugin_type: u8,
+ pub config_digest: [u8; 32],
+ pub sequence_number: u64,
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/pools.rs b/chains/solana/contracts/programs/ccip-router/src/pools.rs
new file mode 100644
index 000000000..4bc615401
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/pools.rs
@@ -0,0 +1,294 @@
+use anchor_lang::prelude::*;
+use anchor_spl::{
+ associated_token::get_associated_token_address_with_program_id,
+ token_2022::spl_token_2022::{self, instruction::transfer_checked, state::Mint},
+ token_interface::TokenAccount,
+};
+use solana_program::{
+ address_lookup_table::state::AddressLookupTable, instruction::Instruction,
+ program::invoke_signed,
+};
+use solana_program::{program::get_return_data, program_pack::Pack};
+
+use crate::{
+ CcipRouterError, ExternalExecutionConfig, TokenAdminRegistry, CCIP_TOKENPOOL_CONFIG,
+ CCIP_TOKENPOOL_SIGNER, TOKEN_ADMIN_REGISTRY_SEED, TOKEN_POOL_BILLING_SEED,
+ TOKEN_POOL_CONFIG_SEED,
+};
+
+pub const CCIP_POOL_V1_RET_BYTES: usize = 8;
+const MIN_TOKEN_POOL_ACCOUNTS: usize = 11; // see TokenAccounts struct for all required accounts
+
+pub fn calculate_token_pool_account_indices(
+ i: usize,
+ start_indices: &[u8],
+ remaining_accounts_count: usize,
+) -> Result<(usize, usize)> {
+ // account set = [start...end)
+ let start: usize = start_indices[i] as usize;
+ let end: usize = if i == start_indices.len() - 1 {
+ remaining_accounts_count
+ } else {
+ (start_indices[i + 1]) as usize
+ };
+
+ // validate indexes and account lengths
+ // start < end: prevent overflow
+ // end <= MAX, index not exceeded
+ // end - start >= MIN_TOKEN_POOL_ACCOUNTS, ensure there are enough accounts
+ require!(
+ start < end && end <= remaining_accounts_count && end - start >= MIN_TOKEN_POOL_ACCOUNTS,
+ CcipRouterError::InvalidInputsTokenIndices
+ );
+
+ Ok((start, end))
+}
+
+pub struct TokenAccounts<'a> {
+ pub user_token_account: &'a AccountInfo<'a>,
+ // pub token_billing_config: &'a AccountInfo<'a>, // TODO: enable with token billing
+ pub pool_chain_config: &'a AccountInfo<'a>,
+ pub pool_program: &'a AccountInfo<'a>,
+ pub pool_config: &'a AccountInfo<'a>,
+ pub pool_token_account: &'a AccountInfo<'a>,
+ pub pool_signer: &'a AccountInfo<'a>,
+ pub token_program: &'a AccountInfo<'a>,
+ pub mint: &'a AccountInfo<'a>,
+ pub remaining_accounts: &'a [AccountInfo<'a>],
+}
+
+pub fn parse_token_accounts<'info>(
+ user: Pubkey,
+ chain_selector: u64,
+ router: Pubkey,
+ accounts: &'info [AccountInfo<'info>],
+) -> Result {
+ // accounts based on user or chain
+ let (user_token_account, remaining_accounts) = accounts.split_first().unwrap();
+ let (token_billing_config, remaining_accounts) = remaining_accounts.split_first().unwrap();
+ let (pool_chain_config, remaining_accounts) = remaining_accounts.split_first().unwrap();
+
+ // constant accounts for any pool interaction
+ let (lookup_table, remaining_accounts) = remaining_accounts.split_first().unwrap();
+ let (token_admin_registry, remaining_accounts) = remaining_accounts.split_first().unwrap();
+ let (pool_program, remaining_accounts) = remaining_accounts.split_first().unwrap();
+ let (pool_config, remaining_accounts) = remaining_accounts.split_first().unwrap();
+ let (pool_token_account, remaining_accounts) = remaining_accounts.split_first().unwrap();
+ let (pool_signer, remaining_accounts) = remaining_accounts.split_first().unwrap();
+ let (token_program, remaining_accounts) = remaining_accounts.split_first().unwrap();
+ let (mint, remaining_accounts) = remaining_accounts.split_first().unwrap();
+
+ // Account validations (using remaining_accounts does not facilitate built-in anchor checks)
+ {
+ // Check Token Admin Registry
+ let (expected_token_admin_registry, _) = Pubkey::find_program_address(
+ &[TOKEN_ADMIN_REGISTRY_SEED, mint.key().as_ref()],
+ &router,
+ );
+ require!(
+ token_admin_registry.key() == expected_token_admin_registry,
+ CcipRouterError::InvalidInputsTokenAdminRegistryAccounts
+ );
+
+ // check pool program + pool config + pool signer
+ let (expected_pool_config, _) = Pubkey::find_program_address(
+ &[CCIP_TOKENPOOL_CONFIG, mint.key().as_ref()],
+ &pool_program.key(),
+ );
+ let (expected_pool_signer, _) = Pubkey::find_program_address(
+ &[CCIP_TOKENPOOL_SIGNER, mint.key().as_ref()],
+ &pool_program.key(),
+ );
+ require!(
+ *pool_config.owner == pool_program.key()
+ && pool_config.key() == expected_pool_config
+ && pool_signer.key() == expected_pool_signer,
+ CcipRouterError::InvalidInputsPoolAccounts
+ );
+
+ // check token accounts
+ require!(
+ *mint.owner == token_program.key(),
+ CcipRouterError::InvalidInputsTokenAccounts
+ );
+ require!(
+ user_token_account.key()
+ == get_associated_token_address_with_program_id(
+ &user,
+ &mint.key(),
+ &token_program.key()
+ )
+ && pool_token_account.key()
+ == get_associated_token_address_with_program_id(
+ &pool_signer.key(),
+ &mint.key(),
+ &token_program.key()
+ ),
+ CcipRouterError::InvalidInputsTokenAccounts
+ );
+
+ // check per token per chain configs
+ // billing: configured via CCIP router/fee quoter
+ // chain config: configured via pool
+ let (expected_billing_config, _) = Pubkey::find_program_address(
+ &[
+ TOKEN_POOL_BILLING_SEED,
+ chain_selector.to_le_bytes().as_ref(),
+ mint.key().as_ref(),
+ ],
+ &router,
+ );
+ let (expected_pool_chain_config, _) = Pubkey::find_program_address(
+ &[
+ TOKEN_POOL_CONFIG_SEED,
+ chain_selector.to_le_bytes().as_ref(),
+ mint.key().as_ref(),
+ ],
+ &pool_program.key(),
+ );
+ require!(
+ token_billing_config.key() == expected_billing_config, // TODO: determine if this can be zero key for optional billing config?
+ CcipRouterError::InvalidInputsConfigAccounts
+ );
+ require!(
+ pool_chain_config.key() == expected_pool_chain_config,
+ CcipRouterError::InvalidInputsConfigAccounts
+ );
+
+ // Check Lookup Table Address
+ let token_admin_registry_account: Account =
+ Account::try_from(token_admin_registry)?;
+ require!(
+ token_admin_registry_account.lookup_table == lookup_table.key(),
+ CcipRouterError::InvalidInputsLookupTableAccounts
+ );
+
+ // Check Lookup Table Entries
+ let lookup_table_data = &mut &lookup_table.data.borrow()[..];
+ let lookup_table_account: AddressLookupTable =
+ AddressLookupTable::deserialize(lookup_table_data)
+ .map_err(|_| CcipRouterError::InvalidInputsLookupTableAccounts)?;
+
+ // reconstruct + validate expected values in token pool lookup table
+ // base set of constant accounts (8)
+ // + additional constant accounts (remaining_accounts) that are not required but may be used for additional token pool functionality (like CPI)
+ let mut expected_entries = vec![
+ lookup_table.key(),
+ token_admin_registry.key(),
+ pool_program.key(),
+ pool_config.key(),
+ pool_token_account.key(),
+ pool_signer.key(),
+ token_program.key(),
+ mint.key(),
+ ];
+ let mut remaining_keys: Vec = remaining_accounts.iter().map(|x| x.key()).collect();
+ expected_entries.append(&mut remaining_keys);
+ require!(
+ lookup_table_account.addresses.len() == expected_entries.len(),
+ CcipRouterError::InvalidInputsLookupTableAccounts
+ );
+ require!(
+ lookup_table_account.addresses.as_ref() == expected_entries,
+ CcipRouterError::InvalidInputsLookupTableAccounts
+ );
+ }
+
+ Ok(TokenAccounts {
+ user_token_account,
+ // token_billing_config, // TODO: enable with token billing
+ pool_chain_config,
+ pool_program,
+ pool_config,
+ pool_token_account,
+ pool_signer,
+ token_program,
+ mint,
+ remaining_accounts,
+ })
+}
+
+pub fn transfer_token<'info>(
+ amount: u64,
+ token_program: &AccountInfo,
+ mint: &AccountInfo<'info>,
+ from: &AccountInfo<'info>,
+ to: &AccountInfo<'info>,
+ signer: &Account<'info, ExternalExecutionConfig>,
+ seeds: &[&[u8]],
+) -> std::result::Result<(), ProgramError> {
+ let mint_data = Mint::unpack(*mint.try_borrow_data()?)?;
+ let mut transfer_ix = transfer_checked(
+ &spl_token_2022::ID, // SDK requires spl-token or spl-token-2022 (cannot handle arbitrary token program)
+ &from.key(),
+ &mint.key(),
+ &to.key(),
+ &signer.key(),
+ &[],
+ amount,
+ mint_data.decimals, // parse decimals from token account
+ )?;
+ transfer_ix.program_id = token_program.key(); // set token program in case custom
+ invoke_signed(
+ &transfer_ix,
+ &[
+ from.to_account_info(),
+ mint.to_account_info(),
+ to.to_account_info(),
+ signer.to_account_info(),
+ ],
+ &[seeds],
+ )
+}
+
+// ToTxData implements an interface that returns instruction data
+pub trait ToTxData {
+ fn to_tx_data(&self) -> Vec;
+}
+
+pub fn interact_with_pool(
+ pool_program: Pubkey,
+ signer: Pubkey,
+ acc_infos: Vec,
+ data: D,
+ seeds: &[&[u8]],
+) -> std::result::Result, ProgramError> {
+ let acc_metas: Vec = acc_infos
+ .to_vec()
+ .iter()
+ .flat_map(|acc_info| {
+ // Check signer from PDA External Execution config
+ let is_signer = acc_info.key() == signer;
+ acc_info.to_account_metas(Some(is_signer))
+ })
+ .collect();
+
+ let ix = Instruction {
+ program_id: pool_program,
+ accounts: acc_metas,
+ data: data.to_tx_data(),
+ };
+
+ // CPI call to pool is expected to return data using set_return_data
+ // anchor does this automatically but is limited to max 256 bytes
+ // https://github.com/coral-xyz/anchor/blob/0109f4a3cf4117570f627e3ae465b6247d504f69/lang/syn/src/codegen/program/handlers.rs#L113
+ invoke_signed(&ix, &acc_infos, &[seeds])?;
+
+ // parse return data
+ // https://github.com/coral-xyz/anchor/blob/0109f4a3cf4117570f627e3ae465b6247d504f69/lang/syn/src/codegen/program/cpi.rs#L83
+ let (_, data) = get_return_data().unwrap();
+ Ok(data)
+}
+
+pub fn get_balance<'a>(token_account: &'a AccountInfo<'a>) -> Result {
+ let mut acc: InterfaceAccount = InterfaceAccount::try_from(token_account)?;
+ acc.reload()?; // reload state to ensure latest balance
+ Ok(acc.amount)
+}
+
+// pack u64 into LE u256 for cross-chain amount
+pub fn u64_to_le_u256(v: u64) -> [u8; 32] {
+ let mut out: [u8; 32] = [0; 32];
+ out[..8].copy_from_slice(v.to_le_bytes().as_slice());
+ out
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/state.rs b/chains/solana/contracts/programs/ccip-router/src/state.rs
new file mode 100644
index 000000000..6b8c1a73b
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/state.rs
@@ -0,0 +1,331 @@
+use anchor_lang::prelude::*;
+
+use crate::ocr3base::Ocr3Config;
+
+// zero_copy is used to prevent hitting stack/heap memory limits
+#[account(zero_copy)]
+#[derive(InitSpace, AnchorSerialize, AnchorDeserialize)]
+pub struct Config {
+ pub version: u8,
+ pub default_allow_out_of_order_execution: u8, // bytemuck::Pod compliant required for zero_copy
+ _padding0: [u8; 6],
+ pub solana_chain_selector: u64,
+ pub default_gas_limit: u128,
+ _padding1: [u8; 8],
+
+ pub owner: Pubkey,
+ pub proposed_owner: Pubkey,
+
+ pub enable_manual_execution_after: i64, // Expressed as Unix time (i.e. seconds since the Unix epoch).
+ _padding2: [u8; 8],
+
+ pub ocr3: [Ocr3Config; 2],
+
+ // TODO: token pool global config
+
+ // TODO: billing global configs'
+ _padding_before_billing: [u8; 8],
+ pub latest_price_sequence_number: u64,
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, InitSpace, Debug)]
+pub struct SourceChainConfig {
+ pub is_enabled: bool, // Flag whether the source chain is enabled or not
+ #[max_len(64)]
+ pub on_ramp: Vec, // OnRamp address on the source chain
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, InitSpace, Debug)]
+pub struct SourceChainState {
+ pub min_seq_nr: u64, // The min sequence number expected for future messages
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, InitSpace, Debug)]
+pub struct SourceChain {
+ pub state: SourceChainState, // values that are updated automatically
+ pub config: SourceChainConfig, // values configured by an admin
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, InitSpace, Debug)]
+pub struct DestChainState {
+ pub sequence_number: u64, // The last used sequence number
+ pub usd_per_unit_gas: TimestampedPackedU224,
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, InitSpace, Debug)]
+pub struct DestChainConfig {
+ pub is_enabled: bool, // Whether this destination chain is enabled
+
+ pub max_number_of_tokens_per_msg: u16, // Maximum number of distinct ERC20 token transferred per message
+ pub max_data_bytes: u32, // Maximum payload data size in bytes
+ pub max_per_msg_gas_limit: u32, // Maximum gas limit for messages targeting EVMs
+ pub dest_gas_overhead: u32, // Gas charged on top of the gasLimit to cover destination chain costs
+ pub dest_gas_per_payload_byte: u16, // Destination chain gas charged for passing each byte of `data` payload to receiver
+ pub dest_data_availability_overhead_gas: u32, // Extra data availability gas charged on top of the message, e.g. for OCR
+ pub dest_gas_per_data_availability_byte: u16, // Amount of gas to charge per byte of message data that needs availability
+ pub dest_data_availability_multiplier_bps: u16, // Multiplier for data availability gas, multiples of bps, or 0.0001
+
+ // The following three properties are defaults, they can be overridden by setting the TokenTransferFeeConfig for a token
+ pub default_token_fee_usdcents: u16, // Default token fee charged per token transfer
+ pub default_token_dest_gas_overhead: u32, // Default gas charged to execute the token transfer on the destination chain
+ pub default_tx_gas_limit: u32, // Default gas limit for a tx
+ pub gas_multiplier_wei_per_eth: u64, // Multiplier for gas costs, 1e18 based so 11e17 = 10% extra cost.
+ pub network_fee_usdcents: u32, // Flat network fee to charge for messages, multiples of 0.01 USD
+ pub gas_price_staleness_threshold: u32, // The amount of time a gas price can be stale before it is considered invalid (0 means disabled)
+ pub enforce_out_of_order: bool, // Whether to enforce the allowOutOfOrderExecution extraArg value to be true.
+ pub chain_family_selector: [u8; 4], // Selector that identifies the destination chain's family. Used to determine the correct validations to perform for the dest chain.
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, InitSpace, Debug)]
+pub struct DestChain {
+ pub state: DestChainState, // values that are updated automatically
+ pub config: DestChainConfig, // values configured by an admin
+}
+
+#[account]
+#[derive(InitSpace, Debug)]
+pub struct ChainState {
+ pub version: u8,
+ pub source_chain: SourceChain, // Config for Any2Solana
+ pub dest_chain: DestChain, // Config for Solana2Any
+}
+
+#[account]
+#[derive(InitSpace)]
+pub struct Nonce {
+ pub version: u8, // version to check if nonce account is already initialized
+ pub counter: u64, // Counter per user and per lane to use as nonce for all the messages to be executed in order
+}
+
+#[account]
+#[derive(InitSpace)]
+pub struct ExternalExecutionConfig {}
+
+#[account]
+#[derive(InitSpace)]
+pub struct CommitReport {
+ pub version: u8,
+ pub timestamp: i64, // Expressed as Unix time (i.e. seconds since the Unix epoch).
+ pub min_msg_nr: u64,
+ pub max_msg_nr: u64, // TODO: Change this to [u128; 2] when supporting commit reports with 256 messages
+ pub execution_states: u128,
+}
+
+impl CommitReport {
+ pub fn set_state(&mut self, sequence_number: u64, execution_state: MessageExecutionState) {
+ let packed = &mut self.execution_states;
+ let dif = sequence_number.checked_sub(self.min_msg_nr);
+ assert!(dif.is_some(), "Sequence number out of bounds");
+ let i = dif.unwrap();
+ assert!(i < 64, "Sequence number out of bounds");
+
+ // Clear the 2 bits at position 'i'
+ *packed &= !(0b11 << (i * 2));
+ // Set the new value in the cleared bits
+ *packed |= (execution_state as u128) << (i * 2);
+ }
+
+ pub fn get_state(&self, sequence_number: u64) -> MessageExecutionState {
+ let packed = self.execution_states;
+ let dif = sequence_number.checked_sub(self.min_msg_nr);
+ assert!(dif.is_some(), "Sequence number out of bounds");
+ let i = dif.unwrap();
+ assert!(i < 64, "Sequence number out of bounds");
+
+ let mask = 0b11 << (i * 2);
+ let state = (packed & mask) >> (i * 2);
+ MessageExecutionState::try_from(state).unwrap()
+ }
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize, Debug, PartialEq)]
+pub enum MessageExecutionState {
+ Untouched = 0,
+ InProgress = 1, // Not used in Solana, but used in EVM
+ Success = 2,
+ Failure = 3,
+}
+
+impl TryFrom for MessageExecutionState {
+ type Error = &'static str;
+
+ fn try_from(value: u128) -> std::result::Result {
+ match value {
+ 0 => Ok(MessageExecutionState::Untouched),
+ 1 => Ok(MessageExecutionState::InProgress),
+ 2 => Ok(MessageExecutionState::Success),
+ 3 => Ok(MessageExecutionState::Failure),
+ _ => Err("Invalid ExecutionState"),
+ }
+ }
+}
+
+#[account]
+#[derive(InitSpace)]
+pub struct PerChainPerTokenConfig {
+ pub version: u8, // schema version
+ pub chain_selector: u64, // remote chain
+ pub mint: Pubkey, // token on solana
+
+ pub billing: TokenBilling, // EVM: configurable in router only by ccip admins
+}
+
+#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct TokenBilling {
+ pub min_fee_usdcents: u32, // Minimum fee to charge per token transfer, multiples of 0.01 USD
+ pub max_fee_usdcents: u32, // Maximum fee to charge per token transfer, multiples of 0.01 USD
+ pub deci_bps: u16, // Basis points charged on token transfers, multiples of 0.1bps, or 1e-5
+ pub dest_gas_overhead: u32, // Gas charged to execute the token transfer on the destination chain
+ // Extra data availability bytes that are returned from the source pool and sent
+ pub dest_bytes_overhead: u32, // to the destination pool. Must be >= Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES
+ pub is_enabled: bool, // Whether this token has custom transfer fees
+}
+
+#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct RateLimitTokenBucket {
+ pub tokens: u128, // Current number of tokens that are in the bucket.
+ pub last_updated: u32, // Timestamp in seconds of the last token refill, good for 100+ years.
+ pub is_enabled: bool, // Indication whether the rate limiting is enabled or not
+ pub capacity: u128, // Maximum number of tokens that can be in the bucket.
+ pub rate: u128, // Number of tokens per second that the bucket is refilled.
+}
+
+// WIP
+#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize, Debug)]
+pub struct BillingTokenConfig {
+ // NOTE: when modifying this struct, make sure to update the version in the wrapper
+ pub enabled: bool,
+ pub mint: Pubkey,
+
+ // price tracking
+ pub usd_per_token: TimestampedPackedU224,
+ // billing configs
+ pub premium_multiplier_wei_per_eth: u64,
+}
+
+#[account]
+#[derive(InitSpace, Debug)]
+pub struct BillingTokenConfigWrapper {
+ pub version: u8,
+ pub config: BillingTokenConfig,
+}
+
+#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize, Debug)]
+pub struct TimestampedPackedU224 {
+ pub value: [u8; 28],
+ pub timestamp: i64, // maintaining the type that Solana returns for the time (solana_program::clock::UnixTimestamp = i64)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::convert::TryFrom;
+
+ #[test]
+ fn test_set_state() {
+ let mut commit_report = CommitReport {
+ version: 1,
+ timestamp: 0,
+ min_msg_nr: 0,
+ max_msg_nr: 64,
+ execution_states: 0,
+ };
+
+ commit_report.set_state(0, MessageExecutionState::Success);
+ assert_eq!(commit_report.get_state(0), MessageExecutionState::Success);
+
+ commit_report.set_state(1, MessageExecutionState::Failure);
+ assert_eq!(commit_report.get_state(1), MessageExecutionState::Failure);
+
+ commit_report.set_state(2, MessageExecutionState::Untouched);
+ assert_eq!(commit_report.get_state(2), MessageExecutionState::Untouched);
+
+ commit_report.set_state(3, MessageExecutionState::InProgress);
+ assert_eq!(
+ commit_report.get_state(3),
+ MessageExecutionState::InProgress
+ );
+ }
+
+ #[test]
+ #[should_panic(expected = "Sequence number out of bounds")]
+ fn test_set_state_out_of_bounds() {
+ let mut commit_report = CommitReport {
+ version: 1,
+ timestamp: 1,
+ min_msg_nr: 1500,
+ max_msg_nr: 1530,
+ execution_states: 0,
+ };
+
+ commit_report.set_state(65, MessageExecutionState::Success);
+ }
+
+ #[test]
+ fn test_get_state() {
+ let mut commit_report = CommitReport {
+ version: 1,
+ timestamp: 1,
+ min_msg_nr: 1500,
+ max_msg_nr: 1530,
+ execution_states: 0,
+ };
+
+ commit_report.set_state(1501, MessageExecutionState::Success);
+ commit_report.set_state(1505, MessageExecutionState::Failure);
+ commit_report.set_state(1520, MessageExecutionState::Untouched);
+ commit_report.set_state(1523, MessageExecutionState::InProgress);
+
+ assert_eq!(
+ commit_report.get_state(1501),
+ MessageExecutionState::Success
+ );
+ assert_eq!(
+ commit_report.get_state(1505),
+ MessageExecutionState::Failure
+ );
+ assert_eq!(
+ commit_report.get_state(1520),
+ MessageExecutionState::Untouched
+ );
+ assert_eq!(
+ commit_report.get_state(1523),
+ MessageExecutionState::InProgress
+ );
+ }
+
+ #[test]
+ #[should_panic(expected = "Sequence number out of bounds")]
+ fn test_get_state_out_of_bounds() {
+ let commit_report = CommitReport {
+ version: 1,
+ timestamp: 1,
+ min_msg_nr: 1500,
+ max_msg_nr: 1530,
+ execution_states: 0,
+ };
+
+ commit_report.get_state(65);
+ }
+
+ #[test]
+ fn test_execution_state_try_from() {
+ assert_eq!(
+ MessageExecutionState::try_from(0).unwrap(),
+ MessageExecutionState::Untouched
+ );
+ assert_eq!(
+ MessageExecutionState::try_from(1).unwrap(),
+ MessageExecutionState::InProgress
+ );
+ assert_eq!(
+ MessageExecutionState::try_from(2).unwrap(),
+ MessageExecutionState::Success
+ );
+ assert_eq!(
+ MessageExecutionState::try_from(3).unwrap(),
+ MessageExecutionState::Failure
+ );
+ assert!(MessageExecutionState::try_from(4).is_err());
+ }
+}
diff --git a/chains/solana/contracts/programs/ccip-router/src/token_context.rs b/chains/solana/contracts/programs/ccip-router/src/token_context.rs
new file mode 100644
index 000000000..876afa997
--- /dev/null
+++ b/chains/solana/contracts/programs/ccip-router/src/token_context.rs
@@ -0,0 +1,114 @@
+use anchor_lang::prelude::*;
+
+use crate::context::*;
+use crate::state::*;
+use crate::CcipRouterError;
+
+use anchor_spl::token_interface::Mint;
+
+#[account]
+#[derive(InitSpace)]
+pub struct TokenAdminRegistry {
+ pub version: u8,
+ pub administrator: Pubkey,
+ pub pending_administrator: Pubkey,
+ pub lookup_table: Pubkey,
+}
+
+#[derive(Accounts)]
+#[instruction(mint: Pubkey)]
+pub struct RegisterTokenAdminRegistryViaGetCCIPAdmin<'info> {
+ #[account(
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_TOKEN_REGISTRY_V) @ CcipRouterError::InvalidInputs,
+ )]
+ pub config: AccountLoader<'info, Config>,
+ #[account(
+ init,
+ seeds = [TOKEN_ADMIN_REGISTRY_SEED, mint.as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + TokenAdminRegistry::INIT_SPACE,
+ constraint = uninitialized(token_admin_registry.version) @ CcipRouterError::InvalidInputs,
+ )]
+ pub token_admin_registry: Account<'info, TokenAdminRegistry>,
+ #[account(mut, address = config.load()?.owner @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct RegisterTokenAdminRegistryViaOwner<'info> {
+ #[account(
+ init,
+ seeds = [TOKEN_ADMIN_REGISTRY_SEED, mint.key().as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + TokenAdminRegistry::INIT_SPACE,
+ constraint = uninitialized(token_admin_registry.version) @ CcipRouterError::InvalidInputs,
+ )]
+ pub token_admin_registry: Account<'info, TokenAdminRegistry>,
+ #[account(mut)]
+ pub mint: InterfaceAccount<'info, Mint>, // underlying token that the pool wraps
+ #[account(
+ mut,
+ address = mint.mint_authority.unwrap() @ CcipRouterError::Unauthorized,
+ )]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(mint: Pubkey)]
+pub struct ModifyTokenAdminRegistry<'info> {
+ #[account(
+ mut,
+ seeds = [TOKEN_ADMIN_REGISTRY_SEED, mint.as_ref()],
+ bump,
+ constraint = valid_version(token_admin_registry.version, MAX_TOKEN_REGISTRY_V) @ CcipRouterError::InvalidInputs,
+ )]
+ pub token_admin_registry: Account<'info, TokenAdminRegistry>,
+ #[account(mut, address = token_admin_registry.administrator @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(mint: Pubkey)]
+pub struct AcceptAdminRoleTokenAdminRegistry<'info> {
+ #[account(
+ mut,
+ seeds = [TOKEN_ADMIN_REGISTRY_SEED, mint.as_ref()],
+ bump,
+ constraint = valid_version(token_admin_registry.version, MAX_TOKEN_REGISTRY_V) @ CcipRouterError::InvalidInputs,
+ )]
+ pub token_admin_registry: Account<'info, TokenAdminRegistry>,
+ #[account(mut, address = token_admin_registry.pending_administrator @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(chain_selector: u64, mint: Pubkey)]
+pub struct SetTokenBillingConfig<'info> {
+ #[account(
+ seeds = [CONFIG_SEED],
+ bump,
+ constraint = valid_version(config.load()?.version, MAX_CONFIG_V) @ CcipRouterError::InvalidInputs, // validate state version
+ )]
+ pub config: AccountLoader<'info, Config>,
+
+ #[account(
+ init_if_needed,
+ seeds = [TOKEN_POOL_BILLING_SEED, chain_selector.to_le_bytes().as_ref(), mint.as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + PerChainPerTokenConfig::INIT_SPACE,
+ constraint = uninitialized(per_chain_per_token_config.version) || valid_version(per_chain_per_token_config.version, MAX_TOKEN_AND_CHAIN_CONFIG_V) @ CcipRouterError::InvalidInputs,
+ )]
+ pub per_chain_per_token_config: Account<'info, PerChainPerTokenConfig>,
+
+ // validate signer is registered ccip admin
+ #[account(mut, address = config.load()?.owner @ CcipRouterError::Unauthorized)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+}
diff --git a/chains/solana/contracts/programs/external-program-cpi-stub/Cargo.toml b/chains/solana/contracts/programs/external-program-cpi-stub/Cargo.toml
new file mode 100644
index 000000000..a451f11b5
--- /dev/null
+++ b/chains/solana/contracts/programs/external-program-cpi-stub/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "external-program-cpi-stub"
+version = "0.0.0-dev"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "external_program_cpi_stub"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.29.0"
diff --git a/chains/solana/contracts/programs/external-program-cpi-stub/Xargo.toml b/chains/solana/contracts/programs/external-program-cpi-stub/Xargo.toml
new file mode 100644
index 000000000..475fb71ed
--- /dev/null
+++ b/chains/solana/contracts/programs/external-program-cpi-stub/Xargo.toml
@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []
diff --git a/chains/solana/contracts/programs/external-program-cpi-stub/src/lib.rs b/chains/solana/contracts/programs/external-program-cpi-stub/src/lib.rs
new file mode 100644
index 000000000..94be97f01
--- /dev/null
+++ b/chains/solana/contracts/programs/external-program-cpi-stub/src/lib.rs
@@ -0,0 +1,101 @@
+/**
+ * This program is meant to only be used in integration tests on localnet.
+ * Used to test CPIs made by other programs (with actual business logic).
+ */
+use anchor_lang::prelude::*;
+
+declare_id!("4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ");
+
+#[program]
+pub mod external_program_cpi_stub {
+ use super::*;
+
+ pub fn initialize(ctx: Context) -> Result<()> {
+ msg!("Called `initialize` {:?}", ctx);
+ ctx.accounts.u8_value.value = 1;
+ Ok(())
+ }
+
+ pub fn empty(ctx: Context) -> Result<()> {
+ msg!("Called `empty` {:?}", ctx);
+ Ok(())
+ }
+
+ pub fn u8_instruction_data(ctx: Context, data: u8) -> Result<()> {
+ msg!("Called `u8_instruction_data` {:?} and data {data}", ctx);
+ Ok(())
+ }
+
+ pub fn struct_instruction_data(ctx: Context, data: Value) -> Result<()> {
+ msg!(
+ "Called `struct_instruction_data` {:?} and data {:?}",
+ ctx,
+ data
+ );
+ Ok(())
+ }
+
+ pub fn account_read(ctx: Context) -> Result<()> {
+ msg!("Called `account_read` {:?}", ctx);
+ Ok(())
+ }
+
+ pub fn account_mut(ctx: Context) -> Result<()> {
+ msg!("Called `account_mut` {:?}", ctx);
+ let u8_value = &mut ctx.accounts.u8_value;
+ u8_value.value += 1;
+ Ok(())
+ }
+}
+
+const VALUE_SEED: &[u8] = b"u8_value";
+const ANCHOR_DISCRIMINATOR: usize = 8;
+
+#[derive(Accounts, Debug)]
+pub struct Empty {}
+
+#[account]
+#[derive(InitSpace, Debug)]
+pub struct Value {
+ pub value: u8,
+}
+
+#[derive(Accounts, Debug)]
+pub struct Initialize<'info> {
+ #[account(
+ init,
+ seeds = [VALUE_SEED],
+ bump,
+ payer = stub_caller,
+ space = ANCHOR_DISCRIMINATOR + Value::INIT_SPACE,
+ )]
+ pub u8_value: Account<'info, Value>,
+
+ #[account(mut)]
+ pub stub_caller: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts, Debug)]
+pub struct AccountRead<'info> {
+ #[account(
+ seeds = [VALUE_SEED],
+ bump,
+ )]
+ pub u8_value: Account<'info, Value>,
+}
+
+#[derive(Accounts, Debug)]
+pub struct AccountMut<'info> {
+ #[account(
+ mut,
+ seeds = [VALUE_SEED],
+ bump,
+ )]
+ pub u8_value: Account<'info, Value>,
+
+ pub stub_caller: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+}
diff --git a/chains/solana/contracts/programs/mcm/Cargo.toml b/chains/solana/contracts/programs/mcm/Cargo.toml
new file mode 100644
index 000000000..b88e064ce
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "mcm"
+version = "0.1.0-dev"
+description = "Solana implementation of ManyChainMultiSig"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "mcm"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.29.0"
+
+[dev-dependencies]
+hex = "0.4.3"
diff --git a/chains/solana/contracts/programs/mcm/Xargo.toml b/chains/solana/contracts/programs/mcm/Xargo.toml
new file mode 100644
index 000000000..475fb71ed
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/Xargo.toml
@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []
diff --git a/chains/solana/contracts/programs/mcm/src/constant.rs b/chains/solana/contracts/programs/mcm/src/constant.rs
new file mode 100644
index 000000000..e4145047d
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/constant.rs
@@ -0,0 +1,24 @@
+// Business-logic constants
+pub const MAX_NUM_SIGNERS: usize = 200; // This has to be below u8 limit (255). Value copied from EVM reference contract
+pub const NUM_GROUPS: usize = 32; // Value copied from EVM reference contract
+
+// fixed size msig name for distinguishing different multisig instances
+pub const MULTISIG_NAME_PADDED: usize = 32;
+
+// PDA seeds
+// Note: These seeds are not full seed, for unique seeds, multisig_name should be appended
+pub const SIGNER_SEED: &[u8] = b"multisig_signer"; // seed for dataless pda signing CPI
+pub const CONFIG_SEED: &[u8] = b"multisig_config";
+pub const CONFIG_SIGNERS_SEED: &[u8] = b"multisig_config_signers";
+pub const ROOT_METADATA_SEED: &[u8] = b"root_metadata";
+pub const ROOT_SIGNATURES_SEED: &[u8] = b"root_signatures";
+
+pub const EXPIRING_ROOT_AND_OP_COUNT_SEED: &[u8] = b"expiring_root_and_op_count";
+pub const SEEN_SIGNED_HASHES_SEED: &[u8] = b"seen_signed_hashes";
+
+// Fixed sizes in bytes
+pub const ANCHOR_DISCRIMINATOR: usize = 8;
+pub const U8: usize = 1;
+pub const U64: usize = 8;
+pub const VEC_PREFIX: usize = 4;
+pub const STRING_PREFIX: usize = 4;
diff --git a/chains/solana/contracts/programs/mcm/src/error.rs b/chains/solana/contracts/programs/mcm/src/error.rs
new file mode 100644
index 000000000..e951b54db
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/error.rs
@@ -0,0 +1,121 @@
+use anchor_lang::error_code;
+
+// error range
+// note: custom numeric error codes start from 6000 unless specified like #[error_code(offset = 1000)]
+// https://github.com/coral-xyz/anchor/blob/c25bd7b7ebbcaf12f6b8cbd3e6f34ae4e2833cb2/lang/syn/src/codegen/error.rs#L72
+// Anchor built-in errors: https://anchor.so/errors
+//
+// [0:100] Global errors
+// [100:N] Function errors
+// todo: anchor-go error generation support update?
+
+// todo: align message with EVM
+
+#[error_code]
+pub enum McmError {
+ #[msg("Invalid multisig")]
+ WrongMultiSig = 0, // 6000
+
+ #[msg("Invalid chainID")]
+ WrongChainId,
+
+ #[msg("The signer is unauthorized")]
+ Unauthorized,
+
+ #[msg("Invalid inputs")]
+ InvalidInputs,
+
+ #[msg("overflow occurred.")]
+ Overflow,
+
+ #[msg("Invalid signature")]
+ InvalidSignature,
+
+ #[msg("Failed ECDSA recover")]
+ FailedEcdsaRecover,
+
+ #[msg("Invalid root length")]
+ InvalidRootLen,
+
+ #[msg("Config signers not finalized")]
+ SignersNotFinalized,
+
+ #[msg("Config signers already finalized")]
+ SignersAlreadyFinalized,
+
+ #[msg("Signatures already finalized")]
+ SignaturesAlreadyFinalized,
+
+ #[msg("Uploaded signatures count mismatch")]
+ SignatureCountMismatch,
+
+ #[msg("Too many signatures")]
+ TooManySignatures,
+
+ #[msg("Signatures not finalized")]
+ SignaturesNotFinalized,
+
+ #[msg("Signatures root mismatch")]
+ SignaturesRootMismatch,
+
+ #[msg("Signatures valid until mismatch")]
+ SignaturesValidUntilMismatch,
+
+ #[msg("The input vectors for signer addresses and signer groups must have the same length")]
+ MismatchedInputSignerVectorsLength = 200,
+
+ #[msg("The number of signers is 0 or greater than MAX_NUM_SIGNERS")]
+ OutOfBoundsNumOfSigners,
+
+ #[msg("The input arrays for group parents and group quorums must be of length NUM_GROUPS")]
+ MismatchedInputGroupArraysLength,
+
+ #[msg("the group tree isn't well-formed.")]
+ GroupTreeNotWellFormed,
+
+ #[msg("a disabled group contains a signer.")]
+ SignerInDisabledGroup,
+
+ #[msg("the quorum of some group is larger than the number of signers in it.")]
+ OutOfBoundsGroupQuorum,
+
+ // Prevents signers from including more than one signature
+ #[msg("the signers' addresses are not a strictly increasing monotone sequence.")]
+ SignersAddressesMustBeStrictlyIncreasing,
+
+ #[msg("The combination of signature and valid_until has already been seen")]
+ SignedHashAlreadySeen,
+
+ #[msg("Invalid signer")]
+ InvalidSigner,
+
+ #[msg("Missing configuration")]
+ MissingConfig,
+
+ #[msg("Insufficient signers")]
+ InsufficientSigners,
+
+ #[msg("Valid until has already passed")]
+ ValidUntilHasAlreadyPassed,
+
+ #[msg("Proof cannot be verified")]
+ ProofCannotBeVerified,
+
+ #[msg("Pending operations")]
+ PendingOps,
+
+ #[msg("Wrong pre-operation count")]
+ WrongPreOpCount,
+
+ #[msg("Wrong post-operation count")]
+ WrongPostOpCount,
+
+ #[msg("Post-operation count reached")]
+ PostOpCountReached,
+
+ #[msg("Root expired")]
+ RootExpired,
+
+ #[msg("Wrong nonce")]
+ WrongNonce,
+}
diff --git a/chains/solana/contracts/programs/mcm/src/eth_utils.rs b/chains/solana/contracts/programs/mcm/src/eth_utils.rs
new file mode 100644
index 000000000..a6804080a
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/eth_utils.rs
@@ -0,0 +1,327 @@
+use anchor_lang::prelude::*;
+use anchor_lang::solana_program::keccak::{hash, hashv, Hash, HASH_BYTES}; // use keccak256 for EVM compatibility
+use anchor_lang::solana_program::secp256k1_recover::{
+ secp256k1_recover, Secp256k1Pubkey, Secp256k1RecoverError,
+};
+
+use crate::{error::*, RootMetadataInput};
+
+pub const EVM_ADDRESS_BYTES: usize = 20;
+const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA: &[u8; HASH_BYTES] = &[
+ // result of keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA")
+ 0xe6, 0xb8, 0x2b, 0xe9, 0x89, 0x10, 0x1b, 0x4e, 0xb5, 0x19, 0x77, 0x01, 0x14, 0xb9, 0x97, 0xb9,
+ 0x7b, 0x3c, 0x87, 0x07, 0x51, 0x52, 0x86, 0x74, 0x8a, 0x87, 0x17, 0x17, 0xf0, 0xe4, 0xea, 0x1c,
+];
+
+pub fn ecdsa_recover_evm_addr(
+ eth_signed_msg_hash: &[u8; HASH_BYTES],
+ sig: &Signature,
+) -> Result<[u8; EVM_ADDRESS_BYTES]> {
+ // retrieve signer public key
+ let public_key = sig.secp256k1_recover_from(eth_signed_msg_hash);
+ require!(public_key.is_ok(), McmError::FailedEcdsaRecover);
+
+ let public_key_bytes = &public_key.unwrap().to_bytes();
+
+ // return last 20 bytes of hashed public key as the recovered ethereum address
+ let evm_addr: [u8; EVM_ADDRESS_BYTES] = hash(public_key_bytes).to_bytes()
+ [(HASH_BYTES - EVM_ADDRESS_BYTES)..]
+ .try_into()
+ .unwrap();
+
+ Ok(evm_addr)
+}
+
+pub fn compute_eth_message_hash(root: &[u8; HASH_BYTES], valid_until: u32) -> Hash {
+ // Use big-endian encoding for EVM compatibility
+ let valid_until_bytes = left_pad_vec(&valid_until.to_be_bytes());
+ let hashed_encoded_params = hashv(&[root, &valid_until_bytes]);
+
+ hashv(&[
+ b"\x19Ethereum Signed Message:\n32",
+ &hashed_encoded_params.to_bytes(),
+ ])
+}
+
+pub fn calculate_merkle_root(
+ proof: Vec<[u8; HASH_BYTES]>,
+ leaf: &[u8; HASH_BYTES],
+) -> [u8; HASH_BYTES] {
+ let mut computed_hash = *leaf;
+ for proof_element in proof {
+ computed_hash = hash_pair(&computed_hash, &proof_element);
+ }
+ computed_hash
+}
+
+fn hash_pair(a: &[u8; HASH_BYTES], b: &[u8; HASH_BYTES]) -> [u8; HASH_BYTES] {
+ let (left, right) = if a < b { (a, b) } else { (b, a) };
+ hashv(&[left, right]).to_bytes()
+}
+
+fn _left_pad_vec(input: &[u8], num_bytes: usize) -> Vec {
+ let len = input.len();
+ if len >= num_bytes {
+ return input.to_vec();
+ };
+ let bytes_to_pad = num_bytes - len;
+ let mut padded: Vec = Vec::with_capacity(num_bytes);
+ padded.resize(bytes_to_pad, 0);
+ padded.extend_from_slice(input);
+ padded
+}
+
+pub fn left_pad_vec(input: &[u8]) -> Vec {
+ _left_pad_vec(input, HASH_BYTES)
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace, Debug)]
+pub struct Signature {
+ pub v: u8,
+ pub r: [u8; 32],
+ pub s: [u8; 32],
+}
+
+impl Signature {
+ fn secp256k1_recover_from(
+ &self,
+ eth_signed_msg_hash: &[u8; HASH_BYTES],
+ ) -> std::result::Result {
+ // See https://github.com/anza-xyz/agave/blob/c8685ce0e1bb9b26014f1024de2cd2b8c308cbde/curves/secp256k1-recover/src/lib.rs#L106-L115
+ if self.v < 27 {
+ return Err(Secp256k1RecoverError::InvalidRecoveryId);
+ }
+ let v = self.v - 27;
+ let rs = [self.r, self.s].concat();
+ secp256k1_recover(eth_signed_msg_hash, v, rs.as_slice())
+ }
+}
+
+impl RootMetadataInput {
+ // computes keccak256(abi.encode(MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA, metadata))
+ pub fn hash_leaf(&self) -> [u8; HASH_BYTES] {
+ let chain_id = left_pad_vec(&self.chain_id.to_le_bytes());
+ let pre_op_count = left_pad_vec(&self.pre_op_count.to_le_bytes());
+ let post_op_count = left_pad_vec(&self.post_op_count.to_le_bytes());
+
+ let override_previous_root: &[u8] = &[if self.override_previous_root { 1 } else { 0 }];
+ let override_previous_root_bytes = left_pad_vec(override_previous_root);
+
+ hashv(&[
+ MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA,
+ chain_id.as_slice(),
+ &self.multisig.to_bytes(),
+ pre_op_count.as_slice(),
+ post_op_count.as_slice(),
+ override_previous_root_bytes.as_slice(),
+ ])
+ .to_bytes()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // Last 8 bytes of keccak256("solana:localnet") as big-endian
+ // This is 0x4808e31713a26612 --> in little-endian, it is "1266a21317e30848"
+ const CHAIN_ID: u64 = 5190648258797659666;
+
+ fn _decode(s: &str) -> [u8; N] {
+ hex::decode(s).unwrap().to_owned().try_into().unwrap()
+ }
+
+ fn decode32(s: &str) -> [u8; HASH_BYTES] {
+ _decode::(s)
+ }
+
+ fn decode20(s: &str) -> [u8; EVM_ADDRESS_BYTES] {
+ _decode::(s)
+ }
+
+ mod test_hash_pair {
+ use super::*;
+
+ #[test]
+ fn basic_keccak() {
+ let a = &[0; 32];
+ let b = &[0; 32];
+ let result = hash_pair(a, b);
+
+ assert_eq!(
+ result,
+ decode32("ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5")
+ );
+ }
+
+ #[test]
+ fn ordering() {
+ let a = &[0; 32];
+ let b = &[1; 32];
+
+ let expected =
+ decode32("d5f4f7e1d989848480236fb0a5f808d5877abf778364ae50845234dd6c1e80fc");
+
+ assert_eq!(hash_pair(a, b), expected);
+ assert_eq!(hash_pair(b, a), expected);
+ }
+ }
+
+ mod test_left_pad_vec {
+ use super::*;
+
+ #[test]
+ fn too_few() {
+ let input = [1, 2, 3];
+ let result = _left_pad_vec(&input, 1); // 1 is smaller than the input length
+ assert_eq!(result.as_slice(), input);
+ }
+
+ #[test]
+ fn exact() {
+ let input = [1, 2, 3];
+ let result = _left_pad_vec(&input, input.len());
+ assert_eq!(result.as_slice(), input);
+ }
+
+ #[test]
+ fn single() {
+ let input = [1, 2, 3];
+ let result = _left_pad_vec(&input, input.len() + 1);
+ assert_eq!(result.as_slice(), [0, 1, 2, 3]);
+ }
+
+ #[test]
+ fn multiple() {
+ let input = [1, 2, 3];
+ let result = _left_pad_vec(&input, 32);
+ let expected: [u8; 32] = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 2, 3,
+ ];
+ assert_eq!(result.as_slice(), expected);
+ }
+ }
+ mod test_compute_merkle_root {
+ // All test values constructed from a tree with 3 leaves. See below "diagram" (all in hex):
+ //
+ // 0000000000000000000000000000000000000000000000000000000000000000
+ // |
+ // |--> 8e4b8e18156a1c7271055ce5b7ef53bb370294ebd631a3b95418a92da46e681f
+ // | |
+ // 1111111111111111111111111111111111111111111111111111111111111111 |
+ // |
+ // 2222222222222222222222222222222222222222222222222222222222222222 ----> 888aba2887457beba19643fd1c5e5be943d3f0b910d418c1ab49c057c06f6738
+
+ use super::*;
+
+ #[test]
+ fn identity() {
+ let hashed_leaf = [0; HASH_BYTES];
+ let proofs = vec![];
+ let expected_root = [0; HASH_BYTES];
+
+ let result = calculate_merkle_root(proofs, &hashed_leaf);
+ assert_eq!(expected_root, result);
+ }
+
+ #[test]
+ fn single_step() {
+ let hashed_leaf =
+ decode32("0000000000000000000000000000000000000000000000000000000000000000");
+
+ let proofs = vec![decode32(
+ "1111111111111111111111111111111111111111111111111111111111111111",
+ )];
+ let expected_root =
+ decode32("8e4b8e18156a1c7271055ce5b7ef53bb370294ebd631a3b95418a92da46e681f");
+
+ let result = calculate_merkle_root(proofs, &hashed_leaf);
+ assert_eq!(expected_root, result);
+ }
+
+ #[test]
+ fn multi_step() {
+ let hashed_leaf =
+ decode32("1111111111111111111111111111111111111111111111111111111111111111");
+ let proofs = vec![
+ decode32("0000000000000000000000000000000000000000000000000000000000000000"),
+ decode32("2222222222222222222222222222222222222222222222222222222222222222"),
+ ];
+
+ let expected_root: [u8; HASH_BYTES] =
+ decode32("888aba2887457beba19643fd1c5e5be943d3f0b910d418c1ab49c057c06f6738");
+
+ let result = calculate_merkle_root(proofs, &hashed_leaf);
+ assert_eq!(expected_root, result);
+ }
+ }
+
+ mod test_compute_eth_message_hash {
+ use super::*;
+
+ #[test]
+ fn basic() {
+ let root =
+ &decode32("d5ef592d1ad183db43b4980d7ab7ee43a6f6a284988c3e3a23d38c07beb520c7");
+ let valid_until: u32 = 1748317727;
+
+ let result = compute_eth_message_hash(root, valid_until);
+
+ assert_eq!(
+ result.to_bytes(),
+ decode32("032705bd71839baef725154f00f87ddcc1d95c4b5189c9fb5983f26ad6c95102")
+ );
+ }
+ }
+
+ mod test_hash_leaf {
+ use super::*;
+
+ #[test]
+ fn valid() {
+ let md = RootMetadataInput {
+ chain_id: CHAIN_ID,
+ multisig: Pubkey::new_from_array(decode32(
+ "b870e12dd379891561d2e9fa8f26431834eb736f2f24fc2a2a4dff1fd5dca4df",
+ )),
+ pre_op_count: 0,
+ post_op_count: 1,
+ override_previous_root: false,
+ };
+
+ assert_eq!(
+ md.hash_leaf(),
+ decode32("5b0c13f119b512c6b4de4c3fa2486a8261de1386d72e4535b7508e201fdb4826")
+ );
+
+ // This is the metadata in hex:
+ // e6b82be989101b4eb519770114b997b97b3c8707515286748a871717f0e4ea1c
+ // 0000000000000000000000000000000000000000000000001266a21317e30848
+ // b870e12dd379891561d2e9fa8f26431834eb736f2f24fc2a2a4dff1fd5dca4df
+ // 0000000000000000000000000000000000000000000000000000000000000000
+ // 0000000000000000000000000000000000000000000000000100000000000000
+ // 0000000000000000000000000000000000000000000000000000000000000000
+ }
+ }
+
+ mod test_ecdsa_recover_evm_addr {
+ use super::*;
+
+ #[test]
+ fn valid() {
+ let eth_signed_message_hash =
+ &decode32("910cd291f5281f5bf25d8a83962f282b6c2bdf831f079dfcb84480f922abd2e1");
+ let sig = &Signature {
+ v: 28, // 1c
+ r: decode32("45283a6239b1b559a910e97f79a52bab1605e8bd952c4b4e0720ed9b1e9e9671"),
+ s: decode32("2acab6f5f946bfa3dfa61f47705aff6e2f17f6ad83d484857bb119a06ba1f0e7"),
+ };
+ let recovered_addr = ecdsa_recover_evm_addr(eth_signed_message_hash, sig);
+ assert_eq!(
+ recovered_addr.unwrap(),
+ decode20("16c9fACed8a1e3C6aEA2B654EEca5617eb900EFf")
+ );
+ }
+ }
+}
diff --git a/chains/solana/contracts/programs/mcm/src/event.rs b/chains/solana/contracts/programs/mcm/src/event.rs
new file mode 100644
index 000000000..8779f54e9
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/event.rs
@@ -0,0 +1,35 @@
+use anchor_lang::prelude::*;
+
+use crate::constant::*;
+
+#[event]
+/// @dev Emitted when a new root is set.
+pub struct NewRoot {
+ pub root: [u8; 32],
+ pub valid_until: u32,
+
+ // Flatten Metadata fields
+ pub metadata_chain_id: u64,
+ pub metadata_multisig: Pubkey,
+ pub metadata_pre_op_count: u64,
+ pub metadata_post_op_count: u64,
+ pub metadata_override_previous_root: bool,
+}
+
+#[event]
+/// @dev Emitted when a new config is set.
+pub struct ConfigSet {
+ // todo: emitting all signers causes a memory overflow, need to find a way to emit them
+ // pub signers: Vec,
+ pub group_parents: [u8; NUM_GROUPS],
+ pub group_quorums: [u8; NUM_GROUPS],
+ pub is_root_cleared: bool,
+}
+
+#[event]
+/// @dev Emitted when an op gets successfully executed.
+pub struct OpExecuted {
+ pub nonce: u64,
+ pub to: Pubkey,
+ pub data: Vec,
+}
diff --git a/chains/solana/contracts/programs/mcm/src/instructions/execute.rs b/chains/solana/contracts/programs/mcm/src/instructions/execute.rs
new file mode 100644
index 000000000..abc9079f4
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/instructions/execute.rs
@@ -0,0 +1,277 @@
+use anchor_lang::prelude::*;
+use anchor_lang::solana_program::instruction::Instruction;
+use anchor_lang::solana_program::keccak::{hashv, HASH_BYTES};
+use anchor_lang::solana_program::program::invoke_signed;
+
+use crate::constant::*;
+use crate::error::*;
+use crate::eth_utils::*;
+use crate::event::*;
+use crate::state::config::*;
+use crate::state::root::*;
+
+pub fn execute<'info>(
+ ctx: Context<'_, '_, '_, 'info, Execute<'info>>,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ chain_id: u64,
+ nonce: u64,
+ data: Vec,
+ proof: Vec<[u8; 32]>,
+) -> Result<()> {
+ let Execute {
+ root_metadata,
+ expiring_root_and_op_count,
+ multisig_config,
+ multisig_signer,
+ ..
+ } = &ctx.accounts;
+
+ require!(
+ root_metadata.post_op_count > expiring_root_and_op_count.op_count,
+ McmError::PostOpCountReached
+ );
+
+ require!(chain_id == multisig_config.chain_id, McmError::WrongChainId);
+
+ // use unix_timestamp from Clock which provides seconds precision.
+ // while Solana's PoH provides cryptographic proofs of time passage,
+ // clock drift can still occur if slot times exceed the ideal 400ms
+ // (e.g., network had 30min drift in June 2022 when slots took 730ms)
+ // ref: https://docs.solana.com/architecture/synchronization
+ let now_ts = Clock::get()?.unix_timestamp;
+ require!(
+ now_ts <= expiring_root_and_op_count.valid_until.into(),
+ McmError::RootExpired
+ );
+
+ require!(
+ nonce == expiring_root_and_op_count.op_count,
+ McmError::WrongNonce
+ );
+
+ let acc_infos = ctx.remaining_accounts;
+ let acc_metas: Vec = acc_infos
+ .iter()
+ .flat_map(|acc_info| acc_info.to_account_metas(None))
+ .collect();
+
+ let op = Op {
+ chain_id: chain_id.to_owned(),
+ multisig: multisig_config.key(),
+ nonce: nonce.to_owned(),
+ data: data.to_owned(),
+ to: ctx.accounts.to.key(),
+
+ // remaining account metas, as received. No change made here to signers,
+ // so that the proofs can be checked prior to editing the signer.
+ remaining_accounts: acc_metas,
+ };
+
+ {
+ let calculated_root = calculate_merkle_root(proof, &op.hash_leaf());
+ require!(
+ calculated_root == ctx.accounts.expiring_root_and_op_count.root,
+ McmError::ProofCannotBeVerified
+ );
+ };
+
+ // All validations past, now actually execute the op and count it
+ let instruction = Instruction {
+ program_id: op.to,
+ accounts: op.cpi_remaining_accounts(multisig_signer.key()), // account metas enforcing our PDA as signer
+ data: op.data,
+ };
+
+ let seeds = &[
+ SIGNER_SEED,
+ multisig_name.as_ref(),
+ &[ctx.bumps.multisig_signer],
+ ];
+ let signer = &[&seeds[..]];
+
+ ctx.accounts.expiring_root_and_op_count.op_count += 1;
+
+ invoke_signed(&instruction, acc_infos, signer)?;
+
+ emit!(OpExecuted {
+ nonce,
+ to: instruction.program_id,
+ data: instruction.data,
+ });
+
+ Ok(())
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED])]
+pub struct Execute<'info> {
+ #[account(mut, seeds = [CONFIG_SEED, multisig_name.as_ref()], bump)]
+ pub multisig_config: Account<'info, MultisigConfig>,
+
+ #[account(seeds = [ROOT_METADATA_SEED, multisig_name.as_ref()], bump)]
+ pub root_metadata: Account<'info, RootMetadata>,
+
+ #[account(mut, seeds = [EXPIRING_ROOT_AND_OP_COUNT_SEED, multisig_name.as_ref()], bump)]
+ pub expiring_root_and_op_count: Account<'info, ExpiringRootAndOpCount>,
+
+ /// CHECK: This is just used to invoke it through the CPI, it's value is not accessed directly
+ pub to: UncheckedAccount<'info>,
+
+ /// CHECK: program signer PDA that can hold balance
+ #[account(
+ seeds = [SIGNER_SEED, multisig_name.as_ref()],
+ bump
+ )]
+ pub multisig_signer: UncheckedAccount<'info>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+}
+
+const MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP: &[u8] = &[
+ 0x08, 0xd2, 0x75, 0x62, 0x20, 0x06, 0xc4, 0xca, 0x82, 0xd0, 0x3f, 0x49, 0x8e, 0x90, 0x16, 0x3c,
+ 0xaf, 0xd5, 0x3c, 0x66, 0x3a, 0x48, 0x47, 0x0c, 0x3b, 0x52, 0xac, 0x8b, 0xfb, 0xd9, 0xf5, 0x2c,
+];
+
+#[derive(Debug)]
+pub struct Op {
+ pub chain_id: u64, // network identification
+ pub multisig: Pubkey, // multisig instance's PDA(config)
+ pub nonce: u64,
+ pub data: Vec,
+ pub to: Pubkey,
+ pub remaining_accounts: Vec,
+}
+
+impl Op {
+ fn hash_leaf(&self) -> [u8; HASH_BYTES] {
+ let chain_id = left_pad_vec(&self.chain_id.to_le_bytes());
+ let nonce = left_pad_vec(&self.nonce.to_le_bytes());
+ let data_len = left_pad_vec(&self.data.len().to_le_bytes());
+ let remaining_accounts_len = left_pad_vec(&self.remaining_accounts.len().to_le_bytes());
+
+ hashv(&[
+ MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP,
+ chain_id.as_slice(),
+ &self.multisig.to_bytes(),
+ nonce.as_slice(),
+ &self.to.to_bytes(),
+ data_len.as_slice(),
+ self.data.as_slice(),
+ remaining_accounts_len.as_slice(),
+ self.serialized_remaining_accounts().as_slice(),
+ ])
+ .to_bytes()
+ }
+
+ fn serialized_remaining_accounts(&self) -> Vec {
+ self.remaining_accounts
+ .iter()
+ .flat_map(|meta| {
+ let mut bytes_vec = meta.pubkey.to_bytes().to_vec();
+ bytes_vec.append(&mut bools_to_byte(meta.is_signer, meta.is_writable));
+ bytes_vec
+ })
+ .collect()
+ }
+
+ fn cpi_remaining_accounts(&self, multisig_signer: Pubkey) -> Vec {
+ self.remaining_accounts
+ .iter()
+ .map(|acc_meta| {
+ let mut cpi_acc_meta = acc_meta.clone();
+ cpi_acc_meta.is_signer = acc_meta.pubkey == multisig_signer;
+ cpi_acc_meta
+ })
+ .collect()
+ }
+}
+
+fn bools_to_byte(b1: bool, b2: bool) -> Vec {
+ let byte = match (b1, b2) {
+ (false, false) => 0x00,
+ (false, true) => 0x01,
+ (true, false) => 0x02,
+ (true, true) => 0x03,
+ };
+ vec![byte]
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // Last 8 bytes of keccak256("solana:localnet") as big-endian
+ // This is 0x4808e31713a26612 --> in little-endian, it is "1266a21317e30848"
+ const CHAIN_ID: u64 = 5190648258797659666;
+
+ fn decode32(s: &str) -> [u8; 32] {
+ hex::decode(s).unwrap().to_owned().try_into().unwrap()
+ }
+
+ mod test_op_hash_leaf {
+ use super::*;
+
+ #[test]
+ fn empty_op() {
+ let op = Op {
+ chain_id: CHAIN_ID,
+ multisig: Pubkey::from(decode32(
+ "eceeab9f961bbf0050babcbf22663ee37905749830f41cd5096fbc9b158f8b13",
+ )),
+ nonce: 0,
+ data: hex::decode("d62c04f70c29d96e").unwrap(),
+ to: Pubkey::from(decode32(
+ "30d721519466e7a7c60691f75f49734954bee02fdb6700dd7f02a19de972ccdb",
+ )),
+ remaining_accounts: vec![],
+ };
+
+ // op in hex:
+ // 08d275622006c4ca82d03f498e90163cafd53c663a48470c3b52ac8bfbd9f52c separator
+ // 0000000000000000000000000000000000000000000000001266a21317e30848 chain_id
+ // eceeab9f961bbf0050babcbf22663ee37905749830f41cd5096fbc9b158f8b13 multisig
+ // 0000000000000000000000000000000000000000000000000000000000000000 nonce
+ // 30d721519466e7a7c60691f75f49734954bee02fdb6700dd7f02a19de972ccdb to
+ // 0000000000000000000000000000000000000000000000000800000000000000 data_len
+ // d62c04f70c29d96e data
+ // 0000000000000000000000000000000000000000000000000000000000000000 remaining_accounts_len
+
+ assert_eq!(
+ op.hash_leaf(),
+ decode32("98ab3312f47d75a0173f23f50ba6042748e07bea064ae297982efebae3ecec9b")
+ );
+ }
+
+ #[test]
+ fn u8_instruction_data() {
+ let op = Op {
+ chain_id: CHAIN_ID,
+ multisig: Pubkey::from(decode32(
+ "eceeab9f961bbf0050babcbf22663ee37905749830f41cd5096fbc9b158f8b13",
+ )),
+ nonce: 1,
+ data: hex::decode("11af9cfd5bad1ae47b").unwrap(),
+ to: Pubkey::from(decode32(
+ "30d721519466e7a7c60691f75f49734954bee02fdb6700dd7f02a19de972ccdb",
+ )),
+ remaining_accounts: vec![],
+ };
+
+ // Raw Buffers
+ // Buffer[0]: 08d275622006c4ca82d03f498e90163cafd53c663a48470c3b52ac8bfbd9f52c
+ // Buffer[1]: 0000000000000000000000000000000000000000000000001266a21317e30848
+ // Buffer[2]: eceeab9f961bbf0050babcbf22663ee37905749830f41cd5096fbc9b158f8b13
+ // Buffer[3]: 0000000000000000000000000000000000000000000000000100000000000000
+ // Buffer[4]: 30d721519466e7a7c60691f75f49734954bee02fdb6700dd7f02a19de972ccdb
+ // Buffer[5]: 0000000000000000000000000000000000000000000000000900000000000000
+ // Buffer[6]: 11af9cfd5bad1ae47b
+ // Buffer[7]: 0000000000000000000000000000000000000000000000000000000000000000
+
+ assert_eq!(
+ op.hash_leaf(),
+ decode32("a23f9edc9ab94e247f6273d36633dced27525727b6a50aee95f14fd92aadb6e0")
+ );
+ }
+ }
+}
diff --git a/chains/solana/contracts/programs/mcm/src/instructions/mod.rs b/chains/solana/contracts/programs/mcm/src/instructions/mod.rs
new file mode 100644
index 000000000..ece3ef0b4
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/instructions/mod.rs
@@ -0,0 +1,7 @@
+pub mod execute;
+pub mod set_config;
+pub mod set_root;
+
+pub use execute::*;
+pub use set_config::*;
+pub use set_root::*;
diff --git a/chains/solana/contracts/programs/mcm/src/instructions/set_config.rs b/chains/solana/contracts/programs/mcm/src/instructions/set_config.rs
new file mode 100644
index 000000000..fa4d62b3b
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/instructions/set_config.rs
@@ -0,0 +1,301 @@
+use anchor_lang::prelude::*;
+
+use crate::config::*;
+use crate::constant::*;
+use crate::error::*;
+use crate::eth_utils::*;
+use crate::event::*;
+
+/// Set the configuration for the multisig instance after validating the input
+pub fn set_config(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED], // for pda derivation
+ signer_groups: Vec,
+ group_quorums: [u8; NUM_GROUPS],
+ group_parents: [u8; NUM_GROUPS],
+ clear_root: bool,
+) -> Result<()> {
+ // signer addresses are preloaded in the ConfigSigners account through InitSigners, AppendSigners and FinalizeSigners instructions
+ let signer_addresses = &ctx.accounts.config_signers.signer_addresses;
+
+ require!(
+ !signer_addresses.is_empty() && signer_addresses.len() <= MAX_NUM_SIGNERS,
+ McmError::OutOfBoundsNumOfSigners
+ );
+
+ require!(
+ signer_addresses.len() == signer_groups.len(),
+ McmError::MismatchedInputSignerVectorsLength
+ );
+
+ // count the number of children for each group while validating group structure
+ let mut group_children_counts = signer_groups.iter().try_fold(
+ [0u8; NUM_GROUPS],
+ |mut acc, &group| -> Result<[u8; NUM_GROUPS]> {
+ // make sure the specified signer group is in bound
+ require!(
+ (group as usize) < NUM_GROUPS,
+ McmError::MismatchedInputGroupArraysLength
+ );
+ acc[group as usize] = acc[group as usize]
+ .checked_add(1)
+ .ok_or(McmError::Overflow)?;
+
+ Ok(acc)
+ },
+ )?;
+
+ const ROOT_GROUP: usize = 0;
+ // check if the group structure is a tree
+ for i in (0..NUM_GROUPS).rev() {
+ // validate group structure in backwards(root is 0)
+
+ match i {
+ // root should have itself as parent
+ ROOT_GROUP => require!(
+ group_parents[ROOT_GROUP] == ROOT_GROUP as u8,
+ McmError::GroupTreeNotWellFormed
+ ),
+ // make sure the parent group is at a higher level(lower index) than the current group
+ _ => require!(group_parents[i] < i as u8, McmError::GroupTreeNotWellFormed),
+ }
+
+ let disabled: bool = group_quorums[i] == 0;
+
+ match disabled {
+ true => {
+ // validate disabled group has no children
+ require!(
+ group_children_counts[i] == 0,
+ McmError::SignerInDisabledGroup
+ );
+ }
+ false => {
+ // ensure the group quorum can be met(i.e. have more signers than the quorum)
+ require!(
+ group_children_counts[i] >= group_quorums[i],
+ McmError::OutOfBoundsGroupQuorum
+ );
+
+ // increase the parent group's children count
+ let parent_index = group_parents[i] as usize;
+ group_children_counts[parent_index] = group_children_counts[parent_index]
+ .checked_add(1)
+ .ok_or(McmError::Overflow)?;
+ }
+ }
+ }
+
+ let config = &mut ctx.accounts.multisig_config;
+ let mut signers: Vec = Vec::with_capacity(signer_addresses.len());
+ let mut prev_signer = [0u8; EVM_ADDRESS_BYTES];
+
+ for (index, &evm_addr) in signer_addresses.iter().enumerate() {
+ require!(
+ evm_addr > prev_signer,
+ McmError::SignersAddressesMustBeStrictlyIncreasing
+ );
+
+ // update prev signer
+ prev_signer = evm_addr;
+
+ signers.push(McmSigner {
+ evm_address: evm_addr,
+ index: u8::try_from(index).unwrap(), // This is safe due to previous check on signer_addresses length
+ group: signer_groups[index],
+ })
+ }
+
+ config.signers = signers;
+ config.group_quorums = group_quorums;
+ config.group_parents = group_parents;
+
+ emit!(ConfigSet {
+ // todo: memory inefficient, finding workaround
+ // signers: config.signers.clone(),
+ group_parents,
+ group_quorums,
+ is_root_cleared: clear_root,
+ });
+
+ Ok(())
+}
+
+pub fn init_signers(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+ total_signers: u8,
+) -> Result<()> {
+ require!(
+ total_signers > 0 && total_signers <= MAX_NUM_SIGNERS as u8,
+ McmError::OutOfBoundsNumOfSigners
+ );
+ let evm_signers_acc = &mut ctx.accounts.config_signers;
+ evm_signers_acc.bump = ctx.bumps.config_signers;
+ evm_signers_acc.total_signers = total_signers;
+
+ // Note: is_finalized stays false until finalization
+ Ok(())
+}
+
+pub fn append_signers(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+ signers_batch: Vec<[u8; 20]>,
+) -> Result<()> {
+ let evm_signers_acc = &mut ctx.accounts.config_signers;
+
+ // check bounds
+ require!(
+ evm_signers_acc.signer_addresses.len() + signers_batch.len()
+ <= evm_signers_acc.total_signers as usize,
+ McmError::OutOfBoundsNumOfSigners
+ );
+
+ // check if the signers are strictly increasing from the last signer
+ let mut prev_signer = evm_signers_acc
+ .signer_addresses
+ .last()
+ .copied()
+ .unwrap_or([0u8; EVM_ADDRESS_BYTES]);
+
+ for sig in signers_batch {
+ require!(
+ sig > prev_signer,
+ McmError::SignersAddressesMustBeStrictlyIncreasing
+ );
+ prev_signer = sig;
+ evm_signers_acc.signer_addresses.push(sig);
+ }
+ Ok(())
+}
+
+pub fn clear_signers(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+) -> Result<()> {
+ let config_signers = &mut ctx.accounts.config_signers;
+ config_signers.signer_addresses.clear();
+ Ok(())
+}
+
+pub fn finalize_signers(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+) -> Result<()> {
+ let evm_signers_acc = &mut ctx.accounts.config_signers;
+
+ require!(
+ !evm_signers_acc.signer_addresses.is_empty()
+ && evm_signers_acc.signer_addresses.len() == evm_signers_acc.total_signers as usize,
+ McmError::OutOfBoundsNumOfSigners
+ );
+
+ evm_signers_acc.is_finalized = true;
+ Ok(())
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED])]
+pub struct SetConfig<'info> {
+ #[account(
+ mut,
+ seeds = [CONFIG_SEED, multisig_name.as_ref()],
+ bump,
+ realloc = ANCHOR_DISCRIMINATOR + MultisigConfig::space_with_signers(
+ config_signers.signer_addresses.len()
+ ),
+ realloc::payer = authority,
+ realloc::zero = true,
+ )]
+ pub multisig_config: Account<'info, MultisigConfig>,
+
+ #[account(
+ mut,
+ seeds = [CONFIG_SIGNERS_SEED, multisig_name.as_ref()],
+ bump,
+ constraint = config_signers.is_finalized @ McmError::SignersNotFinalized,
+ close = authority // close after config set
+ )]
+ pub config_signers: Account<'info, ConfigSigners>, // preloaded signers account
+
+ #[account(mut, address = multisig_config.owner @ McmError::Unauthorized)]
+ pub authority: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED], total_signers: u8)]
+pub struct InitSigners<'info> {
+ #[account(seeds = [CONFIG_SEED, multisig_name.as_ref()], bump)]
+ pub multisig_config: Account<'info, MultisigConfig>,
+
+ #[account(
+ init,
+ payer = authority,
+ space = ConfigSigners::space(total_signers as usize),
+ seeds = [CONFIG_SIGNERS_SEED, multisig_name.as_ref()],
+ bump
+ )]
+ pub config_signers: Account<'info, ConfigSigners>,
+
+ #[account(mut, address = multisig_config.owner @ McmError::Unauthorized)]
+ pub authority: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED])]
+pub struct AppendSigners<'info> {
+ #[account(seeds = [CONFIG_SEED, multisig_name.as_ref()], bump)]
+ pub multisig_config: Account<'info, MultisigConfig>,
+
+ #[account(
+ mut,
+ seeds = [CONFIG_SIGNERS_SEED, multisig_name.as_ref()],
+ bump,
+ constraint = !config_signers.is_finalized @ McmError::SignersAlreadyFinalized
+ )]
+ pub config_signers: Account<'info, ConfigSigners>,
+
+ #[account(mut, address = multisig_config.owner @ McmError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED])]
+pub struct ClearSigners<'info> {
+ #[account(seeds = [CONFIG_SEED, multisig_name.as_ref()], bump)]
+ pub multisig_config: Account<'info, MultisigConfig>,
+
+ #[account(
+ mut,
+ seeds = [CONFIG_SIGNERS_SEED, multisig_name.as_ref()],
+ bump,
+ constraint = !config_signers.is_finalized @ McmError::SignersNotFinalized,
+ )]
+ pub config_signers: Account<'info, ConfigSigners>,
+
+ #[account(mut, address = multisig_config.owner @ McmError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED])]
+pub struct FinalizeSigners<'info> {
+ #[account(seeds = [CONFIG_SEED, multisig_name.as_ref()], bump)]
+ pub multisig_config: Account<'info, MultisigConfig>,
+
+ #[account(
+ mut,
+ seeds = [CONFIG_SIGNERS_SEED, multisig_name.as_ref()],
+ bump,
+ constraint = !config_signers.is_finalized @ McmError::SignersAlreadyFinalized
+ )]
+ pub config_signers: Account<'info, ConfigSigners>,
+
+ #[account(mut, address = multisig_config.owner @ McmError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
diff --git a/chains/solana/contracts/programs/mcm/src/instructions/set_root.rs b/chains/solana/contracts/programs/mcm/src/instructions/set_root.rs
new file mode 100644
index 000000000..c02150ee5
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/instructions/set_root.rs
@@ -0,0 +1,345 @@
+use anchor_lang::prelude::*;
+
+use crate::config::MultisigConfig;
+use crate::constant::*;
+use crate::error::*;
+use crate::eth_utils::*;
+use crate::event::*;
+use crate::state::root::*;
+
+pub fn set_root(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+ root: [u8; 32],
+ valid_until: u32,
+ metadata: RootMetadataInput,
+ metadata_proof: Vec<[u8; 32]>,
+) -> Result<()> {
+ require!(
+ !ctx.accounts.seen_signed_hashes.seen,
+ McmError::SignedHashAlreadySeen
+ );
+
+ // verify ECDSA signatures on (root, validUntil) and ensure that the root group is successful
+ let verified = verify_ecdsa_signatures(&ctx, &root, valid_until);
+ #[allow(clippy::unnecessary_unwrap)]
+ if verified.is_err() {
+ return Err(verified.unwrap_err());
+ }
+
+ require!(
+ Clock::get()?.unix_timestamp <= valid_until.into(),
+ McmError::ValidUntilHasAlreadyPassed
+ );
+
+ {
+ // verify metadataProof
+ let calculated_root = calculate_merkle_root(metadata_proof.clone(), &metadata.hash_leaf());
+ require!(root == calculated_root, McmError::ProofCannotBeVerified);
+ }
+
+ require_eq!(
+ metadata.chain_id,
+ ctx.accounts.multisig_config.chain_id,
+ McmError::WrongChainId
+ );
+
+ require_keys_eq!(
+ ctx.accounts.multisig_config.key(),
+ metadata.multisig.key(),
+ McmError::WrongMultiSig
+ );
+
+ let current_op_count = ctx.accounts.expiring_root_and_op_count.op_count;
+ require!(
+ current_op_count == ctx.accounts.root_metadata.post_op_count
+ || metadata.override_previous_root,
+ McmError::PendingOps
+ );
+
+ // the signers are responsible for tracking opCount offchain and ensuring that
+ // preOpCount equals to opCount
+ require_eq!(
+ current_op_count,
+ metadata.pre_op_count,
+ McmError::WrongPreOpCount
+ );
+
+ // note: allows preOpCount == postOpCount to support overriding previous root
+ // while canceling remaining operations
+ require!(
+ metadata.pre_op_count <= metadata.post_op_count,
+ McmError::WrongPostOpCount
+ );
+
+ // After validations, persist all changes
+ ctx.accounts.seen_signed_hashes.seen = true; // now it has been seen
+ ctx.accounts
+ .expiring_root_and_op_count
+ .set_inner(ExpiringRootAndOpCount {
+ root,
+ valid_until,
+ op_count: metadata.pre_op_count,
+ });
+
+ {
+ let account_data = RootMetadata {
+ chain_id: metadata.chain_id,
+ multisig: metadata.multisig,
+ pre_op_count: metadata.pre_op_count,
+ post_op_count: metadata.post_op_count,
+ override_previous_root: metadata.override_previous_root,
+ };
+ ctx.accounts.root_metadata.set_inner(account_data);
+ }
+
+ let md = &ctx.accounts.root_metadata;
+
+ emit!(NewRoot {
+ root,
+ valid_until,
+ metadata_chain_id: md.chain_id,
+ metadata_multisig: md.multisig,
+ metadata_pre_op_count: md.pre_op_count,
+ metadata_post_op_count: md.post_op_count,
+ metadata_override_previous_root: md.override_previous_root,
+ });
+
+ Ok(())
+}
+
+fn verify_ecdsa_signatures(
+ ctx: &Context,
+ root: &[u8; 32],
+ valid_until: u32,
+) -> Result<()> {
+ let signed_hash = compute_eth_message_hash(root, valid_until);
+ // get preloaded signatures
+ let signatures = &ctx.accounts.root_signatures.signatures;
+ let mut previous_addr: [u8; EVM_ADDRESS_BYTES] = [0; EVM_ADDRESS_BYTES];
+ let mut group_vote_counts: [u8; NUM_GROUPS] = [0; NUM_GROUPS];
+ let multisig_config = &ctx.accounts.multisig_config;
+ for sig in signatures {
+ let signer_addr = ecdsa_recover_evm_addr(&signed_hash.to_bytes(), sig);
+
+ #[allow(clippy::unnecessary_unwrap)]
+ // Clippy's alternatives cause problems with conditionally returning (early exit of the fn)
+ if signer_addr.is_err() {
+ return Err(signer_addr.unwrap_err());
+ }
+ let signer_addr = signer_addr.unwrap();
+
+ require!(
+ signer_addr.gt(&previous_addr),
+ McmError::SignersAddressesMustBeStrictlyIncreasing
+ );
+
+ previous_addr = signer_addr;
+
+ let signer_idx = multisig_config
+ .signers
+ .binary_search_by_key(&signer_addr, |s| s.evm_address);
+
+ require!(signer_idx.is_ok(), McmError::InvalidSigner);
+ let signer = &multisig_config.signers[signer_idx.unwrap()];
+
+ let mut group = usize::from(signer.group);
+ loop {
+ group_vote_counts[group] += 1;
+ if group_vote_counts[group] != multisig_config.group_quorums[group] {
+ // bail out unless we just hit the quorum. we only hit each quorum once,
+ // so we never move on to the parent of a group more than once.
+ break;
+ }
+ if group == 0 {
+ // reached root
+ break;
+ }
+ group = usize::from(multisig_config.group_parents[group]);
+ }
+ }
+
+ // the group at the root of the tree (with index 0) determines whether the vote passed,
+ // we cannot proceed if it isn't configured with a valid (non-zero) quorum
+ require!(
+ multisig_config.group_quorums[0] != 0,
+ McmError::MissingConfig
+ );
+
+ // did the root group reach its quorum?
+ require!(
+ group_vote_counts[0] >= multisig_config.group_quorums[0],
+ McmError::InsufficientSigners
+ );
+
+ Ok(())
+}
+
+pub fn init_signatures(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+ _root: [u8; 32],
+ _valid_until: u32,
+ total_signatures: u8,
+) -> Result<()> {
+ let signatures_account = &mut ctx.accounts.signatures;
+ signatures_account.bump = ctx.bumps.signatures;
+ signatures_account.total_signatures = total_signatures;
+
+ // Note: is_finalized stays false until finalization
+ Ok(())
+}
+
+pub fn append_signatures(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+ _root: [u8; 32],
+ _valid_until: u32,
+ signatures_batch: Vec,
+) -> Result<()> {
+ let signatures_account = &mut ctx.accounts.signatures;
+
+ require!(
+ signatures_account.signatures.len() + signatures_batch.len()
+ <= signatures_account.total_signatures as usize,
+ McmError::TooManySignatures
+ );
+
+ for sig in signatures_batch {
+ signatures_account.signatures.push(sig);
+ }
+ Ok(())
+}
+
+pub fn clear_signatures(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+ _root: [u8; 32],
+ _valid_until: u32,
+) -> Result<()> {
+ let signatures_account = &mut ctx.accounts.signatures;
+ signatures_account.signatures.clear();
+ Ok(())
+}
+
+pub fn finalize_signatures(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+ _root: [u8; 32],
+ _valid_until: u32,
+) -> Result<()> {
+ let signatures_account = &mut ctx.accounts.signatures;
+
+ require!(
+ !signatures_account.signatures.is_empty()
+ && signatures_account.signatures.len() == signatures_account.total_signatures as usize,
+ McmError::SignatureCountMismatch
+ );
+
+ signatures_account.is_finalized = true;
+ Ok(())
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED], root: [u8; 32], valid_until: u32)]
+pub struct SetRoot<'info> {
+ #[account(
+ mut,
+ seeds = [ROOT_SIGNATURES_SEED, multisig_name.as_ref(), root.as_ref(), valid_until.to_le_bytes().as_ref()],
+ bump,
+ constraint = root_signatures.is_finalized @ McmError::SignaturesNotFinalized,
+ close = authority
+ )]
+ pub root_signatures: Account<'info, RootSignatures>, // preloaded signatures account
+
+ #[account(mut, seeds = [ROOT_METADATA_SEED, multisig_name.as_ref()], bump)]
+ pub root_metadata: Account<'info, RootMetadata>,
+
+ #[account(
+ init,
+ seeds = [SEEN_SIGNED_HASHES_SEED, multisig_name.as_ref(), root.as_ref(), valid_until.to_le_bytes().as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + SeenSignedHash::INIT_SPACE,
+ )]
+ pub seen_signed_hashes: Account<'info, SeenSignedHash>,
+
+ #[account(mut, seeds = [EXPIRING_ROOT_AND_OP_COUNT_SEED, multisig_name.as_ref()], bump)]
+ pub expiring_root_and_op_count: Account<'info, ExpiringRootAndOpCount>,
+
+ #[account(seeds = [CONFIG_SEED, multisig_name.as_ref()],bump)]
+ pub multisig_config: Account<'info, MultisigConfig>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ root: [u8; 32],
+ valid_until: u32,
+ total_signatures: u8,
+)]
+pub struct InitSignatures<'info> {
+ #[account(
+ init,
+ payer = authority,
+ space = RootSignatures::space(total_signatures as usize),
+ seeds = [ROOT_SIGNATURES_SEED, multisig_name.as_ref(), root.as_ref(), valid_until.to_le_bytes().as_ref()],
+ bump
+ )]
+ pub signatures: Account<'info, RootSignatures>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED], root: [u8; 32], valid_until: u32)]
+pub struct AppendSignatures<'info> {
+ #[account(
+ mut,
+ seeds = [ROOT_SIGNATURES_SEED, multisig_name.as_ref(), root.as_ref(), valid_until.to_le_bytes().as_ref()],
+ bump,
+ constraint = !signatures.is_finalized @ McmError::SignaturesAlreadyFinalized
+ )]
+ pub signatures: Account<'info, RootSignatures>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED], root: [u8; 32], valid_until: u32)]
+pub struct ClearSignatures<'info> {
+ #[account(
+ mut,
+ seeds = [ROOT_SIGNATURES_SEED, multisig_name.as_ref(), root.as_ref(), valid_until.to_le_bytes().as_ref()],
+ bump,
+ constraint = !signatures.is_finalized @ McmError::SignaturesAlreadyFinalized,
+ )]
+ pub signatures: Account<'info, RootSignatures>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED], root: [u8; 32], valid_until: u32)]
+pub struct FinalizeSignatures<'info> {
+ #[account(
+ mut,
+ seeds = [ROOT_SIGNATURES_SEED, multisig_name.as_ref(), root.as_ref(), valid_until.to_le_bytes().as_ref()],
+ bump,
+ constraint = !signatures.is_finalized @ McmError::SignaturesAlreadyFinalized
+ )]
+ pub signatures: Account<'info, RootSignatures>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+}
diff --git a/chains/solana/contracts/programs/mcm/src/lib.rs b/chains/solana/contracts/programs/mcm/src/lib.rs
new file mode 100644
index 000000000..78a42741e
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/lib.rs
@@ -0,0 +1,251 @@
+use anchor_lang::prelude::*;
+
+declare_id!("6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX");
+
+use program::Mcm;
+
+mod constant;
+pub use constant::*;
+
+mod error;
+pub use error::*;
+
+mod event;
+
+mod state;
+pub use state::root::*;
+pub use state::*;
+
+mod eth_utils;
+use eth_utils::*;
+
+mod instructions;
+use instructions::*;
+
+/// This is mcm program supporting multiple instances of multisig configuration
+/// A single deployed program manages multiple multisig states(configurations) identified by multisig_name
+#[program]
+pub mod mcm {
+ use super::*;
+
+ /// initialize a new multisig configuration, store the chain_id and multisig_name
+ /// multisig_name is a unique identifier for the multisig configuration(32 bytes, left-padded)
+ pub fn initialize(
+ ctx: Context,
+ chain_id: u64,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ ) -> Result<()> {
+ let config = &mut ctx.accounts.multisig_config;
+ config.chain_id = chain_id;
+ config.multisig_name = multisig_name;
+ config.owner = ctx.accounts.authority.key();
+
+ // convert bytes to string for logging
+ let name_str = String::from_utf8_lossy(&multisig_name)
+ .trim_end_matches(char::from(0))
+ .to_string();
+
+ msg!("Initialized MCM {} with chain_id {}", name_str, chain_id);
+ Ok(())
+ }
+
+ // shared func signature with other programs
+ pub fn transfer_ownership(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+ proposed_owner: Pubkey,
+ ) -> Result<()> {
+ let config = &mut ctx.accounts.config;
+ require!(proposed_owner != config.owner, McmError::InvalidInputs);
+ config.proposed_owner = proposed_owner;
+ Ok(())
+ }
+
+ // shared func signature with other programs
+ pub fn accept_ownership(
+ ctx: Context,
+ _multisig_name: [u8; MULTISIG_NAME_PADDED],
+ ) -> Result<()> {
+ ctx.accounts.config.owner = std::mem::take(&mut ctx.accounts.config.proposed_owner);
+ ctx.accounts.config.proposed_owner = Pubkey::new_from_array([0; 32]);
+ Ok(())
+ }
+
+ pub fn set_config(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ signer_groups: Vec,
+ group_quorums: [u8; NUM_GROUPS],
+ group_parents: [u8; NUM_GROUPS],
+ clear_root: bool,
+ ) -> Result<()> {
+ instructions::set_config(
+ ctx,
+ multisig_name,
+ signer_groups,
+ group_quorums,
+ group_parents,
+ clear_root,
+ )
+ }
+
+ pub fn set_root(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ root: [u8; 32],
+ valid_until: u32,
+ metadata: RootMetadataInput,
+ metadata_proof: Vec<[u8; 32]>,
+ ) -> Result<()> {
+ instructions::set_root(
+ ctx,
+ multisig_name,
+ root,
+ valid_until,
+ metadata,
+ metadata_proof,
+ )
+ }
+
+ pub fn execute<'info>(
+ ctx: Context<'_, '_, '_, 'info, Execute<'info>>,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ chain_id: u64,
+ nonce: u64,
+ data: Vec, // bytes array
+ proof: Vec<[u8; 32]>,
+ ) -> Result<()> {
+ instructions::execute(ctx, multisig_name, chain_id, nonce, data, proof)
+ }
+
+ // batch configuration methods prerequisites for set_config
+ pub fn init_signers(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ total_signers: u8,
+ ) -> Result<()> {
+ instructions::init_signers(ctx, multisig_name, total_signers)
+ }
+
+ pub fn append_signers(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ signers_batch: Vec<[u8; 20]>,
+ ) -> Result<()> {
+ instructions::append_signers(ctx, multisig_name, signers_batch)
+ }
+
+ pub fn clear_signers(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ ) -> Result<()> {
+ instructions::clear_signers(ctx, multisig_name)
+ }
+
+ pub fn finalize_signers(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ ) -> Result<()> {
+ instructions::finalize_signers(ctx, multisig_name)
+ }
+
+ // batch configuration methods prerequisites for set_root
+ pub fn init_signatures(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ root: [u8; 32],
+ valid_until: u32,
+ total_signatures: u8,
+ ) -> Result<()> {
+ instructions::init_signatures(ctx, multisig_name, root, valid_until, total_signatures)
+ }
+
+ pub fn append_signatures(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ root: [u8; 32],
+ valid_until: u32,
+ signatures_batch: Vec,
+ ) -> Result<()> {
+ instructions::append_signatures(ctx, multisig_name, root, valid_until, signatures_batch)
+ }
+ pub fn clear_signatures(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ root: [u8; 32],
+ valid_until: u32,
+ ) -> Result<()> {
+ instructions::clear_signatures(ctx, multisig_name, root, valid_until)
+ }
+
+ pub fn finalize_signatures(
+ ctx: Context,
+ multisig_name: [u8; MULTISIG_NAME_PADDED],
+ root: [u8; 32],
+ valid_until: u32,
+ ) -> Result<()> {
+ instructions::finalize_signatures(ctx, multisig_name, root, valid_until)
+ }
+}
+
+#[derive(Accounts)]
+#[instruction(chain_id: u64, multisig_name: [u8; MULTISIG_NAME_PADDED])]
+pub struct Initialize<'info> {
+ #[account(
+ init,
+ seeds = [CONFIG_SEED, multisig_name.as_ref()],
+ bump,
+ space = ANCHOR_DISCRIMINATOR + config::MultisigConfig::INIT_SPACE,
+ payer = authority,
+ )]
+ pub multisig_config: Account<'info, config::MultisigConfig>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+
+ #[account(constraint = program.programdata_address()? == Some(program_data.key()))]
+ pub program: Program<'info, Mcm>,
+ // initialization only allowed by program upgrade authority(in common cases, the initial deployer)
+ #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()) @ McmError::Unauthorized)]
+ pub program_data: Account<'info, ProgramData>,
+
+ #[account(
+ init,
+ seeds = [ROOT_METADATA_SEED, multisig_name.as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + RootMetadata::INIT_SPACE
+ )]
+ pub root_metadata: Account<'info, RootMetadata>,
+
+ #[account(
+ init,
+ seeds = [EXPIRING_ROOT_AND_OP_COUNT_SEED, multisig_name.as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + ExpiringRootAndOpCount::INIT_SPACE,
+ )]
+ pub expiring_root_and_op_count: Account<'info, ExpiringRootAndOpCount>,
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED])]
+pub struct TransferOwnership<'info> {
+ #[account(mut, seeds = [CONFIG_SEED, multisig_name.as_ref()], bump)]
+ pub config: Account<'info, config::MultisigConfig>,
+
+ #[account(address = config.owner @ McmError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(multisig_name: [u8; MULTISIG_NAME_PADDED])]
+pub struct AcceptOwnership<'info> {
+ #[account(mut, seeds = [CONFIG_SEED, multisig_name.as_ref()], bump)]
+ pub config: Account<'info, config::MultisigConfig>,
+
+ #[account(address = config.proposed_owner @ McmError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
diff --git a/chains/solana/contracts/programs/mcm/src/state/config.rs b/chains/solana/contracts/programs/mcm/src/state/config.rs
new file mode 100644
index 000000000..5c9619575
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/state/config.rs
@@ -0,0 +1,58 @@
+use anchor_lang::prelude::*;
+
+use crate::{constant::*, EVM_ADDRESS_BYTES};
+
+#[account]
+pub struct ConfigSigners {
+ pub signer_addresses: Vec<[u8; 20]>,
+ pub total_signers: u8,
+ pub is_finalized: bool,
+ pub bump: u8,
+}
+
+impl ConfigSigners {
+ // 8 (discriminator) + 4 (vec len) + (20 * total_signers) +1(total_signers) + 1 (is_finalized) + 1 (bump)
+ pub const fn space(total_signers: usize) -> usize {
+ 8 + 4 + (20 * total_signers) + 1 + 1 + 1
+ }
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace, Debug)]
+pub struct McmSigner {
+ pub evm_address: [u8; EVM_ADDRESS_BYTES],
+ pub index: u8,
+ pub group: u8,
+}
+
+#[account]
+pub struct MultisigConfig {
+ pub chain_id: u64,
+ pub multisig_name: [u8; MULTISIG_NAME_PADDED],
+
+ pub owner: Pubkey,
+ pub proposed_owner: Pubkey,
+
+ pub group_quorums: [u8; NUM_GROUPS],
+ pub group_parents: [u8; NUM_GROUPS],
+
+ // Keep variable-length data at the end of the account struct
+ // https://solana.com/developers/courses/program-optimization/program-architecture#data-order
+ pub signers: Vec, // unable to store as hashmap in Solana
+}
+
+impl MultisigConfig {
+ pub const INIT_SPACE: usize = 8 + // chain_id (u64)
+ NUM_GROUPS + // group_quorums [u8; NUM_GROUPS]
+ NUM_GROUPS + // group_parents [u8; NUM_GROUPS]
+ 32 + // owner (Pubkey)
+ 32 + // proposed_owner (Pubkey)
+ 4 + // string prefix for multisig_name
+ MULTISIG_NAME_PADDED + // fixed max multisig_name length from initialization
+ 4; // empty vec prefix for signers
+
+ // for realloc - only need to account for signers
+ pub fn space_with_signers(num_signers: usize) -> usize {
+ Self::INIT_SPACE + // Base space including fixed multisig_name
+ num_signers * McmSigner::INIT_SPACE // Just add signers space
+ }
+}
diff --git a/chains/solana/contracts/programs/mcm/src/state/mod.rs b/chains/solana/contracts/programs/mcm/src/state/mod.rs
new file mode 100644
index 000000000..7ad9cf298
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/state/mod.rs
@@ -0,0 +1,2 @@
+pub mod config;
+pub mod root;
diff --git a/chains/solana/contracts/programs/mcm/src/state/root.rs b/chains/solana/contracts/programs/mcm/src/state/root.rs
new file mode 100644
index 000000000..64addc883
--- /dev/null
+++ b/chains/solana/contracts/programs/mcm/src/state/root.rs
@@ -0,0 +1,51 @@
+use anchor_lang::prelude::*;
+
+use crate::eth_utils::*;
+
+#[account]
+pub struct RootSignatures {
+ pub total_signatures: u8,
+ pub signatures: Vec,
+ pub is_finalized: bool,
+ pub bump: u8,
+}
+
+impl RootSignatures {
+ // 8 (discriminator) + 4 (vec len) + (65 * max_sigs) + 32 (root) + 4 (valid_until) + 1 (is_finalized) + 1 (bump)
+ pub const fn space(total_signatures: usize) -> usize {
+ 8 + 4 + (65 * total_signatures) + 32 + 4 + 1 + 1 + 1
+ }
+}
+
+#[account]
+#[derive(InitSpace, Debug)]
+pub struct RootMetadata {
+ pub chain_id: u64,
+ pub multisig: Pubkey,
+ pub pre_op_count: u64,
+ pub post_op_count: u64,
+ pub override_previous_root: bool,
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
+pub struct RootMetadataInput {
+ pub chain_id: u64,
+ pub multisig: Pubkey,
+ pub pre_op_count: u64,
+ pub post_op_count: u64,
+ pub override_previous_root: bool,
+}
+
+#[account]
+#[derive(InitSpace)]
+pub struct ExpiringRootAndOpCount {
+ pub root: [u8; 32],
+ pub valid_until: u32,
+ pub op_count: u64,
+}
+
+#[account]
+#[derive(InitSpace)]
+pub struct SeenSignedHash {
+ pub seen: bool, // always set to true in practice when initialized, hacky way of defining a Set via PDAs.
+}
diff --git a/chains/solana/contracts/programs/timelock/Cargo.toml b/chains/solana/contracts/programs/timelock/Cargo.toml
new file mode 100644
index 000000000..d73caf861
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "timelock"
+version = "0.0.1-dev"
+description = "Solana implementation of RBAC Timelock"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "timelock"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = { version = "0.29.0", features = ["init-if-needed"] }
+access-controller = { version = "1.0.1", path = "../access-controller", default-features = false, features = ["cpi"] }
+bytemuck = "1.7"
+
+[dev-dependencies]
+hex = "0.4.3"
diff --git a/chains/solana/contracts/programs/timelock/Xargo.toml b/chains/solana/contracts/programs/timelock/Xargo.toml
new file mode 100644
index 000000000..475fb71ed
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/Xargo.toml
@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []
diff --git a/chains/solana/contracts/programs/timelock/src/access.rs b/chains/solana/contracts/programs/timelock/src/access.rs
new file mode 100644
index 000000000..576240da1
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/access.rs
@@ -0,0 +1,61 @@
+use anchor_lang::prelude::*;
+
+use access_controller::AccessController;
+
+use crate::error::TimelockError;
+use crate::state::{Config, Role};
+
+// NOTE: This macro is used to check if the authority is the owner
+// or account that has access to the role
+#[macro_export]
+macro_rules! only_role_or_admin_role {
+ ($ctx:expr, $role:expr) => {
+ only_role_or_admin_role(
+ &$ctx.accounts.config,
+ &$ctx.accounts.role_access_controller,
+ &$ctx.accounts.authority,
+ $role,
+ )
+ };
+}
+
+pub fn only_role_or_admin_role(
+ config: &Account,
+ role_controller: &AccountLoader,
+ authority: &Signer,
+ role: Role,
+) -> Result<()> {
+ // early authorization check if the authority is the owner
+ if authority.key() == config.owner {
+ return Ok(());
+ }
+
+ // check the provided access controller is the correct one
+ require_keys_eq!(
+ config.get_role_controller(&role),
+ role_controller.key(),
+ TimelockError::InvalidAccessController
+ );
+
+ // check if the authority has access to the role
+ require!(
+ access_controller::has_access(role_controller, &authority.key())?,
+ TimelockError::Unauthorized
+ );
+
+ Ok(())
+}
+
+// NOTE: This macro is used to check if the authority is the owner
+// this can be in account contexts, but added also for explicit consistency with other role check
+#[macro_export]
+macro_rules! only_admin {
+ ($ctx:expr) => {
+ only_admin(&$ctx.accounts.config, &$ctx.accounts.authority)
+ };
+}
+
+pub fn only_admin(config: &Account, authority: &Signer) -> Result<()> {
+ require_keys_eq!(authority.key(), config.owner, TimelockError::Unauthorized);
+ Ok(())
+}
diff --git a/chains/solana/contracts/programs/timelock/src/constants.rs b/chains/solana/contracts/programs/timelock/src/constants.rs
new file mode 100644
index 000000000..256f9cf28
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/constants.rs
@@ -0,0 +1,8 @@
+pub const TIMELOCK_CONFIG_SEED: &[u8] = b"timelock_config";
+pub const TIMELOCK_OPERATION_SEED: &[u8] = b"timelock_operation";
+pub const TIMELOCK_SIGNER_SEED: &[u8] = b"timelock_signer";
+pub const TIMELOCK_BLOCKED_FUNCITON_SELECTOR_SEED: &[u8] = b"timelock_blocked_function_selector";
+
+pub const ANCHOR_DISCRIMINATOR: usize = 8;
+pub const DONE_TIMESTAMP: u64 = 1;
+pub const EMPTY_PREDECESSOR: [u8; 32] = [0; 32];
diff --git a/chains/solana/contracts/programs/timelock/src/error.rs b/chains/solana/contracts/programs/timelock/src/error.rs
new file mode 100644
index 000000000..aa585a4b1
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/error.rs
@@ -0,0 +1,54 @@
+use anchor_lang::error_code;
+
+#[error_code]
+pub enum PlaceholderError {
+ #[msg("Todo generate error type with anchor")]
+ Placeholder,
+}
+
+#[error_code]
+pub enum TimelockError {
+ #[msg("The signer is unauthorized")]
+ Unauthorized = 0,
+
+ #[msg("Invalid inputs")]
+ InvalidInput,
+
+ #[msg("Overflow")]
+ Overflow,
+
+ #[msg("Provided ID is invalid")]
+ InvalidId,
+
+ #[msg("RBACTimelock: operation not finalized")]
+ OperationNotFinalized,
+
+ #[msg("RBACTimelock: operation is already finalized")]
+ OperationAlreadyFinalized,
+
+ #[msg("RBACTimelock: too many instructions in the operation")]
+ TooManyInstructions,
+
+ // on attempt to create PDA with the same seed(existing operation)
+ #[msg("RBACTimelock: operation already scheduled")]
+ OperationAlreadyScheduled,
+
+ #[msg("RBACTimelock: insufficient delay")]
+ DelayInsufficient,
+
+ // cancel
+ #[msg("RBACTimelock: operation cannot be cancelled")]
+ OperationNotCancellable,
+
+ #[msg("operation is not ready")]
+ OperationNotReady,
+
+ #[msg("Predecessor operation is not found")]
+ MissingDependency,
+
+ #[msg("RBACTimelock: selector is blocked")]
+ BlockedSelector,
+
+ #[msg("RBACTimelock: Provided access controller is invalid")]
+ InvalidAccessController,
+}
diff --git a/chains/solana/contracts/programs/timelock/src/event.rs b/chains/solana/contracts/programs/timelock/src/event.rs
new file mode 100644
index 000000000..0736e08fe
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/event.rs
@@ -0,0 +1,55 @@
+use anchor_lang::prelude::*;
+
+#[event]
+/// @dev Emitted when a call is scheduled as part of operation `id`.
+pub struct CallScheduled {
+ pub id: [u8; 32],
+ pub index: u64,
+ pub target: Pubkey,
+ pub data: Vec,
+ pub predecessor: [u8; 32],
+ pub salt: [u8; 32],
+ pub delay: u64,
+}
+
+#[event]
+/// @dev Emitted when a call is performed as part of operation `id`.
+pub struct CallExecuted {
+ pub id: [u8; 32],
+ pub index: u64,
+ pub target: Pubkey,
+ pub data: Vec,
+}
+
+#[event]
+/// @dev Emitted when a call is performed via bypasser.
+pub struct BypasserCallExecuted {
+ pub index: u64,
+ pub target: Pubkey,
+ pub data: Vec,
+}
+
+#[event]
+/// @dev Emitted when operation `id` is cancelled.
+pub struct Cancelled {
+ pub id: [u8; 32],
+}
+
+#[event]
+/// @dev Emitted when the minimum delay for future operations is modified.
+pub struct MinDelayChange {
+ pub old_duration: u64,
+ pub new_duration: u64,
+}
+
+#[event]
+/// @dev Emitted when a function selector is blocked.
+pub struct FunctionSelectorBlocked {
+ pub selector: [u8; 8],
+}
+
+#[event]
+/// @dev Emitted when a function selector is unblocked.
+pub struct FunctionSelectorUnblocked {
+ pub selector: [u8; 8],
+}
diff --git a/chains/solana/contracts/programs/timelock/src/instructions/cancel.rs b/chains/solana/contracts/programs/timelock/src/instructions/cancel.rs
new file mode 100644
index 000000000..5952b1bf5
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/instructions/cancel.rs
@@ -0,0 +1,37 @@
+use anchor_lang::prelude::*;
+
+use access_controller::AccessController;
+
+use crate::constants::{TIMELOCK_CONFIG_SEED, TIMELOCK_OPERATION_SEED};
+use crate::error::TimelockError;
+use crate::event::*;
+use crate::state::{Config, Operation};
+
+/// cancels a pending operation
+pub fn cancel<'info>(_ctx: Context<'_, '_, '_, 'info, Cancel<'info>>, id: [u8; 32]) -> Result<()> {
+ emit!(Cancelled { id });
+ // NOTE: PDA is closed - is handled by anchor on exit due to the `close` attribute
+ Ok(())
+}
+
+#[derive(Accounts)]
+#[instruction(id: [u8; 32])]
+pub struct Cancel<'info> {
+ #[account( seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+
+ #[account(
+ mut,
+ seeds = [TIMELOCK_OPERATION_SEED, id.as_ref()],
+ bump,
+ close = authority, // todo: check if we send fund back to the signer, otherwise, we'll have additional destination account
+ constraint = operation.id == id @ TimelockError::InvalidId,
+ constraint = operation.is_pending() @ TimelockError::OperationNotCancellable,
+ )]
+ pub operation: Account<'info, Operation>,
+ // NOTE: access controller check happens in only_role_or_admin_role macro
+ pub role_access_controller: AccountLoader<'info, AccessController>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+}
diff --git a/chains/solana/contracts/programs/timelock/src/instructions/execute.rs b/chains/solana/contracts/programs/timelock/src/instructions/execute.rs
new file mode 100644
index 000000000..5707e62ed
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/instructions/execute.rs
@@ -0,0 +1,184 @@
+use anchor_lang::solana_program::instruction::Instruction;
+use anchor_lang::{prelude::*, solana_program};
+use solana_program::program::invoke_signed;
+
+use access_controller::AccessController;
+use bytemuck::Zeroable;
+
+use crate::constants::{
+ EMPTY_PREDECESSOR, TIMELOCK_CONFIG_SEED, TIMELOCK_OPERATION_SEED, TIMELOCK_SIGNER_SEED,
+};
+use crate::error::TimelockError;
+use crate::event::*;
+use crate::state::{Config, InstructionData, Operation};
+
+/// execute scheduled operation(instructions) in batch
+/// operation can be executed only if it's ready and all predecessors are done
+pub fn execute_batch<'info>(
+ ctx: Context<'_, '_, '_, 'info, ExecuteBatch<'info>>,
+ _id: [u8; 32],
+) -> Result<()> {
+ let op = &mut ctx.accounts.operation;
+
+ // check if the operation is ready
+ let current_time = Clock::get()?.unix_timestamp as u64;
+ require!(op.is_ready(current_time), TimelockError::OperationNotReady);
+
+ if op.predecessor != EMPTY_PREDECESSOR {
+ // force predecessor to be provided
+ let (expected_address, _) = Pubkey::find_program_address(
+ &[TIMELOCK_OPERATION_SEED, op.predecessor.as_ref()],
+ ctx.program_id,
+ );
+
+ require!(
+ ctx.accounts.predecessor_operation.key() == expected_address,
+ TimelockError::InvalidInput
+ );
+
+ let predecessor_data = ctx.accounts.predecessor_operation.try_borrow_data()?;
+ let mut predecessor_data_slice: &[u8] = &predecessor_data;
+ let predecessor_acc = Operation::try_deserialize(&mut predecessor_data_slice)?;
+
+ require!(predecessor_acc.is_done(), TimelockError::MissingDependency);
+ } else {
+ require!(
+ ctx.accounts.predecessor_operation.key() == Pubkey::zeroed(),
+ TimelockError::InvalidInput
+ );
+ }
+
+ let seeds = &[TIMELOCK_SIGNER_SEED, &[ctx.bumps.timelock_signer]];
+ let signer = &[&seeds[..]];
+
+ for (i, instruction_data) in op.instructions.iter().enumerate() {
+ execute(
+ instruction_data,
+ ctx.remaining_accounts,
+ signer,
+ ctx.accounts.timelock_signer.key(),
+ )?;
+
+ emit!(CallExecuted {
+ id: op.id,
+ index: i as u64,
+ target: instruction_data.program_id,
+ data: instruction_data.data.clone(),
+ });
+ }
+
+ require!(op.is_ready(current_time), TimelockError::OperationNotReady);
+
+ // all executed, update the timestamp
+ op.mark_done();
+
+ Ok(())
+}
+
+/// execute operation(instructions) w/o checking predecessors and readiness
+/// bypasser_execute also need the operation to be uploaded formerly
+pub fn bypasser_execute_batch<'info>(
+ ctx: Context<'_, '_, '_, 'info, BypasserExecuteBatch<'info>>,
+ _id: [u8; 32],
+) -> Result<()> {
+ let op = &mut ctx.accounts.operation;
+
+ let seeds = &[TIMELOCK_SIGNER_SEED, &[ctx.bumps.timelock_signer]];
+ let signer = &[&seeds[..]];
+
+ for (i, instruction_data) in op.instructions.iter().enumerate() {
+ execute(
+ instruction_data,
+ ctx.remaining_accounts,
+ signer,
+ ctx.accounts.timelock_signer.key(),
+ )?;
+ emit!(BypasserCallExecuted {
+ index: i as u64,
+ target: instruction_data.program_id,
+ data: instruction_data.data.clone(),
+ });
+ }
+
+ Ok(())
+}
+
+/// execute each instruction to target program with timelock signer
+fn execute(
+ instruction: &InstructionData,
+ remaining_accounts: &[AccountInfo],
+ signer_seeds: &[&[&[u8]]],
+ timelock_signer: Pubkey,
+) -> Result<()> {
+ let mut ix: Instruction = instruction.into();
+
+ ix.accounts = ix
+ .accounts
+ .iter()
+ .map(|acc| {
+ let mut acc = acc.clone();
+ acc.is_signer = acc.pubkey == timelock_signer;
+ acc
+ })
+ .collect();
+
+ invoke_signed(&ix, remaining_accounts, signer_seeds)?;
+ Ok(())
+}
+
+#[derive(Accounts)]
+#[instruction(id: [u8; 32])]
+pub struct ExecuteBatch<'info> {
+ #[account( seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+
+ /// CHECK: program signer PDA that can hold balance
+ #[account(
+ seeds = [TIMELOCK_SIGNER_SEED],
+ bump
+ )]
+ pub timelock_signer: UncheckedAccount<'info>,
+
+ #[account(
+ mut,
+ seeds = [TIMELOCK_OPERATION_SEED, id.as_ref()],
+ bump,
+ constraint = operation.is_scheduled() @ TimelockError::InvalidId,
+ )]
+ pub operation: Account<'info, Operation>,
+
+ /// CHECK: Will be validated in handler if predecessor exists
+ pub predecessor_operation: UncheckedAccount<'info>,
+ // NOTE: access controller check happens in only_role_or_admin_role macro
+ pub role_access_controller: AccountLoader<'info, AccessController>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(id: [u8; 32])]
+pub struct BypasserExecuteBatch<'info> {
+ #[account( seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+
+ /// CHECK: program signer PDA that can hold balance
+ #[account(
+ seeds = [TIMELOCK_SIGNER_SEED],
+ bump
+ )]
+ pub timelock_signer: UncheckedAccount<'info>,
+
+ #[account(
+ mut,
+ seeds = [TIMELOCK_OPERATION_SEED, id.as_ref()],
+ bump,
+ constraint = operation.is_finalized @ TimelockError::OperationNotFinalized,
+ )]
+ pub operation: Account<'info, Operation>,
+ // NOTE: access controller check happens in only_role_or_admin_role macro
+ pub role_access_controller: AccountLoader<'info, AccessController>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+}
diff --git a/chains/solana/contracts/programs/timelock/src/instructions/initialize.rs b/chains/solana/contracts/programs/timelock/src/instructions/initialize.rs
new file mode 100644
index 000000000..d440bb0ae
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/instructions/initialize.rs
@@ -0,0 +1,108 @@
+use anchor_lang::prelude::*;
+
+use access_controller::AccessController;
+
+use crate::constants::{ANCHOR_DISCRIMINATOR, TIMELOCK_CONFIG_SEED};
+use crate::error::TimelockError;
+use crate::program::Timelock;
+use crate::state::{Config, Role};
+
+/// initialize Timelock config with owner(admin),
+/// role access controller keys and global configuration value.
+pub fn initialize(ctx: Context, min_delay: u64) -> Result<()> {
+ // assign owner(owner is admin)
+ let config = &mut ctx.accounts.config;
+ config.owner = ctx.accounts.authority.key();
+ config.min_delay = min_delay;
+
+ config.proposer_role_access_controller = ctx.accounts.proposer_role_access_controller.key();
+ config.executor_role_access_controller = ctx.accounts.executor_role_access_controller.key();
+ config.canceller_role_access_controller = ctx.accounts.canceller_role_access_controller.key();
+ config.bypasser_role_access_controller = ctx.accounts.bypasser_role_access_controller.key();
+
+ Ok(())
+}
+
+/// wrapper function that calls access_controller::cpi::add_access via batch CPI call.
+/// target addresses should be provided in remaining_accounts,
+/// tested with up to 24 addresses per each transaction.
+pub fn batch_add_access<'info>(
+ ctx: Context<'_, '_, '_, 'info, BatchAddAccess<'info>>,
+ _role: Role,
+) -> Result<()> {
+ require!(
+ !ctx.remaining_accounts.is_empty(),
+ TimelockError::InvalidInput
+ );
+
+ for account_info in ctx.remaining_accounts.iter() {
+ let cpi_accounts = access_controller::cpi::accounts::AddAccess {
+ state: ctx.accounts.role_access_controller.to_account_info(),
+ owner: ctx.accounts.authority.to_account_info(),
+ address: account_info.clone(),
+ };
+ let cpi_ctx = CpiContext::new(
+ ctx.accounts.access_controller_program.to_account_info(),
+ cpi_accounts,
+ );
+ access_controller::cpi::add_access(cpi_ctx)?;
+ }
+ Ok(())
+}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+ #[account(
+ init,
+ space = ANCHOR_DISCRIMINATOR + Config::INIT_SPACE,
+ seeds = [TIMELOCK_CONFIG_SEED],
+ bump,
+ payer = authority,
+ )]
+ pub config: Account<'info, Config>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+
+ #[account(constraint = program.programdata_address()? == Some(program_data.key()))]
+ pub program: Program<'info, Timelock>,
+ // NOTE: initialization only allowed by program upgrade authority
+ #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()) @ TimelockError::Unauthorized)]
+ pub program_data: Account<'info, ProgramData>,
+
+ // access controller program and states per role
+ pub access_controller_program: Program<'info, access_controller::program::AccessController>,
+ #[account(owner = access_controller_program.key())]
+ pub proposer_role_access_controller: AccountLoader<'info, AccessController>,
+ #[account(owner = access_controller_program.key())]
+ pub executor_role_access_controller: AccountLoader<'info, AccessController>,
+ #[account(owner = access_controller_program.key())]
+ pub canceller_role_access_controller: AccountLoader<'info, AccessController>,
+ #[account(owner = access_controller_program.key())]
+ pub bypasser_role_access_controller: AccountLoader<'info, AccessController>,
+}
+
+#[derive(Accounts)]
+#[instruction(role: Role)]
+pub struct BatchAddAccess<'info> {
+ #[account(
+ seeds = [TIMELOCK_CONFIG_SEED],
+ bump,
+ )]
+ pub config: Account<'info, Config>,
+
+ pub access_controller_program: Program<'info, access_controller::program::AccessController>,
+
+ // NOTE: access controller for the role of access list
+ #[account(
+ mut,
+ owner = access_controller_program.key(),
+ address = config.get_role_controller(&role) @ TimelockError::InvalidAccessController,
+ )]
+ pub role_access_controller: AccountLoader<'info, AccessController>,
+
+ #[account(mut, address = config.owner)]
+ pub authority: Signer<'info>,
+}
diff --git a/chains/solana/contracts/programs/timelock/src/instructions/mod.rs b/chains/solana/contracts/programs/timelock/src/instructions/mod.rs
new file mode 100644
index 000000000..7978c6aca
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/instructions/mod.rs
@@ -0,0 +1,9 @@
+pub mod cancel;
+pub mod execute;
+pub mod initialize;
+pub mod schedule;
+
+pub use cancel::*;
+pub use execute::*;
+pub use initialize::*;
+pub use schedule::*;
diff --git a/chains/solana/contracts/programs/timelock/src/instructions/schedule.rs b/chains/solana/contracts/programs/timelock/src/instructions/schedule.rs
new file mode 100644
index 000000000..df8b7274d
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/instructions/schedule.rs
@@ -0,0 +1,230 @@
+use anchor_lang::prelude::*;
+
+use access_controller::AccessController;
+
+use crate::constants::{ANCHOR_DISCRIMINATOR, TIMELOCK_CONFIG_SEED, TIMELOCK_OPERATION_SEED};
+use crate::error::TimelockError;
+use crate::event::*;
+use crate::state::{Config, InstructionData, Operation};
+
+/// this function is used to schedule the operation
+/// Operation account should be preloaded with instructions and finalized before scheduling
+pub fn schedule_batch<'info>(
+ ctx: Context<'_, '_, '_, 'info, ScheduleBatch<'info>>,
+ _id: [u8; 32],
+ delay: u64,
+) -> Result<()> {
+ let op = &mut ctx.accounts.operation;
+
+ // delay should greater than min_delay
+ let config = &ctx.accounts.config;
+ require!(delay >= config.min_delay, TimelockError::DelayInsufficient);
+
+ let current_time = Clock::get()?.unix_timestamp as u64;
+ let scheduled_time = current_time
+ .checked_add(delay)
+ .ok_or(TimelockError::InvalidInput)?;
+
+ require!(
+ scheduled_time > current_time && scheduled_time < u64::MAX,
+ TimelockError::InvalidInput
+ );
+
+ op.timestamp = scheduled_time;
+
+ for (i, ix) in op.instructions.iter().enumerate() {
+ // todo: check function is not blocked
+
+ emit!(CallScheduled {
+ id: op.id,
+ index: i as u64,
+ target: ix.program_id,
+ data: ix.data.clone(),
+ predecessor: op.predecessor,
+ salt: op.salt,
+ delay,
+ });
+ }
+
+ Ok(())
+}
+
+/// initialize_operation, append_instructions, finalize_operation functions are used to create a new operation
+/// and add instructions to it. finalize_operation is used to mark the operation as finalized.
+/// only after the operation is finalized, it can be scheduled.
+/// this is due to the fact that the operation PDA cannot be initialized with CPI call from MCM program.
+/// This pattern also allows to execute larger transaction(multiple instructions) exceeding 1232 bytes
+/// in a single execute_batch transaction ensuring atomicy.
+pub fn initialize_operation<'info>(
+ ctx: Context<'_, '_, '_, 'info, InitializeOperation<'info>>,
+ id: [u8; 32],
+ predecessor: [u8; 32],
+ salt: [u8; 32],
+ instruction_count: u32,
+) -> Result<()> {
+ let op = &mut ctx.accounts.operation;
+
+ op.set_inner(Operation {
+ timestamp: 0, // not scheduled operation should have timestamp 0, see src/state/operation.rs
+ id, // id should be matched with hashed instructions(will be verified in finalize_operation)
+ predecessor, // required for dependency check
+ salt,
+ total_instructions: instruction_count,
+ instructions: Vec::with_capacity(instruction_count as usize), // create empty vector with total instruction capacity
+ is_finalized: false,
+ // operation.authority is the one who will schedule the operation(in mcm use case, the proposer msig signer PDA)
+ authority: ctx.accounts.proposer.key(),
+ });
+
+ Ok(())
+}
+
+/// append instructions to the operation
+pub fn append_instructions<'info>(
+ ctx: Context<'_, '_, '_, 'info, AppendInstructions<'info>>,
+ _id: [u8; 32],
+ instructions_batch: Vec,
+) -> Result<()> {
+ let op = &mut ctx.accounts.operation;
+ op.instructions.extend(instructions_batch);
+
+ Ok(())
+}
+
+pub fn clear_operation(_ctx: Context, _id: [u8; 32]) -> Result<()> {
+ // NOTE: ctx.accounts.operation is closed to be able to re-initialized,
+ // also allow finalized operation to be cleared
+ Ok(())
+}
+
+/// finalize the operation, this is required to schedule the operation
+/// verify the operation status and id before mark it as finalized
+pub fn finalize_operation<'info>(
+ ctx: Context<'_, '_, '_, 'info, FinalizeOperation<'info>>,
+ _id: [u8; 32],
+) -> Result<()> {
+ let op = &mut ctx.accounts.operation;
+ op.is_finalized = true;
+
+ Ok(())
+}
+
+#[derive(Accounts)]
+#[instruction(
+ id: [u8; 32],
+)]
+pub struct ScheduleBatch<'info> {
+ #[account( seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+
+ #[account(
+ mut,
+ seeds = [TIMELOCK_OPERATION_SEED, id.as_ref()],
+ bump,
+ constraint = operation.is_finalized @ TimelockError::OperationNotFinalized,
+ constraint = !operation.is_scheduled() @ TimelockError::OperationAlreadyScheduled
+ )]
+ pub operation: Box>,
+
+ // NOTE: access controller check happens in only_role_or_admin_role macro
+ pub role_access_controller: AccountLoader<'info, AccessController>,
+
+ #[account(
+ mut,
+ address = operation.authority.key() @ TimelockError::Unauthorized
+ )]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(
+ id: [u8; 32],
+ predecessor: [u8; 32],
+ salt: [u8; 32],
+ instruction_count: u32,
+)]
+pub struct InitializeOperation<'info> {
+ #[account( seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+
+ #[account(
+ init,
+ seeds = [TIMELOCK_OPERATION_SEED, id.as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + Operation::INIT_SPACE,
+ constraint = !operation.is_scheduled() @ TimelockError::OperationAlreadyScheduled
+ )]
+ pub operation: Account<'info, Operation>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+
+ /// CHECK: This is the proposer that will be allowed to schedule
+ pub proposer: AccountInfo<'info>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(id: [u8; 32], instructions_batch: Vec)]
+pub struct AppendInstructions<'info> {
+ #[account(
+ mut,
+ seeds = [TIMELOCK_OPERATION_SEED, id.as_ref()],
+ bump,
+ realloc = ANCHOR_DISCRIMINATOR +
+ Operation::INIT_SPACE +
+ operation.instructions.iter().map(|ix| { // space of existing instructions
+ InstructionData::space(ix)
+ }).sum::() +
+ instructions_batch.iter().map(|ix| { // add space for new instructions
+ InstructionData::space(ix)
+ }).sum::(),
+ realloc::payer = authority,
+ realloc::zero = false,
+ constraint = !operation.is_finalized @ TimelockError::OperationAlreadyFinalized,
+ constraint = !operation.is_scheduled() @ TimelockError::OperationAlreadyScheduled,
+ constraint = operation.instructions.len() + instructions_batch.len() <= operation.total_instructions as usize @ TimelockError::TooManyInstructions
+ )]
+ pub operation: Account<'info, Operation>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(id: [u8; 32])]
+pub struct ClearOperation<'info> {
+ #[account(
+ mut,
+ seeds = [TIMELOCK_OPERATION_SEED, id.as_ref()],
+ bump,
+ close = authority,
+ constraint = !operation.is_finalized @ TimelockError::OperationAlreadyFinalized,
+ )]
+ pub operation: Account<'info, Operation>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(id: [u8; 32])]
+pub struct FinalizeOperation<'info> {
+ #[account(
+ mut,
+ seeds = [TIMELOCK_OPERATION_SEED, id.as_ref()],
+ bump,
+ constraint = !operation.is_finalized @ TimelockError::OperationAlreadyFinalized,
+ constraint = !operation.is_scheduled() @ TimelockError::OperationAlreadyScheduled,
+ constraint = operation.instructions.len() == operation.total_instructions as usize @ TimelockError::TooManyInstructions,
+ constraint = operation.verify_id() @ TimelockError::InvalidId
+ )]
+ pub operation: Account<'info, Operation>,
+
+ #[account(mut)]
+ pub authority: Signer<'info>,
+}
diff --git a/chains/solana/contracts/programs/timelock/src/lib.rs b/chains/solana/contracts/programs/timelock/src/lib.rs
new file mode 100644
index 000000000..dffd3041c
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/lib.rs
@@ -0,0 +1,195 @@
+use anchor_lang::prelude::*;
+
+declare_id!("LoCoNsJFuhTkSQjfdDfn3yuwqhSYoPujmviRHVCzsqn");
+
+mod constants;
+pub use constants::*;
+
+pub mod access;
+pub use access::*;
+
+pub mod error;
+pub use error::*;
+
+pub mod event;
+pub use event::*;
+
+pub mod state;
+pub use state::*;
+
+pub mod instructions;
+use instructions::*;
+
+#[program]
+pub mod timelock {
+ use bytemuck::Zeroable;
+
+ use super::*;
+
+ pub fn initialize(ctx: Context, min_delay: u64) -> Result<()> {
+ initialize::initialize(ctx, min_delay)
+ }
+
+ #[access_control(only_admin!(ctx))]
+ pub fn batch_add_access<'info>(
+ ctx: Context<'_, '_, '_, 'info, BatchAddAccess<'info>>,
+ role: Role,
+ ) -> Result<()> {
+ initialize::batch_add_access(ctx, role)
+ }
+
+ #[access_control(only_role_or_admin_role!(ctx, Role::Proposer))]
+ pub fn schedule_batch<'info>(
+ ctx: Context<'_, '_, '_, 'info, ScheduleBatch<'info>>,
+ id: [u8; 32],
+ delay: u64,
+ ) -> Result<()> {
+ schedule::schedule_batch(ctx, id, delay)
+ }
+
+ pub fn initialize_operation<'info>(
+ ctx: Context<'_, '_, '_, 'info, InitializeOperation<'info>>,
+ id: [u8; 32],
+ predecessor: [u8; 32],
+ salt: [u8; 32],
+ instruction_count: u32,
+ ) -> Result<()> {
+ schedule::initialize_operation(ctx, id, predecessor, salt, instruction_count)
+ }
+
+ pub fn append_instructions<'info>(
+ ctx: Context<'_, '_, '_, 'info, AppendInstructions<'info>>,
+ id: [u8; 32],
+ instructions_batch: Vec,
+ ) -> Result<()> {
+ schedule::append_instructions(ctx, id, instructions_batch)
+ }
+
+ pub fn clear_operation<'info>(
+ ctx: Context<'_, '_, '_, 'info, ClearOperation<'info>>,
+ id: [u8; 32],
+ ) -> Result<()> {
+ schedule::clear_operation(ctx, id)
+ }
+
+ pub fn finalize_operation<'info>(
+ ctx: Context<'_, '_, '_, 'info, FinalizeOperation<'info>>,
+ id: [u8; 32],
+ ) -> Result<()> {
+ schedule::finalize_operation(ctx, id)
+ }
+
+ #[access_control(only_role_or_admin_role!(ctx, Role::Canceller))]
+ pub fn cancel<'info>(
+ ctx: Context<'_, '_, '_, 'info, Cancel<'info>>,
+ id: [u8; 32], // precalculated id of the tx(instructions)
+ ) -> Result<()> {
+ cancel::cancel(ctx, id)
+ }
+
+ #[access_control(only_role_or_admin_role!(ctx, Role::Executor))]
+ pub fn execute_batch<'info>(
+ ctx: Context<'_, '_, '_, 'info, ExecuteBatch<'info>>,
+ id: [u8; 32],
+ ) -> Result<()> {
+ execute::execute_batch(ctx, id)
+ }
+
+ #[access_control(only_role_or_admin_role!(ctx, Role::Bypasser))]
+ pub fn bypasser_execute_batch<'info>(
+ ctx: Context<'_, '_, '_, 'info, BypasserExecuteBatch<'info>>,
+ id: [u8; 32],
+ ) -> Result<()> {
+ execute::bypasser_execute_batch(ctx, id)
+ }
+
+ #[access_control(only_admin!(ctx))]
+ pub fn update_delay(ctx: Context, delay: u64) -> Result<()> {
+ let config = &mut ctx.accounts.config;
+ require!(delay > 0, TimelockError::InvalidInput);
+ emit!(MinDelayChange {
+ old_duration: config.min_delay,
+ new_duration: delay,
+ });
+ config.min_delay = delay;
+
+ Ok(())
+ }
+
+ #[access_control(only_admin!(ctx))]
+ pub fn block_function_selector(
+ ctx: Context,
+ selector: [u8; 8],
+ ) -> Result<()> {
+ // todo: implement function blocker related methods
+ emit!(FunctionSelectorBlocked { selector });
+ Ok(())
+ }
+
+ #[access_control(only_admin!(ctx))]
+ pub fn unblock_function_selector(
+ ctx: Context,
+ selector: [u8; 8],
+ ) -> Result<()> {
+ // todo: implement function blocker related methods
+ emit!(FunctionSelectorUnblocked { selector });
+ Ok(())
+ }
+
+ #[access_control(only_admin!(ctx))]
+ pub fn transfer_ownership(
+ ctx: Context,
+ proposed_owner: Pubkey,
+ ) -> Result<()> {
+ let config = &mut ctx.accounts.config;
+ require!(proposed_owner != config.owner, TimelockError::InvalidInput);
+ config.proposed_owner = proposed_owner;
+ Ok(())
+ }
+
+ pub fn accept_ownership(ctx: Context) -> Result<()> {
+ ctx.accounts.config.owner = std::mem::take(&mut ctx.accounts.config.proposed_owner);
+ ctx.accounts.config.proposed_owner = Pubkey::zeroed();
+ Ok(())
+ }
+}
+
+#[derive(Accounts)]
+pub struct TransferOwnership<'info> {
+ #[account(mut, seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+ // owner(admin) only, access control with only_admin macro
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct AcceptOwnership<'info> {
+ #[account(mut, seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+ #[account(address = config.proposed_owner @ TimelockError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct UpdateDelay<'info> {
+ #[account(mut, seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+ // owner(admin) only, access control with only_admin macro
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct BlockFunctionSelector<'info> {
+ #[account(seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+ // owner(admin) only, access control with only_admin macro
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct UnblockFunctionSelector<'info> {
+ #[account(seeds = [TIMELOCK_CONFIG_SEED], bump)]
+ pub config: Account<'info, Config>,
+ // owner(admin) only, access control with only_admin macro
+ pub authority: Signer<'info>,
+}
diff --git a/chains/solana/contracts/programs/timelock/src/state/config.rs b/chains/solana/contracts/programs/timelock/src/state/config.rs
new file mode 100644
index 000000000..d5e36f4ff
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/state/config.rs
@@ -0,0 +1,36 @@
+use anchor_lang::prelude::*;
+
+#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, PartialEq)]
+pub enum Role {
+ Admin = 0,
+ Proposer = 1,
+ Executor = 2,
+ Canceller = 3,
+ Bypasser = 4,
+}
+
+#[account]
+#[derive(InitSpace)]
+pub struct Config {
+ pub owner: Pubkey,
+ pub proposed_owner: Pubkey,
+
+ pub proposer_role_access_controller: Pubkey,
+ pub executor_role_access_controller: Pubkey,
+ pub canceller_role_access_controller: Pubkey,
+ pub bypasser_role_access_controller: Pubkey,
+
+ pub min_delay: u64, // initial minimum delay for operations
+}
+
+impl Config {
+ pub fn get_role_controller(&self, role: &Role) -> Pubkey {
+ match role {
+ Role::Proposer => self.proposer_role_access_controller,
+ Role::Executor => self.executor_role_access_controller,
+ Role::Canceller => self.canceller_role_access_controller,
+ Role::Bypasser => self.bypasser_role_access_controller,
+ _ => panic!("Invalid role"),
+ }
+ }
+}
diff --git a/chains/solana/contracts/programs/timelock/src/state/mod.rs b/chains/solana/contracts/programs/timelock/src/state/mod.rs
new file mode 100644
index 000000000..0e3eee2e8
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/state/mod.rs
@@ -0,0 +1,5 @@
+pub mod config;
+pub mod operation;
+
+pub use config::*;
+pub use operation::*;
diff --git a/chains/solana/contracts/programs/timelock/src/state/operation.rs b/chains/solana/contracts/programs/timelock/src/state/operation.rs
new file mode 100644
index 000000000..01331d16d
--- /dev/null
+++ b/chains/solana/contracts/programs/timelock/src/state/operation.rs
@@ -0,0 +1,273 @@
+use anchor_lang::prelude::*;
+use anchor_lang::solana_program::instruction::Instruction;
+use anchor_lang::solana_program::keccak::{hashv, HASH_BYTES};
+
+use crate::constants::DONE_TIMESTAMP;
+
+#[account]
+pub struct Operation {
+ pub timestamp: u64,
+ pub id: [u8; 32],
+ pub predecessor: [u8; 32],
+ pub salt: [u8; 32],
+
+ pub authority: Pubkey,
+ pub is_finalized: bool,
+ pub total_instructions: u32,
+ pub instructions: Vec,
+}
+
+impl Operation {
+ pub fn is_scheduled(&self) -> bool {
+ self.timestamp > 0
+ }
+
+ pub fn is_pending(&self) -> bool {
+ self.timestamp > DONE_TIMESTAMP
+ }
+
+ pub fn is_ready(&self, current_timestamp: u64) -> bool {
+ self.timestamp > DONE_TIMESTAMP && self.timestamp <= current_timestamp
+ }
+
+ pub fn is_done(&self) -> bool {
+ self.timestamp == DONE_TIMESTAMP
+ }
+
+ pub fn mark_done(&mut self) {
+ self.timestamp = DONE_TIMESTAMP;
+ }
+
+ pub fn hash_instructions(&self, salt: [u8; HASH_BYTES]) -> [u8; HASH_BYTES] {
+ let total_size = self
+ .instructions
+ .iter()
+ .map(|ix_data: &InstructionData| ix_data.space())
+ .sum::()
+ + HASH_BYTES * 2; // add predecessor and salt
+
+ let mut encoded_data = Vec::with_capacity(total_size);
+
+ // encode each instruction
+ for ix in &self.instructions {
+ encoded_data.extend_from_slice(&ix.program_id.to_bytes());
+
+ for acc in &ix.accounts {
+ encoded_data.extend_from_slice(&acc.pubkey.to_bytes());
+ encoded_data.push(acc.is_signer as u8);
+ encoded_data.push(acc.is_writable as u8);
+ }
+
+ encoded_data.extend_from_slice(&ix.data);
+ }
+
+ encoded_data.extend_from_slice(&self.predecessor);
+ encoded_data.extend_from_slice(&salt);
+
+ // hash everything with keccak256
+ hashv(&[&encoded_data]).to_bytes()
+ }
+
+ pub fn verify_id(&self) -> bool {
+ self.hash_instructions(self.salt) == self.id
+ }
+}
+
+impl Space for Operation {
+ // timestamp + id + predecessor + salt + total_ixs + is_finalized + authority + vec prefix for instructions
+ const INIT_SPACE: usize = 8 + 32 + 32 + 32 + 4 + 1 + 32 + 4;
+}
+
+// The native Solana's Instruction type from solana_program doesn't implement the AnchorSerialize trait.
+// This is a wrapper that provides serialization capabilities while maintaining the same functionality
+#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default, Debug)]
+pub struct InstructionData {
+ pub program_id: Pubkey,
+ pub data: Vec,
+ pub accounts: Vec,
+}
+
+impl InstructionData {
+ pub fn space(&self) -> usize {
+ // program id + vector prefix(data) + data + vector prefix(accounts) + accounts
+ 32 + 4 + self.data.len() + 4 + self.accounts.len() * InstructionAccount::INIT_SPACE
+ }
+}
+
+impl From<&InstructionData> for Instruction {
+ fn from(tx: &InstructionData) -> Instruction {
+ Instruction {
+ program_id: tx.program_id,
+ accounts: tx.accounts.iter().map(Into::into).collect(),
+ data: tx.data.clone(),
+ }
+ }
+}
+
+// NOTE: space for InstructionAccount is calculated with InitSpace trait since it's static
+#[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Clone, Default, Debug)]
+pub struct InstructionAccount {
+ pub pubkey: Pubkey,
+ pub is_signer: bool,
+ pub is_writable: bool,
+}
+
+impl From<&InstructionAccount> for AccountMeta {
+ fn from(account: &InstructionAccount) -> AccountMeta {
+ match account.is_writable {
+ false => AccountMeta::new_readonly(account.pubkey, account.is_signer),
+ true => AccountMeta::new(account.pubkey, account.is_signer),
+ }
+ }
+}
+
+impl From<&AccountMeta> for InstructionAccount {
+ fn from(account_meta: &AccountMeta) -> InstructionAccount {
+ InstructionAccount {
+ pubkey: account_meta.pubkey,
+ is_signer: account_meta.is_signer,
+ is_writable: account_meta.is_writable,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anchor_lang::solana_program::{keccak::HASH_BYTES, pubkey::Pubkey};
+
+ fn create_test_operation(
+ instructions: Vec,
+ predecessor: [u8; HASH_BYTES],
+ ) -> Operation {
+ Operation {
+ timestamp: 0,
+ id: [0u8; 32],
+ predecessor,
+ salt: [0u8; 32],
+ authority: Pubkey::new_unique(),
+ is_finalized: false,
+ total_instructions: instructions.len() as u32,
+ instructions,
+ }
+ }
+
+ #[test]
+ fn test_hash_operation_batch() {
+ let program_id = Pubkey::new_unique();
+ let account1 = Pubkey::new_unique();
+ let account2 = Pubkey::new_unique();
+
+ let tx1 = InstructionData {
+ program_id,
+ accounts: vec![
+ InstructionAccount {
+ pubkey: account1,
+ is_signer: true,
+ is_writable: true,
+ },
+ InstructionAccount {
+ pubkey: account2,
+ is_signer: false,
+ is_writable: true,
+ },
+ ],
+ data: vec![1, 2, 3],
+ };
+
+ let tx2 = InstructionData {
+ program_id,
+ accounts: vec![InstructionAccount {
+ pubkey: account2,
+ is_signer: false,
+ is_writable: false,
+ }],
+ data: vec![4, 5, 6],
+ };
+
+ let predecessor = [1u8; HASH_BYTES];
+ let salt = [2u8; HASH_BYTES];
+
+ // Test single instruction
+ let single_op = create_test_operation(vec![tx1.clone()], predecessor);
+ let result1 = single_op.hash_instructions(salt);
+ assert_eq!(result1.len(), HASH_BYTES);
+
+ // Test multiple instructions
+ let multiple_op = create_test_operation(vec![tx1, tx2], predecessor);
+ let result2 = multiple_op.hash_instructions(salt);
+ assert_eq!(result2.len(), HASH_BYTES);
+
+ // Results should be different
+ assert_ne!(result1, result2);
+ }
+
+ #[test]
+ fn test_empty_instruction_list() {
+ let predecessor = [1u8; HASH_BYTES];
+ let salt = [2u8; HASH_BYTES];
+
+ let empty_op = create_test_operation(vec![], predecessor);
+ let result = empty_op.hash_instructions(salt);
+ assert_eq!(result.len(), HASH_BYTES);
+
+ let different_salt = [3u8; HASH_BYTES];
+ let result2 = empty_op.hash_instructions(different_salt);
+ assert_ne!(result, result2);
+ }
+
+ #[test]
+ fn test_different_predecessors() {
+ let program_id = Pubkey::new_unique();
+ let account = Pubkey::new_unique();
+
+ let tx = InstructionData {
+ program_id,
+ accounts: vec![InstructionAccount {
+ pubkey: account,
+ is_signer: true,
+ is_writable: true,
+ }],
+ data: vec![1, 2, 3],
+ };
+
+ let predecessor1 = [1u8; HASH_BYTES];
+ let predecessor2 = [3u8; HASH_BYTES];
+ let salt = [2u8; HASH_BYTES];
+
+ let op1 = create_test_operation(vec![tx.clone()], predecessor1);
+ let result1 = op1.hash_instructions(salt);
+
+ let op2 = create_test_operation(vec![tx], predecessor2);
+ let result2 = op2.hash_instructions(salt);
+
+ assert_ne!(result1, result2);
+ }
+
+ #[test]
+ fn test_deterministic_output() {
+ let program_id = Pubkey::new_unique();
+ let account = Pubkey::new_unique();
+
+ let tx = InstructionData {
+ program_id,
+ accounts: vec![InstructionAccount {
+ pubkey: account,
+ is_signer: true,
+ is_writable: true,
+ }],
+ data: vec![1, 2, 3],
+ };
+
+ let predecessor = [1u8; HASH_BYTES];
+ let salt = [2u8; HASH_BYTES];
+
+ let op1 = create_test_operation(vec![tx.clone()], predecessor);
+ let result1 = op1.hash_instructions(salt);
+
+ let op2 = create_test_operation(vec![tx], predecessor);
+ let result2 = op2.hash_instructions(salt);
+
+ assert_eq!(result1, result2);
+ }
+}
diff --git a/chains/solana/contracts/programs/token-pool/Cargo.toml b/chains/solana/contracts/programs/token-pool/Cargo.toml
new file mode 100644
index 000000000..7811ee03d
--- /dev/null
+++ b/chains/solana/contracts/programs/token-pool/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "token-pool"
+version = "0.1.0-dev"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "token_pool"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = { version = "0.29.0", features = ["init-if-needed"] }
+anchor-spl = "0.29.0"
+solana-program = "1.17.25" # pin solana to 1.17
+spl-math = { version = "0.2.0", features = [ "no-entrypoint" ] }
diff --git a/chains/solana/contracts/programs/token-pool/Xargo.toml b/chains/solana/contracts/programs/token-pool/Xargo.toml
new file mode 100644
index 000000000..475fb71ed
--- /dev/null
+++ b/chains/solana/contracts/programs/token-pool/Xargo.toml
@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []
diff --git a/chains/solana/contracts/programs/token-pool/src/context.rs b/chains/solana/contracts/programs/token-pool/src/context.rs
new file mode 100644
index 000000000..bc38a54e3
--- /dev/null
+++ b/chains/solana/contracts/programs/token-pool/src/context.rs
@@ -0,0 +1,275 @@
+use anchor_lang::prelude::*;
+use anchor_spl::{
+ associated_token::get_associated_token_address_with_program_id,
+ token_interface::{Mint, TokenAccount},
+};
+
+use crate::{
+ ChainConfig, Config, ExternalExecutionConfig, LockOrBurnInV1, RateLimitConfig,
+ ReleaseOrMintInV1,
+};
+
+const ANCHOR_DISCRIMINATOR: usize = 8; // 8-byte anchor discriminator length
+const CCIP_TOKENPOOL_CONFIG: &[u8] = b"ccip_tokenpool_config";
+pub const CCIP_TOKENPOOL_SIGNER: &[u8] = b"ccip_tokenpool_signer";
+pub const CCIP_TOKENPOOL_CHAINCONFIG: &[u8] = b"ccip_tokenpool_chainconfig";
+pub const RELEASE_MINT: [u8; 8] = [0x14, 0x94, 0x71, 0xc6, 0xe5, 0xaa, 0x47, 0x30];
+pub const LOCK_BURN: [u8; 8] = [0xc8, 0x0e, 0x32, 0x09, 0x2c, 0x5b, 0x79, 0x25];
+
+#[derive(Accounts)]
+pub struct InitializeTokenPool<'info> {
+ #[account(
+ init,
+ seeds = [CCIP_TOKENPOOL_CONFIG, mint.key().as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + Config::INIT_SPACE,
+ )]
+ pub config: Account<'info, Config>, // config PDA for token pool
+ pub mint: InterfaceAccount<'info, Mint>, // underlying token that the pool wraps
+ #[account(
+ init,
+ seeds = [CCIP_TOKENPOOL_SIGNER, mint.key().as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + ExternalExecutionConfig::INIT_SPACE,
+ )]
+ pub pool_signer: Account<'info, ExternalExecutionConfig>, // PDA for managing tokens
+ #[account(
+ mut,
+ address = mint.mint_authority.unwrap() @ CcipTokenPoolError::InvalidInitPoolPermissions, // TODO - what if mint_authority is 0-address (no more minting)
+ )]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct SetConfig<'info> {
+ #[account(
+ mut,
+ seeds = [CCIP_TOKENPOOL_CONFIG, config.mint.key().as_ref()],
+ bump,
+ )]
+ pub config: Account<'info, Config>,
+ #[account(address = config.owner @ CcipTokenPoolError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct AcceptOwnership<'info> {
+ #[account(
+ mut,
+ seeds = [CCIP_TOKENPOOL_CONFIG, config.mint.key().as_ref()],
+ bump,
+ )]
+ pub config: Account<'info, Config>,
+ #[account(address = config.proposed_owner @ CcipTokenPoolError::Unauthorized)]
+ pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(release_or_mint: ReleaseOrMintInV1)]
+pub struct TokenOfframp<'info> {
+ // CCIP accounts ------------------------
+ #[account(address = config.ramp_authority @ CcipTokenPoolError::InvalidPoolCaller)]
+ pub authority: Signer<'info>,
+
+ // Token pool accounts ------------------
+ // consistent set + token pool program
+ #[account(
+ mut,
+ seeds = [CCIP_TOKENPOOL_CONFIG, config.mint.key().as_ref()],
+ bump,
+ )]
+ pub config: Account<'info, Config>,
+ #[account(address = *mint.to_account_info().owner)]
+ /// CHECK: CPI to token program
+ pub token_program: AccountInfo<'info>,
+ #[account(mut)]
+ pub mint: InterfaceAccount<'info, Mint>,
+ #[account(
+ seeds = [CCIP_TOKENPOOL_SIGNER, config.mint.key().as_ref()],
+ bump,
+ )]
+ pub pool_signer: Account<'info, ExternalExecutionConfig>,
+ #[account(mut, address = get_associated_token_address_with_program_id(&pool_signer.key(), &mint.key(), &token_program.key()))]
+ pub pool_token_account: InterfaceAccount<'info, TokenAccount>,
+ #[account(
+ mut,
+ seeds = [CCIP_TOKENPOOL_CHAINCONFIG, release_or_mint.remote_chain_selector.to_le_bytes().as_ref(), mint.key().as_ref()],
+ bump,
+ )]
+ pub chain_config: Account<'info, ChainConfig>,
+
+ // User specific accounts ---------------
+ #[account(mut, address = get_associated_token_address_with_program_id(&release_or_mint.receiver, &mint.key(), &token_program.key()))]
+ pub receiver_token_account: InterfaceAccount<'info, TokenAccount>,
+ // remaining accounts -----------------
+ // LockAndRelease: []
+ // BurnAndMint: []
+ // Wrapped: [wrapped program, ..remaining_accounts]
+}
+
+#[derive(Accounts)]
+#[instruction(lock_or_burn: LockOrBurnInV1)]
+pub struct TokenOnramp<'info> {
+ // CCIP accounts ------------------------
+ #[account(address = config.ramp_authority @ CcipTokenPoolError::InvalidPoolCaller)]
+ pub authority: Signer<'info>,
+
+ // Token pool accounts ------------------
+ // consistent set + token pool program
+ #[account(
+ mut,
+ seeds = [CCIP_TOKENPOOL_CONFIG, config.mint.key().as_ref()],
+ bump,
+ )]
+ pub config: Account<'info, Config>,
+ #[account(address = *mint.to_account_info().owner)]
+ /// CHECK: CPI to underlying token program
+ pub token_program: AccountInfo<'info>,
+ #[account(mut)]
+ pub mint: InterfaceAccount<'info, Mint>,
+ #[account(
+ seeds = [CCIP_TOKENPOOL_SIGNER, config.mint.key().as_ref()],
+ bump,
+ )]
+ pub pool_signer: Account<'info, ExternalExecutionConfig>,
+ #[account(mut, address = get_associated_token_address_with_program_id(&pool_signer.key(), &mint.key(), &token_program.key()))]
+ pub pool_token_account: InterfaceAccount<'info, TokenAccount>,
+ #[account(
+ mut,
+ seeds = [CCIP_TOKENPOOL_CHAINCONFIG, lock_or_burn.remote_chain_selector.to_le_bytes().as_ref(), mint.key().as_ref()],
+ bump,
+ )]
+ pub chain_config: Account<'info, ChainConfig>,
+ // remaining accounts -----------------
+ // LockAndRelease: []
+ // BurnAndMint: []
+ // Wrapped: [wrapped program, ..remaining_accounts]
+}
+
+#[derive(Accounts)]
+#[instruction(remote_chain_selector: u64, mint: Pubkey)]
+pub struct SetChainConfig<'info> {
+ #[account(
+ seeds = [CCIP_TOKENPOOL_CONFIG, config.mint.key().as_ref()],
+ bump,
+ )]
+ pub config: Account<'info, Config>,
+ #[account(
+ init_if_needed,
+ seeds = [CCIP_TOKENPOOL_CHAINCONFIG, remote_chain_selector.to_le_bytes().as_ref(), mint.key().as_ref()],
+ bump,
+ payer = authority,
+ space = ANCHOR_DISCRIMINATOR + ChainConfig::INIT_SPACE,
+ )]
+ pub chain_config: Account<'info, ChainConfig>,
+ #[account(mut, address = config.owner)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+#[instruction(remote_chain_selector: u64, mint: Pubkey)]
+pub struct DeleteChainConfig<'info> {
+ #[account(
+ seeds = [CCIP_TOKENPOOL_CONFIG, config.mint.key().as_ref()],
+ bump,
+ )]
+ pub config: Account<'info, Config>,
+ #[account(
+ mut,
+ seeds = [CCIP_TOKENPOOL_CHAINCONFIG, remote_chain_selector.to_le_bytes().as_ref(), mint.key().as_ref()],
+ bump,
+ close = authority,
+ )]
+ pub chain_config: Account<'info, ChainConfig>,
+ #[account(mut, address = config.owner)]
+ pub authority: Signer<'info>,
+ pub system_program: Program<'info, System>,
+}
+
+#[error_code]
+pub enum CcipTokenPoolError {
+ #[msg("Pool authority does not match token mint owner")]
+ InvalidInitPoolPermissions,
+ #[msg("Unauthorized")]
+ Unauthorized,
+ #[msg("Invalid inputs")]
+ InvalidInputs,
+ #[msg("Caller is not ramp on router")]
+ InvalidPoolCaller,
+ #[msg("Invalid source pool address")]
+ InvalidSourcePoolAddress,
+ #[msg("Invalid token")]
+ InvalidToken,
+ #[msg("Invalid token amount conversion")]
+ InvalidTokenAmountConversion,
+
+ // Rate limit errors
+ #[msg("RateLimit: bucket overfilled")]
+ RLBucketOverfilled,
+ #[msg("RateLimit: max capacity exceeded")]
+ RLMaxCapacityExceeded,
+ #[msg("RateLimit: rate limit reached")]
+ RLRateLimitReached,
+ #[msg("RateLimit: invalid rate limit rate")]
+ RLInvalidRateLimitRate,
+ #[msg("RateLimit: disabled non-zero rate limit")]
+ RLDisabledNonZeroRateLimit,
+}
+
+#[event]
+pub struct Burned {
+ pub sender: Pubkey,
+ pub amount: u64,
+}
+
+#[event]
+pub struct Minted {
+ pub sender: Pubkey,
+ pub recipient: Pubkey,
+ pub amount: u64,
+}
+
+#[event]
+pub struct Locked {
+ pub sender: Pubkey,
+ pub amount: u64,
+}
+
+#[event]
+pub struct Released {
+ pub sender: Pubkey,
+ pub recipient: Pubkey,
+ pub amount: u64,
+}
+
+// note: configuration events are slightly different than EVM chains because configuration follows different steps
+#[event]
+pub struct RemoteChainConfigured {
+ pub chain_selector: u64,
+ pub token: Vec,
+ pub previous_token: Vec,
+ pub pool_address: Vec,
+ pub previous_pool_address: Vec,
+}
+
+#[event]
+pub struct RateLimitConfigured {
+ pub chain_selector: u64,
+ pub outbound_rate_limit: RateLimitConfig,
+ pub inbound_rate_limit: RateLimitConfig,
+}
+
+#[event]
+pub struct RemoteChainRemoved {
+ pub chain_selector: u64,
+}
+
+#[event]
+pub struct RouterUpdated {
+ pub old_authority: Pubkey,
+ pub new_authority: Pubkey,
+}
diff --git a/chains/solana/contracts/programs/token-pool/src/lib.rs b/chains/solana/contracts/programs/token-pool/src/lib.rs
new file mode 100644
index 000000000..b4e0ae1be
--- /dev/null
+++ b/chains/solana/contracts/programs/token-pool/src/lib.rs
@@ -0,0 +1,609 @@
+use anchor_lang::prelude::*;
+use spl_math::uint::U256;
+
+declare_id!("GRvFSLwR7szpjgNEZbGe4HtxfJYXqySXuuRUAJDpu4WH");
+
+mod context;
+use crate::context::*;
+
+mod rate_limiter;
+use crate::rate_limiter::*;
+
+#[program]
+pub mod token_pool {
+ use anchor_lang::solana_program::{instruction::Instruction, program::invoke_signed};
+ use anchor_spl::{
+ associated_token::get_associated_token_address_with_program_id,
+ token_2022::spl_token_2022::{
+ self,
+ instruction::{burn, mint_to, transfer_checked},
+ solana_zk_token_sdk::curve25519::scalar::Zeroable,
+ },
+ };
+
+ use super::*;
+
+ pub fn initialize(
+ ctx: Context,
+ pool_type: PoolType,
+ ramp_authority: Pubkey,
+ ) -> Result<()> {
+ let token_info = ctx.accounts.mint.to_account_info();
+
+ let config = &mut ctx.accounts.config;
+ config.pool_type = pool_type;
+ config.token_program = *token_info.owner;
+ config.mint = ctx.accounts.mint.key();
+ config.decimals = ctx.accounts.mint.decimals;
+ config.pool_signer = ctx.accounts.pool_signer.key();
+ config.pool_token_account = get_associated_token_address_with_program_id(
+ &config.pool_signer,
+ &config.mint,
+ &config.token_program,
+ );
+ config.owner = ctx.accounts.authority.key();
+ config.ramp_authority = ramp_authority;
+
+ Ok(())
+ }
+
+ pub fn transfer_ownership(ctx: Context, proposed_owner: Pubkey) -> Result<()> {
+ let config = &mut ctx.accounts.config;
+ require!(
+ proposed_owner != config.owner && proposed_owner != Pubkey::zeroed(),
+ CcipTokenPoolError::InvalidInputs
+ );
+ config.proposed_owner = proposed_owner;
+ Ok(())
+ }
+
+ // shared func signature with other programs
+ pub fn accept_ownership(ctx: Context) -> Result<()> {
+ let config = &mut ctx.accounts.config;
+ config.owner = std::mem::take(&mut config.proposed_owner);
+ config.proposed_owner = Pubkey::new_from_array([0; 32]);
+ Ok(())
+ }
+
+ // set_ramp_authority changes the expected signer for mint/release + burn/lock method calls
+ // this is used to update the router address
+ pub fn set_ramp_authority(ctx: Context, new_authority: Pubkey) -> Result<()> {
+ require!(
+ new_authority != Pubkey::zeroed(),
+ CcipTokenPoolError::InvalidInputs
+ );
+
+ let old_authority = ctx.accounts.config.ramp_authority;
+ ctx.accounts.config.ramp_authority = new_authority;
+ emit!(RouterUpdated {
+ old_authority,
+ new_authority
+ });
+ Ok(())
+ }
+
+ // set remote config
+ pub fn set_chain_remote_config(
+ ctx: Context,
+ remote_chain_selector: u64,
+ _mint: Pubkey,
+ cfg: RemoteConfig,
+ ) -> Result<()> {
+ let old_mint = ctx.accounts.chain_config.remote.token_address.clone();
+ let old_pool = ctx.accounts.chain_config.remote.pool_address.clone();
+
+ ctx.accounts.chain_config.remote = cfg;
+
+ emit!(RemoteChainConfigured {
+ chain_selector: remote_chain_selector,
+ token: ctx.accounts.chain_config.remote.token_address.clone(),
+ previous_token: old_mint,
+ pool_address: ctx.accounts.chain_config.remote.pool_address.clone(),
+ previous_pool_address: old_pool,
+ });
+
+ Ok(())
+ }
+
+ // set rate limit
+ pub fn set_chain_rate_limit(
+ ctx: Context,
+ remote_chain_selector: u64,
+ _mint: Pubkey,
+ inbound: RateLimitConfig,
+ outbound: RateLimitConfig,
+ ) -> Result<()> {
+ validate_token_bucket_config(&inbound)?;
+ validate_token_bucket_config(&outbound)?;
+
+ ctx.accounts.chain_config.inbound_rate_limit.cfg = inbound.clone();
+ ctx.accounts.chain_config.outbound_rate_limit.cfg = outbound.clone();
+
+ emit!(RateLimitConfigured {
+ chain_selector: remote_chain_selector,
+ outbound_rate_limit: outbound,
+ inbound_rate_limit: inbound,
+ });
+
+ Ok(())
+ }
+
+ // delete chain config
+ pub fn delete_chain_config(
+ _ctx: Context,
+ remote_chain_selector: u64,
+ _mint: Pubkey,
+ ) -> Result<()> {
+ emit!(RemoteChainRemoved {
+ chain_selector: remote_chain_selector,
+ });
+ Ok(())
+ }
+
+ pub fn release_or_mint_tokens<'info>(
+ ctx: Context<'_, '_, '_, 'info, TokenOfframp<'info>>,
+ release_or_mint: ReleaseOrMintInV1,
+ ) -> Result {
+ let parsed_amount = to_solana_token_amount(
+ release_or_mint.amount,
+ ctx.accounts.chain_config.remote.decimals,
+ ctx.accounts.config.decimals,
+ )?;
+
+ validate_release_or_mint(
+ &release_or_mint,
+ parsed_amount,
+ ctx.accounts.config.mint,
+ &ctx.accounts.chain_config.remote.pool_address.clone(),
+ &mut ctx.accounts.chain_config.inbound_rate_limit,
+ )?;
+
+ match ctx.accounts.config.pool_type {
+ PoolType::LockAndRelease => {
+ // transfer from pool -> receiver
+ // https://docs.rs/spl-token-2022/latest/spl_token_2022/instruction/fn.transfer.html
+ let mut ix = transfer_checked(
+ &spl_token_2022::ID, // use spl-token-2022 to compile instruction - change program later
+ &ctx.accounts.pool_token_account.key(),
+ &ctx.accounts.mint.key(),
+ &ctx.accounts.receiver_token_account.key(),
+ &ctx.accounts.pool_signer.key(),
+ &[],
+ parsed_amount,
+ ctx.accounts.mint.decimals,
+ )?;
+ ix.program_id = ctx.accounts.token_program.key(); // set to user specified program
+
+ let seeds = &[
+ CCIP_TOKENPOOL_SIGNER,
+ &ctx.accounts.mint.key().to_bytes(),
+ &[ctx.bumps.pool_signer],
+ ];
+ invoke_signed(
+ &ix,
+ &[
+ ctx.accounts.pool_token_account.to_account_info(),
+ ctx.accounts.mint.to_account_info(),
+ ctx.accounts.receiver_token_account.to_account_info(),
+ ctx.accounts.pool_signer.to_account_info(),
+ ],
+ &[&seeds[..]],
+ )?;
+
+ emit!(Released {
+ sender: ctx.accounts.pool_signer.key(),
+ recipient: release_or_mint.receiver,
+ amount: parsed_amount,
+ })
+ }
+ PoolType::BurnAndMint => {
+ // mint to receiver
+ // https://docs.rs/spl-token-2022/latest/spl_token_2022/instruction/fn.mint_to.html
+ let mut ix = mint_to(
+ &spl_token_2022::ID, // use spl-token-2022 to compile instruction - change program later
+ &ctx.accounts.mint.key(),
+ &ctx.accounts.receiver_token_account.key(),
+ &ctx.accounts.pool_signer.key(),
+ &[],
+ parsed_amount,
+ )?;
+ ix.program_id = ctx.accounts.token_program.key(); // set to user specified program
+
+ let seeds = &[
+ CCIP_TOKENPOOL_SIGNER,
+ &ctx.accounts.mint.key().to_bytes(),
+ &[ctx.bumps.pool_signer],
+ ];
+ invoke_signed(
+ &ix,
+ &[
+ ctx.accounts.receiver_token_account.to_account_info(),
+ ctx.accounts.mint.to_account_info(),
+ ctx.accounts.pool_signer.to_account_info(),
+ ],
+ &[&seeds[..]],
+ )?;
+
+ emit!(Minted {
+ sender: ctx.accounts.pool_signer.key(),
+ recipient: release_or_mint.receiver,
+ amount: parsed_amount,
+ })
+ }
+ PoolType::Wrapped => {
+ // The External Execution Config Account is used to sign the CPI instruction
+ let signer = &ctx.accounts.pool_signer;
+ // first remaining accounts is the wrapped program
+ require!(
+ !ctx.remaining_accounts.is_empty(),
+ CcipTokenPoolError::InvalidInputs
+ );
+ let (wrapped_program, remaining_accounts) =
+ ctx.remaining_accounts.split_first().unwrap();
+
+ // The accounts of the user that will be used in the CPI instruction, none of them are signers
+ // They need to specify if mutable or not, but none of them is allowed to init, realloc or close
+ // note: CPI signer is always first account
+ let mut acc_infos = signer.to_account_infos();
+ acc_infos.extend_from_slice(&[
+ ctx.accounts.pool_token_account.to_account_info(),
+ ctx.accounts.mint.to_account_info(),
+ ctx.accounts.token_program.to_account_info(),
+ ctx.accounts.receiver_token_account.to_account_info(),
+ ]);
+ acc_infos.extend_from_slice(remaining_accounts);
+
+ let acc_metas: Vec = acc_infos
+ .to_vec()
+ .iter()
+ .flat_map(|acc_info| {
+ // Check signer from PDA External Execution config
+ let is_signer = acc_info.key() == signer.key();
+ acc_info.to_account_metas(Some(is_signer))
+ })
+ .collect();
+
+ let mut data = RELEASE_MINT.to_vec();
+ data.extend_from_slice(&release_or_mint.try_to_vec()?);
+ let ix = Instruction {
+ program_id: wrapped_program.key(),
+ accounts: acc_metas,
+ data,
+ };
+
+ let seeds = &[
+ CCIP_TOKENPOOL_SIGNER,
+ &ctx.accounts.mint.key().to_bytes(),
+ &[ctx.bumps.pool_signer],
+ ];
+ invoke_signed(&ix, &acc_infos, &[&seeds[..]])?;
+ }
+ };
+
+ Ok(ReleaseOrMintOutV1 {
+ destination_amount: parsed_amount,
+ })
+ }
+
+ pub fn lock_or_burn_tokens<'info>(
+ ctx: Context<'_, '_, '_, 'info, TokenOnramp<'info>>,
+ lock_or_burn: LockOrBurnInV1,
+ ) -> Result {
+ validate_lock_or_burn(
+ &lock_or_burn,
+ ctx.accounts.config.mint,
+ &mut ctx.accounts.chain_config.outbound_rate_limit,
+ )?;
+
+ match ctx.accounts.config.pool_type {
+ PoolType::LockAndRelease => {
+ // receiver -> token pool (occurs outside pool)
+ // hold tokens
+ emit!(Locked {
+ sender: ctx.accounts.authority.key(),
+ amount: lock_or_burn.amount,
+ });
+ }
+ PoolType::BurnAndMint => {
+ // receiver -> token pool (occurs outside pool)
+ // burn tokens held in pool
+ // https://docs.rs/spl-token-2022/latest/spl_token_2022/instruction/fn.burn.html
+ let mut ix = burn(
+ &spl_token_2022::ID, // use spl-token-2022 to compile instruction - change program later
+ &ctx.accounts.pool_token_account.key(),
+ &ctx.accounts.mint.key(),
+ &ctx.accounts.pool_signer.key(),
+ &[],
+ lock_or_burn.amount,
+ )?;
+ ix.program_id = ctx.accounts.token_program.key(); // set to user specified program
+
+ let seeds = &[
+ CCIP_TOKENPOOL_SIGNER,
+ &ctx.accounts.mint.key().to_bytes(),
+ &[ctx.bumps.pool_signer],
+ ];
+ invoke_signed(
+ &ix,
+ &[
+ ctx.accounts.pool_token_account.to_account_info(),
+ ctx.accounts.mint.to_account_info(),
+ ctx.accounts.pool_signer.to_account_info(),
+ ],
+ &[&seeds[..]],
+ )?;
+
+ emit!(Burned {
+ sender: ctx.accounts.authority.key(),
+ amount: lock_or_burn.amount,
+ })
+ }
+ PoolType::Wrapped => {
+ // The External Execution Config Account is used to sign the CPI instruction
+ let signer = &ctx.accounts.pool_signer;
+ // first remaining accounts is the wrapped program
+ require!(
+ !ctx.remaining_accounts.is_empty(),
+ CcipTokenPoolError::InvalidInputs
+ );
+ let (wrapped_program, remaining_accounts) =
+ ctx.remaining_accounts.split_first().unwrap();
+
+ // The accounts of the user that will be used in the CPI instruction, none of them are signers
+ // They need to specify if mutable or not, but none of them is allowed to init, realloc or close
+ // note: CPI signer is always first account
+ let mut acc_infos = signer.to_account_infos();
+ acc_infos.extend_from_slice(&[
+ ctx.accounts.pool_token_account.to_account_info(),
+ ctx.accounts.mint.to_account_info(),
+ ctx.accounts.token_program.to_account_info(),
+ ]);
+ acc_infos.extend_from_slice(remaining_accounts);
+
+ let acc_metas: Vec = acc_infos
+ .to_vec()
+ .iter()
+ .flat_map(|acc_info| {
+ // Check signer from PDA External Execution config
+ let is_signer = acc_info.key() == signer.key();
+ acc_info.to_account_metas(Some(is_signer))
+ })
+ .collect();
+
+ let mut data = LOCK_BURN.to_vec();
+ data.extend_from_slice(&lock_or_burn.try_to_vec()?);
+ let ix = Instruction {
+ program_id: wrapped_program.key(),
+ accounts: acc_metas,
+ data,
+ };
+
+ let seeds = &[
+ CCIP_TOKENPOOL_SIGNER,
+ &ctx.accounts.mint.key().to_bytes(),
+ &[ctx.bumps.pool_signer],
+ ];
+ invoke_signed(&ix, &acc_infos, &[&seeds[..]])?;
+ }
+ };
+
+ Ok(LockOrBurnOutV1 {
+ dest_token_address: ctx.accounts.chain_config.remote.token_address.clone(),
+ dest_pool_data: [0_u8; 0].to_vec(), // empty
+ })
+ }
+}
+
+// validate_lock_or_burn checks for correctness on inputs
+// - token & pool is correct for chain
+// - rate limiting
+fn validate_lock_or_burn(
+ lock_or_burn_in: &LockOrBurnInV1,
+ config_mint: Pubkey,
+ outbound_rate_limit: &mut RateLimitTokenBucket,
+) -> Result<()> {
+ // validate token matches configured pool token
+ require!(
+ config_mint == lock_or_burn_in.local_token,
+ CcipTokenPoolError::InvalidToken
+ );
+
+ outbound_rate_limit.consume(lock_or_burn_in.amount)
+}
+
+// validate_lock_or_burn checks for correctness on inputs
+// - token & pool is correct for chain
+// - rate limiting
+fn validate_release_or_mint(
+ release_or_mint_in: &ReleaseOrMintInV1,
+ parsed_amount: u64,
+ config_mint: Pubkey,
+ pool_address: &[u8],
+ inbound_rate_limit: &mut RateLimitTokenBucket,
+) -> Result<()> {
+ require!(
+ config_mint == release_or_mint_in.local_token,
+ CcipTokenPoolError::InvalidToken
+ );
+ require!(
+ !pool_address.is_empty() && pool_address == release_or_mint_in.source_pool_address,
+ CcipTokenPoolError::InvalidSourcePoolAddress
+ );
+
+ inbound_rate_limit.consume(parsed_amount)
+}
+
+#[account]
+#[derive(InitSpace)]
+pub struct Config {
+ pub version: u8,
+ pub pool_type: PoolType,
+
+ // token config
+ pub token_program: Pubkey,
+ pub mint: Pubkey,
+ pub decimals: u8,
+ pub pool_signer: Pubkey, // PDA for token ownership
+ pub pool_token_account: Pubkey, // associated token account for pool_signer + token
+
+ // ownership
+ pub owner: Pubkey,
+ pub proposed_owner: Pubkey,
+ ramp_authority: Pubkey, // signer for CCIP calls
+}
+
+#[account]
+#[derive(InitSpace)]
+pub struct ExternalExecutionConfig {}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct LockOrBurnInV1 {
+ pub receiver: Vec, // The recipient of the tokens on the destination chain
+ pub remote_chain_selector: u64, // The chain ID of the destination chain
+ pub original_sender: Pubkey, // The original sender of the tx on the source chain
+ pub amount: u64, // local solana amount to lock/burn, The amount of tokens to lock or burn, denominated in the source token's decimals
+ pub local_token: Pubkey, // The address on this chain of the token to lock or burn
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct LockOrBurnOutV1 {
+ pub dest_token_address: Vec,
+ pub dest_pool_data: Vec,
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct ReleaseOrMintInV1 {
+ pub original_sender: Vec, // The original sender of the tx on the source chain
+ pub remote_chain_selector: u64, // ─╮ The chain ID of the source chain
+ pub receiver: Pubkey, // ───────────╯ The recipient of the tokens on the destination chain.
+ pub amount: [u8; 32], // u256, incoming cross-chain amount - The amount of tokens to release or mint, denominated in the source token's decimals
+ pub local_token: Pubkey, // The address on this chain of the token to release or mint
+ /// @dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the
+ /// expected pool address for the given remoteChainSelector.
+ pub source_pool_address: Vec, // The address of the source pool, abi encoded in the case of EVM chains
+ pub source_pool_data: Vec, // The data received from the source pool to process the release or mint
+ /// @dev WARNING: offchainTokenData is untrusted data.
+ pub offchain_token_data: Vec, // The offchain data to process the release or mint
+}
+
+#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct ReleaseOrMintOutV1 {
+ // The number of tokens released or minted on the destination chain, denominated in the local token's decimals.
+ // This value is expected to be equal to the ReleaseOrMintInV1.amount in the case where the source and destination
+ // chain have the same number of decimals.
+ pub destination_amount: u64, // token amounts local to solana
+}
+
+#[repr(u8)]
+#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize)]
+pub enum PoolType {
+ LockAndRelease,
+ BurnAndMint,
+ Wrapped, // wrap forwards the CPI call to an arbitrary program, assumes CPI call will handle programs pass to the pool
+}
+
+#[account]
+#[derive(InitSpace)]
+pub struct ChainConfig {
+ pub remote: RemoteConfig,
+ pub inbound_rate_limit: RateLimitTokenBucket,
+ pub outbound_rate_limit: RateLimitTokenBucket,
+}
+
+#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct RemoteConfig {
+ #[max_len(64)]
+ pub pool_address: Vec,
+ #[max_len(64)]
+ pub token_address: Vec,
+ pub decimals: u8, // needed to track decimals from remote to convert properly
+}
+
+pub fn to_solana_token_amount(
+ incoming_amount_bytes: [u8; 32], // LE encoded u256
+ incoming_decimal: u8,
+ local_decimal: u8,
+) -> Result {
+ let mut incoming_amount = U256::from_little_endian(&incoming_amount_bytes);
+
+ // handle differences in decimals by multipling/dividing by 10^N
+ match incoming_decimal.cmp(&local_decimal) {
+ std::cmp::Ordering::Less => {
+ incoming_amount = incoming_amount
+ .checked_mul(U256::exp10((local_decimal - incoming_decimal) as usize))
+ .ok_or(CcipTokenPoolError::InvalidTokenAmountConversion)?;
+ }
+ std::cmp::Ordering::Equal => {
+ // nothing needed if decimals are equal
+ }
+ std::cmp::Ordering::Greater => {
+ incoming_amount = incoming_amount
+ .checked_div(U256::exp10((incoming_decimal - local_decimal) as usize))
+ .ok_or(CcipTokenPoolError::InvalidTokenAmountConversion)?;
+ }
+ }
+
+ // check to prevent panic
+ require!(
+ incoming_amount <= U256::from(u64::MAX),
+ CcipTokenPoolError::InvalidTokenAmountConversion
+ );
+ Ok(incoming_amount.as_u64())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ const BASE_VALUE: u64 = 1_000_000_000; // 1e9
+
+ #[test]
+ fn test_larger_incoming_decimal() {
+ let mut u256_bytes = [0u8; 32];
+ U256::from(BASE_VALUE * BASE_VALUE).to_little_endian(&mut u256_bytes);
+ let local_val = to_solana_token_amount(u256_bytes, 18, 9).unwrap();
+ assert!(local_val == BASE_VALUE);
+ }
+
+ #[test]
+ fn test_smaller_incoming_decimal() {
+ let mut u256_bytes = [0u8; 32];
+ U256::from(BASE_VALUE).to_little_endian(&mut u256_bytes);
+ let local_val = to_solana_token_amount(u256_bytes, 9, 18).unwrap();
+ assert!(local_val == BASE_VALUE * BASE_VALUE);
+ }
+
+ #[test]
+ fn test_equal_incoming_decimal() {
+ let mut u256_bytes = [0u8; 32];
+ U256::from(BASE_VALUE).to_little_endian(&mut u256_bytes);
+ let local_val = to_solana_token_amount(u256_bytes, 9, 9).unwrap();
+ assert!(local_val == BASE_VALUE);
+ }
+
+ #[test]
+ fn test_u256_overflow() {
+ let mut u256_bytes = [0u8; 32];
+ U256::from(BASE_VALUE * BASE_VALUE).to_little_endian(&mut u256_bytes);
+ let res = to_solana_token_amount(u256_bytes, 0, 18);
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn test_u256_divide_to_zero() {
+ let mut u256_bytes = [0u8; 32];
+ U256::from(BASE_VALUE).to_little_endian(&mut u256_bytes);
+ let local_val = to_solana_token_amount(u256_bytes, 18, 0).unwrap();
+ assert!(local_val == 0);
+ }
+
+ #[test]
+ fn test_u64_overflow() {
+ let mut u256_bytes = [0u8; 32];
+ U256::from(u64::MAX)
+ .checked_add(U256::from(1))
+ .unwrap()
+ .to_little_endian(&mut u256_bytes);
+ let res = to_solana_token_amount(u256_bytes, 0, 0);
+ assert!(res.is_err());
+ }
+}
diff --git a/chains/solana/contracts/programs/token-pool/src/rate_limiter.rs b/chains/solana/contracts/programs/token-pool/src/rate_limiter.rs
new file mode 100644
index 000000000..69d627581
--- /dev/null
+++ b/chains/solana/contracts/programs/token-pool/src/rate_limiter.rs
@@ -0,0 +1,131 @@
+use std::cmp::min;
+
+use anchor_lang::prelude::*;
+use solana_program::log::sol_log;
+
+use crate::CcipTokenPoolError;
+
+// NOTE: spl-token or spl-token-2022 use u64 for amounts
+
+#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct RateLimitTokenBucket {
+ pub tokens: u64, // Current number of tokens that are in the bucket. Represents how many tokens a single transaction can consume in a single transaction - different than total number of tokens within a pool
+ pub last_updated: u64, // Timestamp in seconds of the last token refill, good for 100+ years.
+ pub cfg: RateLimitConfig,
+}
+
+#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize)]
+pub struct RateLimitConfig {
+ pub enabled: bool, // Indication whether the rate limiting is enabled or not
+ pub capacity: u64, // Maximum number of tokens that can be in the bucket.
+ pub rate: u64, // Number of tokens per second that the bucket is refilled.
+}
+
+impl RateLimitTokenBucket {
+ // consume calculates the new amount of tokens in a bucket (up to the capacity)
+ // and validates that the bucket has enough to fulfill the request
+ pub fn consume(&mut self, request_tokens: u64) -> Result<()> {
+ if !self.cfg.enabled || request_tokens == 0 {
+ return Ok(());
+ }
+
+ let clock: Clock = Clock::get()?;
+ let current_timestamp = clock.unix_timestamp as u64; // positive part of i64 will always fit in u64
+
+ let mut tokens = self.tokens;
+ let capacity = self.cfg.capacity;
+ let time_diff = current_timestamp.checked_sub(self.last_updated).unwrap();
+
+ if time_diff != 0 {
+ // this should never happen - requires manual resetting of config if this occurs
+ require!(tokens <= capacity, CcipTokenPoolError::RLBucketOverfilled);
+
+ // Refill tokens when arriving at a new block time
+ tokens = self.calculate_refill(capacity, tokens, time_diff, self.cfg.rate);
+
+ self.last_updated = current_timestamp;
+ }
+
+ require!(
+ capacity >= request_tokens,
+ CcipTokenPoolError::RLMaxCapacityExceeded,
+ );
+
+ if tokens < request_tokens {
+ let rate = self.cfg.rate;
+ // Wait required until the bucket is refilled enough to accept this value, round up to next higher second
+ // Consume is not guaranteed to succeed after wait time passes if there is competing traffic.
+ // This acts as a lower bound of wait time.
+ let min_wait_sec = (request_tokens.checked_sub(tokens).unwrap()
+ + rate.checked_sub(1).unwrap())
+ .checked_div(self.cfg.rate)
+ .unwrap();
+
+ // print human readable message before reverting with total wait time
+ let msg = "min wait (s): ".to_owned() + &min_wait_sec.to_string();
+ sol_log(&msg);
+ }
+ require!(
+ tokens >= request_tokens,
+ CcipTokenPoolError::RLRateLimitReached
+ );
+
+ self.tokens = tokens.checked_sub(request_tokens).unwrap();
+ emit!(TokensConsumed {
+ tokens: request_tokens
+ });
+
+ Ok(())
+ }
+
+ // set_token_bucket_config sets + validates a new config and updates the number of tokens in the bucket
+ pub fn set_token_bucket_config(&mut self, config: RateLimitConfig) -> Result<()> {
+ let clock: Clock = Clock::get()?;
+ let current_timestamp = clock.unix_timestamp as u64; // positive part of i64 will always fit in u64
+ let time_diff = current_timestamp.checked_sub(self.last_updated).unwrap();
+ if time_diff != 0 {
+ // Refill tokens when arriving at a new block time
+ self.tokens =
+ self.calculate_refill(self.cfg.capacity, self.tokens, time_diff, self.cfg.rate);
+
+ self.last_updated = current_timestamp;
+ }
+
+ self.tokens = min(config.capacity, self.tokens);
+ self.cfg = config.clone();
+
+ emit!(ConfigChanged { config });
+
+ Ok(())
+ }
+
+ // calculate_refill returns either the maximum capacity of the bucket or the current + refill amount
+ fn calculate_refill(&self, capacity: u64, tokens: u64, time_diff: u64, rate: u64) -> u64 {
+ min(capacity, tokens + time_diff * rate)
+ }
+}
+
+pub fn validate_token_bucket_config(cfg: &RateLimitConfig) -> Result<()> {
+ if cfg.enabled {
+ require!(
+ cfg.rate < cfg.capacity && cfg.rate != 0,
+ CcipTokenPoolError::RLInvalidRateLimitRate
+ );
+ } else {
+ require!(
+ cfg.rate == 0 && cfg.capacity == 0,
+ CcipTokenPoolError::RLDisabledNonZeroRateLimit
+ );
+ }
+ Ok(())
+}
+
+#[event]
+pub struct TokensConsumed {
+ pub tokens: u64,
+}
+
+#[event]
+pub struct ConfigChanged {
+ pub config: RateLimitConfig,
+}
diff --git a/chains/solana/contracts/target/idl/access_controller.json b/chains/solana/contracts/target/idl/access_controller.json
new file mode 100644
index 000000000..6b8dad08d
--- /dev/null
+++ b/chains/solana/contracts/target/idl/access_controller.json
@@ -0,0 +1,174 @@
+{
+ "version": "1.0.1",
+ "name": "access_controller",
+ "constants": [
+ {
+ "name": "MAX_ADDRS",
+ "type": {
+ "defined": "usize"
+ },
+ "value": "64"
+ }
+ ],
+ "instructions": [
+ {
+ "name": "initialize",
+ "accounts": [
+ {
+ "name": "state",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "owner",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "transferOwnership",
+ "accounts": [
+ {
+ "name": "state",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "acceptOwnership",
+ "accounts": [
+ {
+ "name": "state",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "addAccess",
+ "accounts": [
+ {
+ "name": "state",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "owner",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "address",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "removeAccess",
+ "accounts": [
+ {
+ "name": "state",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "owner",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "address",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": []
+ }
+ ],
+ "accounts": [
+ {
+ "name": "AccessController",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "owner",
+ "type": "publicKey"
+ },
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ },
+ {
+ "name": "accessList",
+ "type": {
+ "defined": "AccessList"
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "types": [
+ {
+ "name": "AccessList",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "xs",
+ "type": {
+ "array": [
+ "publicKey",
+ 64
+ ]
+ }
+ },
+ {
+ "name": "len",
+ "type": "u64"
+ }
+ ]
+ }
+ }
+ ],
+ "errors": [
+ {
+ "code": 6000,
+ "name": "Unauthorized",
+ "msg": "Unauthorized"
+ },
+ {
+ "code": 6001,
+ "name": "InvalidInput",
+ "msg": "Invalid input"
+ },
+ {
+ "code": 6002,
+ "name": "Full",
+ "msg": "Access list is full"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/chains/solana/contracts/target/idl/ccip_invalid_receiver.json b/chains/solana/contracts/target/idl/ccip_invalid_receiver.json
new file mode 100644
index 000000000..f0a1a4333
--- /dev/null
+++ b/chains/solana/contracts/target/idl/ccip_invalid_receiver.json
@@ -0,0 +1,103 @@
+{
+ "version": "0.0.0-dev",
+ "name": "ccip_invalid_receiver",
+ "instructions": [
+ {
+ "name": "ccipReceive",
+ "accounts": [
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "counter",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "message",
+ "type": {
+ "defined": "Any2SolanaMessage"
+ }
+ }
+ ]
+ }
+ ],
+ "accounts": [
+ {
+ "name": "Counter",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "value",
+ "type": "u8"
+ }
+ ]
+ }
+ }
+ ],
+ "types": [
+ {
+ "name": "Any2SolanaMessage",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "messageId",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "sourceChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "sender",
+ "type": "bytes"
+ },
+ {
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "name": "tokenAmounts",
+ "type": {
+ "vec": {
+ "defined": "SolanaTokenAmount"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "SolanaTokenAmount",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "token",
+ "type": "publicKey"
+ },
+ {
+ "name": "amount",
+ "type": "u64"
+ }
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/chains/solana/contracts/target/idl/ccip_receiver.json b/chains/solana/contracts/target/idl/ccip_receiver.json
new file mode 100644
index 000000000..5ef0bf52d
--- /dev/null
+++ b/chains/solana/contracts/target/idl/ccip_receiver.json
@@ -0,0 +1,302 @@
+{
+ "version": "0.1.0-dev",
+ "name": "ccip_receiver",
+ "docs": [
+ "This program an example of a CCIP Receiver Program.",
+ "Used to test CCIP Router execute."
+ ],
+ "instructions": [
+ {
+ "name": "initialize",
+ "docs": [
+ "The initialization is responsibility of the External User, CCIP is not handling initialization of Accounts"
+ ],
+ "accounts": [
+ {
+ "name": "counter",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "externalExecutionConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "ccipReceive",
+ "docs": [
+ "This function is called by the CCIP Router to execute the CCIP message.",
+ "The method name needs to be ccip_receive with Anchor encoding,",
+ "if not using Anchor the discriminator needs to be [0x0b, 0xf4, 0x09, 0xf9, 0x2c, 0x53, 0x2f, 0xf5]",
+ "You can send as many accounts as you need, specifying if mutable or not.",
+ "But none of them could be an init, realloc or close.",
+ "In this case, it increments the counter value by 1 and logs the parsed message."
+ ],
+ "accounts": [
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "externalExecutionConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "counter",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "message",
+ "type": {
+ "defined": "Any2SolanaMessage"
+ }
+ }
+ ]
+ },
+ {
+ "name": "ccipTokenReleaseMint",
+ "accounts": [
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "poolTokenAccount",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "mint",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "tokenProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "input",
+ "type": {
+ "defined": "ReleaseOrMintInV1"
+ }
+ }
+ ]
+ },
+ {
+ "name": "ccipTokenLockBurn",
+ "accounts": [
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "poolTokenAccount",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "mint",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "tokenProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "input",
+ "type": {
+ "defined": "LockOrBurnInV1"
+ }
+ }
+ ]
+ }
+ ],
+ "accounts": [
+ {
+ "name": "Counter",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "value",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ExternalExecutionConfig",
+ "type": {
+ "kind": "struct",
+ "fields": []
+ }
+ }
+ ],
+ "types": [
+ {
+ "name": "Any2SolanaMessage",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "messageId",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "sourceChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "sender",
+ "type": "bytes"
+ },
+ {
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "name": "tokenAmounts",
+ "type": {
+ "vec": {
+ "defined": "SolanaTokenAmount"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "SolanaTokenAmount",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "token",
+ "type": "publicKey"
+ },
+ {
+ "name": "amount",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "LockOrBurnInV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "receiver",
+ "type": "bytes"
+ },
+ {
+ "name": "remoteChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "originalSender",
+ "type": "publicKey"
+ },
+ {
+ "name": "amount",
+ "type": "u64"
+ },
+ {
+ "name": "localToken",
+ "type": "publicKey"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ReleaseOrMintInV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "originalSender",
+ "type": "bytes"
+ },
+ {
+ "name": "remoteChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "receiver",
+ "type": "publicKey"
+ },
+ {
+ "name": "amount",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "localToken",
+ "type": "publicKey"
+ },
+ {
+ "name": "sourcePoolAddress",
+ "docs": [
+ "@dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the",
+ "expected pool address for the given remoteChainSelector."
+ ],
+ "type": "bytes"
+ },
+ {
+ "name": "sourcePoolData",
+ "type": "bytes"
+ },
+ {
+ "name": "offchainTokenData",
+ "docs": [
+ "@dev WARNING: offchainTokenData is untrusted data."
+ ],
+ "type": "bytes"
+ }
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/chains/solana/contracts/target/idl/ccip_router.json b/chains/solana/contracts/target/idl/ccip_router.json
new file mode 100644
index 000000000..028e5885e
--- /dev/null
+++ b/chains/solana/contracts/target/idl/ccip_router.json
@@ -0,0 +1,3236 @@
+{
+ "version": "0.1.0-dev",
+ "name": "ccip_router",
+ "docs": [
+ "The `ccip_router` module contains the implementation of the Cross-Chain Interoperability Protocol (CCIP) Router.",
+ "",
+ "This is the Collapsed Router Program for CCIP.",
+ "As it's upgradable persisting the same program id, there is no need to have an indirection of a Proxy Program.",
+ "This Router handles both the OnRamp and OffRamp flow of the CCIP Messages."
+ ],
+ "constants": [
+ {
+ "name": "MAX_ORACLES",
+ "type": {
+ "defined": "usize"
+ },
+ "value": "16"
+ }
+ ],
+ "instructions": [
+ {
+ "name": "initialize",
+ "docs": [
+ "Initializes the CCIP Router.",
+ "",
+ "The initialization of the Router is responsibility of Admin, nothing more than calling this method should be done first.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for initialization.",
+ "* `solana_chain_selector` - The chain selector for Solana.",
+ "* `default_gas_limit` - The default gas limit for other destination chains.",
+ "* `default_allow_out_of_order_execution` - Whether out-of-order execution is allowed by default for other destination chains.",
+ "* `enable_execution_after` - The minimum amount of time required between a message has been committed and can be manually executed."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "program",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "programData",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "externalExecutionConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "tokenPoolsSigner",
+ "isMut": true,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "solanaChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "defaultGasLimit",
+ "type": "u128"
+ },
+ {
+ "name": "defaultAllowOutOfOrderExecution",
+ "type": "bool"
+ },
+ {
+ "name": "enableExecutionAfter",
+ "type": "i64"
+ }
+ ]
+ },
+ {
+ "name": "transferOwnership",
+ "docs": [
+ "Transfers the ownership of the router to a new proposed owner.",
+ "",
+ "Shared func signature with other programs",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for the transfer.",
+ "* `proposed_owner` - The public key of the new proposed owner."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "acceptOwnership",
+ "docs": [
+ "Accepts the ownership of the router by the proposed owner.",
+ "",
+ "Shared func signature with other programs",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for accepting ownership.",
+ "The new owner must be a signer of the transaction."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "addChainSelector",
+ "docs": [
+ "Adds a new chain selector to the router.",
+ "",
+ "The Admin needs to add any new chain supported (this means both OnRamp and OffRamp).",
+ "When adding a new chain, the Admin needs to specify if it's enabled or not.",
+ "They may enable only source, or only destination, or neither, or both.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for adding the chain selector.",
+ "* `new_chain_selector` - The new chain selector to be added.",
+ "* `source_chain_config` - The configuration for the chain as source.",
+ "* `dest_chain_config` - The configuration for the chain as destination."
+ ],
+ "accounts": [
+ {
+ "name": "chainState",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "newChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "sourceChainConfig",
+ "type": {
+ "defined": "SourceChainConfig"
+ }
+ },
+ {
+ "name": "destChainConfig",
+ "type": {
+ "defined": "DestChainConfig"
+ }
+ }
+ ]
+ },
+ {
+ "name": "disableSourceChainSelector",
+ "docs": [
+ "Disables the source chain selector.",
+ "",
+ "The Admin is the only one able to disable the chain selector as source. This method is thought of as an emergency kill-switch.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for disabling the chain selector.",
+ "* `source_chain_selector` - The source chain selector to be disabled."
+ ],
+ "accounts": [
+ {
+ "name": "chainState",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "sourceChainSelector",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "disableDestChainSelector",
+ "docs": [
+ "Disables the destination chain selector.",
+ "",
+ "The Admin is the only one able to disable the chain selector as destination. This method is thought of as an emergency kill-switch.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for disabling the chain selector.",
+ "* `dest_chain_selector` - The destination chain selector to be disabled."
+ ],
+ "accounts": [
+ {
+ "name": "chainState",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "destChainSelector",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "updateSourceChainConfig",
+ "docs": [
+ "Updates the configuration of the source chain selector.",
+ "",
+ "The Admin is the only one able to update the source chain config.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for updating the chain selector.",
+ "* `source_chain_selector` - The source chain selector to be updated.",
+ "* `source_chain_config` - The new configuration for the source chain."
+ ],
+ "accounts": [
+ {
+ "name": "chainState",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "sourceChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "sourceChainConfig",
+ "type": {
+ "defined": "SourceChainConfig"
+ }
+ }
+ ]
+ },
+ {
+ "name": "updateDestChainConfig",
+ "docs": [
+ "Updates the configuration of the destination chain selector.",
+ "",
+ "The Admin is the only one able to update the destination chain config.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for updating the chain selector.",
+ "* `dest_chain_selector` - The destination chain selector to be updated.",
+ "* `dest_chain_config` - The new configuration for the destination chain."
+ ],
+ "accounts": [
+ {
+ "name": "chainState",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "destChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "destChainConfig",
+ "type": {
+ "defined": "DestChainConfig"
+ }
+ }
+ ]
+ },
+ {
+ "name": "updateSolanaChainSelector",
+ "docs": [
+ "Updates the Solana chain selector in the router configuration.",
+ "",
+ "This method should only be used if there was an error with the initial configuration or if the solana chain selector changes.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for updating the configuration.",
+ "* `new_chain_selector` - The new chain selector for Solana."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "newChainSelector",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "updateDefaultGasLimit",
+ "docs": [
+ "Updates the default gas limit in the router configuration.",
+ "",
+ "This change affects the default value for gas limit on every other destination chain.",
+ "The Admin is the only one able to update the default gas limit.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for updating the configuration.",
+ "* `new_gas_limit` - The new default gas limit."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "newGasLimit",
+ "type": "u128"
+ }
+ ]
+ },
+ {
+ "name": "updateDefaultAllowOutOfOrderExecution",
+ "docs": [
+ "Updates the default setting for allowing out-of-order execution for other destination chains.",
+ "The Admin is the only one able to update this config.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for updating the configuration.",
+ "* `new_allow_out_of_order_execution` - The new setting for allowing out-of-order execution."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "newAllowOutOfOrderExecution",
+ "type": "bool"
+ }
+ ]
+ },
+ {
+ "name": "updateEnableManualExecutionAfter",
+ "docs": [
+ "Updates the minimum amount of time required between a message being committed and when it can be manually executed.",
+ "",
+ "This is part of the OffRamp Configuration for Solana.",
+ "The Admin is the only one able to update this config.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for updating the configuration.",
+ "* `new_enable_manual_execution_after` - The new minimum amount of time required."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "newEnableManualExecutionAfter",
+ "type": "i64"
+ }
+ ]
+ },
+ {
+ "name": "registerTokenAdminRegistryViaGetCcipAdmin",
+ "docs": [
+ "Registers the Token Admin Registry via the CCIP Admin",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for registration.",
+ "* `mint` - The public key of the token mint.",
+ "* `token_admin_registry_admin` - The public key of the token admin registry admin."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "tokenAdminRegistry",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "mint",
+ "type": "publicKey"
+ },
+ {
+ "name": "tokenAdminRegistryAdmin",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "registerTokenAdminRegistryViaOwner",
+ "docs": [
+ "Registers the Token Admin Registry via the token owner.",
+ "",
+ "The Authority of the Mint Token can claim the registry of the token.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for registration."
+ ],
+ "accounts": [
+ {
+ "name": "tokenAdminRegistry",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "mint",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "setPool",
+ "docs": [
+ "Sets the pool lookup table for a given token mint.",
+ "",
+ "The administrator of the token admin registry can set the pool lookup table for a given token mint.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for setting the pool.",
+ "* `mint` - The public key of the token mint.",
+ "* `pool_lookup_table` - The public key of the pool lookup table, this address will be used for validations when interacting with the pool."
+ ],
+ "accounts": [
+ {
+ "name": "tokenAdminRegistry",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "mint",
+ "type": "publicKey"
+ },
+ {
+ "name": "poolLookupTable",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "transferAdminRoleTokenAdminRegistry",
+ "docs": [
+ "Transfers the admin role of the token admin registry to a new admin.",
+ "",
+ "Only the Admin can transfer the Admin Role of the Token Admin Registry, this setups the Pending Admin and then it's their responsibility to accept the role.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for the transfer.",
+ "* `mint` - The public key of the token mint.",
+ "* `new_admin` - The public key of the new admin."
+ ],
+ "accounts": [
+ {
+ "name": "tokenAdminRegistry",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "mint",
+ "type": "publicKey"
+ },
+ {
+ "name": "newAdmin",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "acceptAdminRoleTokenAdminRegistry",
+ "docs": [
+ "Accepts the admin role of the token admin registry.",
+ "",
+ "The Pending Admin must call this function to accept the admin role of the Token Admin Registry.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for accepting the admin role.",
+ "* `mint` - The public key of the token mint."
+ ],
+ "accounts": [
+ {
+ "name": "tokenAdminRegistry",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "mint",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "setTokenBilling",
+ "docs": [
+ "Sets the token billing configuration.",
+ "",
+ "Only CCIP Admin can set the token billing configuration.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for setting the token billing configuration.",
+ "* `_chain_selector` - The chain selector.",
+ "* `_mint` - The public key of the token mint.",
+ "* `cfg` - The token billing configuration."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "perChainPerTokenConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "chainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "mint",
+ "type": "publicKey"
+ },
+ {
+ "name": "cfg",
+ "type": {
+ "defined": "TokenBilling"
+ }
+ }
+ ]
+ },
+ {
+ "name": "setOcrConfig",
+ "docs": [
+ "Sets the OCR configuration.",
+ "Only CCIP Admin can set the OCR configuration.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for setting the OCR configuration.",
+ "* `plugin_type` - The type of OCR plugin [0: Commit, 1: Execution].",
+ "* `config_info` - The OCR configuration information.",
+ "* `signers` - The list of signers.",
+ "* `transmitters` - The list of transmitters."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "pluginType",
+ "type": "u8"
+ },
+ {
+ "name": "configInfo",
+ "type": {
+ "defined": "Ocr3ConfigInfo"
+ }
+ },
+ {
+ "name": "signers",
+ "type": {
+ "vec": {
+ "array": [
+ "u8",
+ 20
+ ]
+ }
+ }
+ },
+ {
+ "name": "transmitters",
+ "type": {
+ "vec": "publicKey"
+ }
+ }
+ ]
+ },
+ {
+ "name": "addBillingTokenConfig",
+ "docs": [
+ "Adds a billing token configuration.",
+ "Only CCIP Admin can add a billing token configuration.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for adding the billing token configuration.",
+ "* `config` - The billing token configuration to be added."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "billingTokenConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "tokenProgram",
+ "isMut": false,
+ "isSigner": false,
+ "docs": [
+ "type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount",
+ "with a constraint enforcing that it is one of the two allowed programs."
+ ]
+ },
+ {
+ "name": "feeTokenMint",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "feeTokenReceiver",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "feeBillingSigner",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "associatedTokenProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "config",
+ "type": {
+ "defined": "BillingTokenConfig"
+ }
+ }
+ ]
+ },
+ {
+ "name": "updateBillingTokenConfig",
+ "docs": [
+ "Updates the billing token configuration.",
+ "Only CCIP Admin can update a billing token configuration.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for updating the billing token configuration.",
+ "* `config` - The new billing token configuration."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "billingTokenConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "config",
+ "type": {
+ "defined": "BillingTokenConfig"
+ }
+ }
+ ]
+ },
+ {
+ "name": "removeBillingTokenConfig",
+ "docs": [
+ "Removes the billing token configuration.",
+ "Only CCIP Admin can remove a billing token configuration.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for removing the billing token configuration."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "billingTokenConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "tokenProgram",
+ "isMut": false,
+ "isSigner": false,
+ "docs": [
+ "type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount",
+ "with a constraint enforcing that it is one of the two allowed programs."
+ ]
+ },
+ {
+ "name": "feeTokenMint",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "feeTokenReceiver",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "feeBillingSigner",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "getFee",
+ "docs": [
+ "Calculates the fee for sending a message to the destination chain.",
+ "",
+ "# Arguments",
+ "",
+ "* `_ctx` - The context containing the accounts required for the fee calculation.",
+ "* `dest_chain_selector` - The chain selector for the destination chain.",
+ "* `message` - The message to be sent.",
+ "",
+ "# Returns",
+ "",
+ "The fee amount in u64."
+ ],
+ "accounts": [
+ {
+ "name": "chainState",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "billingTokenConfig",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "destChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "message",
+ "type": {
+ "defined": "Solana2AnyMessage"
+ }
+ }
+ ],
+ "returns": "u64"
+ },
+ {
+ "name": "ccipSend",
+ "docs": [
+ "ON RAMP FLOW",
+ "Sends a message to the destination chain.",
+ "",
+ "Request a message to be sent to the destination chain.",
+ "The method name needs to be ccip_send with Anchor encoding.",
+ "This function is called by the CCIP Sender Contract (or final user) to send a message to the CCIP Router.",
+ "The message will be sent to the receiver on the destination chain selector.",
+ "This message emits the event CCIPSendRequested with all the necessary data to be retrieved by the OffChain Code",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for sending the message.",
+ "* `dest_chain_selector` - The chain selector for the destination chain.",
+ "* `message` - The message to be sent. The size limit of data is 256 bytes."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "chainState",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "nonce",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "feeTokenProgram",
+ "isMut": false,
+ "isSigner": false,
+ "docs": [
+ "type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount",
+ "with a constraint enforcing that it is one of the two allowed programs."
+ ]
+ },
+ {
+ "name": "feeTokenMint",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "feeTokenConfig",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "feeTokenUserAssociatedAccount",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "feeTokenReceiver",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "feeBillingSigner",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "tokenPoolsSigner",
+ "isMut": true,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "destChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "message",
+ "type": {
+ "defined": "Solana2AnyMessage"
+ }
+ }
+ ]
+ },
+ {
+ "name": "commit",
+ "docs": [
+ "OFF RAMP FLOW",
+ "Commits a report to the router.",
+ "",
+ "The method name needs to be commit with Anchor encoding.",
+ "",
+ "This function is called by the OffChain when committing one Report to the Solana Router.",
+ "In this Flow only one report is sent, the Commit Report. This is different as EVM does,",
+ "this is because here all the chain state is stored in one account per Merkle Tree Root.",
+ "So, to avoid having to send a dynamic size array of accounts, in this message only one Commit Report Account is sent.",
+ "This message validates the signatures of the report and stores the Merkle Root in the Commit Report Account.",
+ "The Report must contain an interval of messages, and the min of them must be the next sequence number expected.",
+ "The max size of the interval is 64.",
+ "This message emits two events: CommitReportAccepted and Transmitted.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for the commit.",
+ "* `report_context_byte_words` - consists of:",
+ "* report_context_byte_words[0]: ConfigDigest",
+ "* report_context_byte_words[1]: 24 byte padding, 8 byte sequence number",
+ "* report_context_byte_words[2]: ExtraHash",
+ "* `report` - The commit input report, single merkle root with RMN signatures and price updates",
+ "* `signatures` - The list of signatures. v0.29.0 - anchor idl does not build with ocr3base::SIGNATURE_LENGTH"
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "chainState",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "commitReport",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "sysvarInstructions",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "reportContextByteWords",
+ "type": {
+ "array": [
+ {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ 3
+ ]
+ }
+ },
+ {
+ "name": "report",
+ "type": {
+ "defined": "CommitInput"
+ }
+ },
+ {
+ "name": "signatures",
+ "type": {
+ "vec": {
+ "array": [
+ "u8",
+ 65
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "execute",
+ "docs": [
+ "OFF RAMP FLOW",
+ "Executes a message on the destination chain.",
+ "",
+ "The method name needs to be execute with Anchor encoding.",
+ "",
+ "This function is called by the OffChain when executing one Report to the Solana Router.",
+ "In this Flow only one message is sent, the Execution Report. This is different as EVM does,",
+ "this is because there is no try/catch mechanism to allow batch execution.",
+ "This message validates that the Merkle Tree Proof of the given message is correct and is stored in the Commit Report Account.",
+ "The message must be untouched to be executed.",
+ "This message emits the event ExecutionStateChanged with the new state of the message.",
+ "Finally, executes the CPI instruction to the receiver program in the ccip_receive message.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for the execute.",
+ "* `execution_report` - the execution report containing only one message and proofs",
+ "* `report_context_byte_words` - report_context after execution_report to match context for manually execute (proper decoding order)",
+ "* consists of:",
+ "* report_context_byte_words[0]: ConfigDigest",
+ "* report_context_byte_words[1]: 24 byte padding, 8 byte sequence number",
+ "* report_context_byte_words[2]: ExtraHash"
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "chainState",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "commitReport",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "externalExecutionConfig",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "sysvarInstructions",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "tokenPoolsSigner",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "executionReport",
+ "type": {
+ "defined": "ExecutionReportSingleChain"
+ }
+ },
+ {
+ "name": "reportContextByteWords",
+ "type": {
+ "array": [
+ {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ 3
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "manuallyExecute",
+ "docs": [
+ "Manually executes a report to the router.",
+ "",
+ "When a message is not being executed, then the user can trigger the execution manually.",
+ "No verification over the transmitter, but the message needs to be in some commit report.",
+ "It validates that the required time has passed since the commit and then executes the report.",
+ "",
+ "# Arguments",
+ "",
+ "* `ctx` - The context containing the accounts required for the execution.",
+ "* `execution_report` - The execution report containing the message and proofs."
+ ],
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "chainState",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "commitReport",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "externalExecutionConfig",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "sysvarInstructions",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "tokenPoolsSigner",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "executionReport",
+ "type": {
+ "defined": "ExecutionReportSingleChain"
+ }
+ }
+ ]
+ }
+ ],
+ "accounts": [
+ {
+ "name": "Config",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "version",
+ "type": "u8"
+ },
+ {
+ "name": "defaultAllowOutOfOrderExecution",
+ "type": "u8"
+ },
+ {
+ "name": "padding0",
+ "type": {
+ "array": [
+ "u8",
+ 6
+ ]
+ }
+ },
+ {
+ "name": "solanaChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "defaultGasLimit",
+ "type": "u128"
+ },
+ {
+ "name": "padding1",
+ "type": {
+ "array": [
+ "u8",
+ 8
+ ]
+ }
+ },
+ {
+ "name": "owner",
+ "type": "publicKey"
+ },
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ },
+ {
+ "name": "enableManualExecutionAfter",
+ "type": "i64"
+ },
+ {
+ "name": "padding2",
+ "type": {
+ "array": [
+ "u8",
+ 8
+ ]
+ }
+ },
+ {
+ "name": "ocr3",
+ "type": {
+ "array": [
+ {
+ "defined": "Ocr3Config"
+ },
+ 2
+ ]
+ }
+ },
+ {
+ "name": "paddingBeforeBilling",
+ "type": {
+ "array": [
+ "u8",
+ 8
+ ]
+ }
+ },
+ {
+ "name": "latestPriceSequenceNumber",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ChainState",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "version",
+ "type": "u8"
+ },
+ {
+ "name": "sourceChain",
+ "type": {
+ "defined": "SourceChain"
+ }
+ },
+ {
+ "name": "destChain",
+ "type": {
+ "defined": "DestChain"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "Nonce",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "version",
+ "type": "u8"
+ },
+ {
+ "name": "counter",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ExternalExecutionConfig",
+ "type": {
+ "kind": "struct",
+ "fields": []
+ }
+ },
+ {
+ "name": "CommitReport",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "version",
+ "type": "u8"
+ },
+ {
+ "name": "timestamp",
+ "type": "i64"
+ },
+ {
+ "name": "minMsgNr",
+ "type": "u64"
+ },
+ {
+ "name": "maxMsgNr",
+ "type": "u64"
+ },
+ {
+ "name": "executionStates",
+ "type": "u128"
+ }
+ ]
+ }
+ },
+ {
+ "name": "PerChainPerTokenConfig",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "version",
+ "type": "u8"
+ },
+ {
+ "name": "chainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "mint",
+ "type": "publicKey"
+ },
+ {
+ "name": "billing",
+ "type": {
+ "defined": "TokenBilling"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "BillingTokenConfigWrapper",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "version",
+ "type": "u8"
+ },
+ {
+ "name": "config",
+ "type": {
+ "defined": "BillingTokenConfig"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "TokenAdminRegistry",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "version",
+ "type": "u8"
+ },
+ {
+ "name": "administrator",
+ "type": "publicKey"
+ },
+ {
+ "name": "pendingAdministrator",
+ "type": "publicKey"
+ },
+ {
+ "name": "lookupTable",
+ "type": "publicKey"
+ }
+ ]
+ }
+ }
+ ],
+ "types": [
+ {
+ "name": "CommitInput",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "priceUpdates",
+ "type": {
+ "defined": "PriceUpdates"
+ }
+ },
+ {
+ "name": "merkleRoot",
+ "type": {
+ "defined": "MerkleRoot"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "PriceUpdates",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "tokenPriceUpdates",
+ "type": {
+ "vec": {
+ "defined": "TokenPriceUpdate"
+ }
+ }
+ },
+ {
+ "name": "gasPriceUpdates",
+ "type": {
+ "vec": {
+ "defined": "GasPriceUpdate"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "TokenPriceUpdate",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "sourceToken",
+ "type": "publicKey"
+ },
+ {
+ "name": "usdPerToken",
+ "type": {
+ "array": [
+ "u8",
+ 28
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "GasPriceUpdate",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "destChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "usdPerUnitGas",
+ "type": {
+ "array": [
+ "u8",
+ 28
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "MerkleRoot",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "sourceChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "onRampAddress",
+ "type": "bytes"
+ },
+ {
+ "name": "minSeqNr",
+ "type": "u64"
+ },
+ {
+ "name": "maxSeqNr",
+ "type": "u64"
+ },
+ {
+ "name": "merkleRoot",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "Solana2AnyMessage",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "receiver",
+ "type": "bytes"
+ },
+ {
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "name": "tokenAmounts",
+ "type": {
+ "vec": {
+ "defined": "SolanaTokenAmount"
+ }
+ }
+ },
+ {
+ "name": "feeToken",
+ "type": "publicKey"
+ },
+ {
+ "name": "extraArgs",
+ "type": {
+ "defined": "ExtraArgsInput"
+ }
+ },
+ {
+ "name": "tokenIndexes",
+ "type": "bytes"
+ }
+ ]
+ }
+ },
+ {
+ "name": "SolanaTokenAmount",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "token",
+ "type": "publicKey"
+ },
+ {
+ "name": "amount",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ExtraArgsInput",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "gasLimit",
+ "type": {
+ "option": "u128"
+ }
+ },
+ {
+ "name": "allowOutOfOrderExecution",
+ "type": {
+ "option": "bool"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "Any2SolanaMessage",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "messageId",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "sourceChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "sender",
+ "type": "bytes"
+ },
+ {
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "name": "tokenAmounts",
+ "type": {
+ "vec": {
+ "defined": "SolanaTokenAmount"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "RampMessageHeader",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "messageId",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "sourceChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "destChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "sequenceNumber",
+ "type": "u64"
+ },
+ {
+ "name": "nonce",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ExecutionReportSingleChain",
+ "docs": [
+ "Report that is submitted by the execution DON at the execution phase. (including chain selector data)"
+ ],
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "sourceChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "message",
+ "type": {
+ "defined": "Any2SolanaRampMessage"
+ }
+ },
+ {
+ "name": "offchainTokenData",
+ "type": {
+ "vec": "bytes"
+ }
+ },
+ {
+ "name": "root",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "proofs",
+ "type": {
+ "vec": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ },
+ {
+ "name": "tokenIndexes",
+ "type": "bytes"
+ }
+ ]
+ }
+ },
+ {
+ "name": "SolanaAccountMeta",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "pubkey",
+ "type": "publicKey"
+ },
+ {
+ "name": "isWritable",
+ "type": "bool"
+ }
+ ]
+ }
+ },
+ {
+ "name": "SolanaExtraArgs",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "computeUnits",
+ "type": "u32"
+ },
+ {
+ "name": "accounts",
+ "type": {
+ "vec": {
+ "defined": "SolanaAccountMeta"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "EvmExtraArgs",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "gasLimit",
+ "type": "u128"
+ },
+ {
+ "name": "allowOutOfOrderExecution",
+ "type": "bool"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Any2SolanaRampMessage",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "header",
+ "type": {
+ "defined": "RampMessageHeader"
+ }
+ },
+ {
+ "name": "sender",
+ "type": "bytes"
+ },
+ {
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "name": "receiver",
+ "type": "publicKey"
+ },
+ {
+ "name": "tokenAmounts",
+ "type": {
+ "vec": {
+ "defined": "Any2SolanaTokenTransfer"
+ }
+ }
+ },
+ {
+ "name": "extraArgs",
+ "type": {
+ "defined": "SolanaExtraArgs"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "Solana2AnyRampMessage",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "header",
+ "type": {
+ "defined": "RampMessageHeader"
+ }
+ },
+ {
+ "name": "sender",
+ "type": "publicKey"
+ },
+ {
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "name": "receiver",
+ "type": "bytes"
+ },
+ {
+ "name": "extraArgs",
+ "type": {
+ "defined": "EvmExtraArgs"
+ }
+ },
+ {
+ "name": "feeToken",
+ "type": "publicKey"
+ },
+ {
+ "name": "tokenAmounts",
+ "type": {
+ "vec": {
+ "defined": "Solana2AnyTokenTransfer"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "Solana2AnyTokenTransfer",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "sourcePoolAddress",
+ "type": "publicKey"
+ },
+ {
+ "name": "destTokenAddress",
+ "type": "bytes"
+ },
+ {
+ "name": "extraData",
+ "type": "bytes"
+ },
+ {
+ "name": "amount",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "destExecData",
+ "type": "bytes"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Any2SolanaTokenTransfer",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "sourcePoolAddress",
+ "type": "bytes"
+ },
+ {
+ "name": "destTokenAddress",
+ "type": "publicKey"
+ },
+ {
+ "name": "destGasAmount",
+ "type": "u32"
+ },
+ {
+ "name": "extraData",
+ "type": "bytes"
+ },
+ {
+ "name": "amount",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "LockOrBurnInV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "receiver",
+ "type": "bytes"
+ },
+ {
+ "name": "remoteChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "originalSender",
+ "type": "publicKey"
+ },
+ {
+ "name": "amount",
+ "type": "u64"
+ },
+ {
+ "name": "localToken",
+ "type": "publicKey"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ReleaseOrMintInV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "originalSender",
+ "type": "bytes"
+ },
+ {
+ "name": "remoteChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "receiver",
+ "type": "publicKey"
+ },
+ {
+ "name": "amount",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "localToken",
+ "type": "publicKey"
+ },
+ {
+ "name": "sourcePoolAddress",
+ "docs": [
+ "@dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the",
+ "expected pool address for the given remoteChainSelector."
+ ],
+ "type": "bytes"
+ },
+ {
+ "name": "sourcePoolData",
+ "type": "bytes"
+ },
+ {
+ "name": "offchainTokenData",
+ "docs": [
+ "@dev WARNING: offchainTokenData is untrusted data."
+ ],
+ "type": "bytes"
+ }
+ ]
+ }
+ },
+ {
+ "name": "LockOrBurnOutV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "destTokenAddress",
+ "type": "bytes"
+ },
+ {
+ "name": "destPoolData",
+ "type": "bytes"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ReleaseOrMintOutV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "destinationAmount",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ReportContext",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "byteWords",
+ "type": {
+ "array": [
+ {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ 3
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "Ocr3Config",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "pluginType",
+ "type": "u8"
+ },
+ {
+ "name": "configInfo",
+ "type": {
+ "defined": "Ocr3ConfigInfo"
+ }
+ },
+ {
+ "name": "signers",
+ "type": {
+ "array": [
+ {
+ "array": [
+ "u8",
+ 20
+ ]
+ },
+ 16
+ ]
+ }
+ },
+ {
+ "name": "transmitters",
+ "type": {
+ "array": [
+ {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ 16
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "Ocr3ConfigInfo",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "configDigest",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "f",
+ "type": "u8"
+ },
+ {
+ "name": "n",
+ "type": "u8"
+ },
+ {
+ "name": "isSignatureVerificationEnabled",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "SourceChainConfig",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "isEnabled",
+ "type": "bool"
+ },
+ {
+ "name": "onRamp",
+ "type": "bytes"
+ }
+ ]
+ }
+ },
+ {
+ "name": "SourceChainState",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "minSeqNr",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "SourceChain",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "state",
+ "type": {
+ "defined": "SourceChainState"
+ }
+ },
+ {
+ "name": "config",
+ "type": {
+ "defined": "SourceChainConfig"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "DestChainState",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "sequenceNumber",
+ "type": "u64"
+ },
+ {
+ "name": "usdPerUnitGas",
+ "type": {
+ "defined": "TimestampedPackedU224"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "DestChainConfig",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "isEnabled",
+ "type": "bool"
+ },
+ {
+ "name": "maxNumberOfTokensPerMsg",
+ "type": "u16"
+ },
+ {
+ "name": "maxDataBytes",
+ "type": "u32"
+ },
+ {
+ "name": "maxPerMsgGasLimit",
+ "type": "u32"
+ },
+ {
+ "name": "destGasOverhead",
+ "type": "u32"
+ },
+ {
+ "name": "destGasPerPayloadByte",
+ "type": "u16"
+ },
+ {
+ "name": "destDataAvailabilityOverheadGas",
+ "type": "u32"
+ },
+ {
+ "name": "destGasPerDataAvailabilityByte",
+ "type": "u16"
+ },
+ {
+ "name": "destDataAvailabilityMultiplierBps",
+ "type": "u16"
+ },
+ {
+ "name": "defaultTokenFeeUsdcents",
+ "type": "u16"
+ },
+ {
+ "name": "defaultTokenDestGasOverhead",
+ "type": "u32"
+ },
+ {
+ "name": "defaultTxGasLimit",
+ "type": "u32"
+ },
+ {
+ "name": "gasMultiplierWeiPerEth",
+ "type": "u64"
+ },
+ {
+ "name": "networkFeeUsdcents",
+ "type": "u32"
+ },
+ {
+ "name": "gasPriceStalenessThreshold",
+ "type": "u32"
+ },
+ {
+ "name": "enforceOutOfOrder",
+ "type": "bool"
+ },
+ {
+ "name": "chainFamilySelector",
+ "type": {
+ "array": [
+ "u8",
+ 4
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "DestChain",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "state",
+ "type": {
+ "defined": "DestChainState"
+ }
+ },
+ {
+ "name": "config",
+ "type": {
+ "defined": "DestChainConfig"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "TokenBilling",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "minFeeUsdcents",
+ "type": "u32"
+ },
+ {
+ "name": "maxFeeUsdcents",
+ "type": "u32"
+ },
+ {
+ "name": "deciBps",
+ "type": "u16"
+ },
+ {
+ "name": "destGasOverhead",
+ "type": "u32"
+ },
+ {
+ "name": "destBytesOverhead",
+ "type": "u32"
+ },
+ {
+ "name": "isEnabled",
+ "type": "bool"
+ }
+ ]
+ }
+ },
+ {
+ "name": "RateLimitTokenBucket",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "tokens",
+ "type": "u128"
+ },
+ {
+ "name": "lastUpdated",
+ "type": "u32"
+ },
+ {
+ "name": "isEnabled",
+ "type": "bool"
+ },
+ {
+ "name": "capacity",
+ "type": "u128"
+ },
+ {
+ "name": "rate",
+ "type": "u128"
+ }
+ ]
+ }
+ },
+ {
+ "name": "BillingTokenConfig",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "enabled",
+ "type": "bool"
+ },
+ {
+ "name": "mint",
+ "type": "publicKey"
+ },
+ {
+ "name": "usdPerToken",
+ "type": {
+ "defined": "TimestampedPackedU224"
+ }
+ },
+ {
+ "name": "premiumMultiplierWeiPerEth",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "TimestampedPackedU224",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "value",
+ "type": {
+ "array": [
+ "u8",
+ 28
+ ]
+ }
+ },
+ {
+ "name": "timestamp",
+ "type": "i64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "OcrPluginType",
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "Commit"
+ },
+ {
+ "name": "Execution"
+ }
+ ]
+ }
+ },
+ {
+ "name": "MerkleError",
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "InvalidProof"
+ }
+ ]
+ }
+ },
+ {
+ "name": "MessageExecutionState",
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "Untouched"
+ },
+ {
+ "name": "InProgress"
+ },
+ {
+ "name": "Success"
+ },
+ {
+ "name": "Failure"
+ }
+ ]
+ }
+ },
+ {
+ "name": "CcipRouterError",
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "InvalidSequenceInterval"
+ },
+ {
+ "name": "RootNotCommitted"
+ },
+ {
+ "name": "ExistingMerkleRoot"
+ },
+ {
+ "name": "Unauthorized"
+ },
+ {
+ "name": "InvalidInputs"
+ },
+ {
+ "name": "UnsupportedSourceChainSelector"
+ },
+ {
+ "name": "UnsupportedDestinationChainSelector"
+ },
+ {
+ "name": "InvalidProof"
+ },
+ {
+ "name": "InvalidMessage"
+ },
+ {
+ "name": "ReachedMaxSequenceNumber"
+ },
+ {
+ "name": "ManualExecutionNotAllowed"
+ },
+ {
+ "name": "InvalidInputsTokenIndices"
+ },
+ {
+ "name": "InvalidInputsPoolAccounts"
+ },
+ {
+ "name": "InvalidInputsTokenAccounts"
+ },
+ {
+ "name": "InvalidInputsConfigAccounts"
+ },
+ {
+ "name": "InvalidInputsTokenAdminRegistryAccounts"
+ },
+ {
+ "name": "InvalidInputsLookupTableAccounts"
+ },
+ {
+ "name": "InvalidInputsTokenAmount"
+ },
+ {
+ "name": "OfframpReleaseMintBalanceMismatch"
+ },
+ {
+ "name": "OfframpInvalidDataLength"
+ },
+ {
+ "name": "StaleCommitReport"
+ },
+ {
+ "name": "DestinationChainDisabled"
+ },
+ {
+ "name": "FeeTokenDisabled"
+ }
+ ]
+ }
+ }
+ ],
+ "events": [
+ {
+ "name": "CCIPMessageSent",
+ "fields": [
+ {
+ "name": "destChainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "sequenceNumber",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "message",
+ "type": {
+ "defined": "Solana2AnyRampMessage"
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "CommitReportAccepted",
+ "fields": [
+ {
+ "name": "merkleRoot",
+ "type": {
+ "defined": "MerkleRoot"
+ },
+ "index": false
+ },
+ {
+ "name": "priceUpdates",
+ "type": {
+ "defined": "PriceUpdates"
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "SkippedAlreadyExecutedMessage",
+ "fields": [
+ {
+ "name": "sourceChainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "sequenceNumber",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "AlreadyAttempted",
+ "fields": [
+ {
+ "name": "sourceChainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "sequenceNumber",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "ExecutionStateChanged",
+ "fields": [
+ {
+ "name": "sourceChainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "sequenceNumber",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "messageId",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "messageHash",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "state",
+ "type": {
+ "defined": "MessageExecutionState"
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "PoolSet",
+ "fields": [
+ {
+ "name": "token",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "previousPoolLookupTable",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "newPoolLookupTable",
+ "type": "publicKey",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "AdministratorTransferRequested",
+ "fields": [
+ {
+ "name": "token",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "currentAdmin",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "newAdmin",
+ "type": "publicKey",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "AdministratorTransferred",
+ "fields": [
+ {
+ "name": "token",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "newAdmin",
+ "type": "publicKey",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "FeeTokenAdded",
+ "fields": [
+ {
+ "name": "feeToken",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "enabled",
+ "type": "bool",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "FeeTokenEnabled",
+ "fields": [
+ {
+ "name": "feeToken",
+ "type": "publicKey",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "FeeTokenDisabled",
+ "fields": [
+ {
+ "name": "feeToken",
+ "type": "publicKey",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "FeeTokenRemoved",
+ "fields": [
+ {
+ "name": "feeToken",
+ "type": "publicKey",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "UsdPerUnitGasUpdated",
+ "fields": [
+ {
+ "name": "destChain",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "value",
+ "type": {
+ "array": [
+ "u8",
+ 28
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "timestamp",
+ "type": "i64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "UsdPerTokenUpdated",
+ "fields": [
+ {
+ "name": "token",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "value",
+ "type": {
+ "array": [
+ "u8",
+ 28
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "timestamp",
+ "type": "i64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "TokenTransferFeeConfigUpdated",
+ "fields": [
+ {
+ "name": "destChainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "token",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "tokenTransferFeeConfig",
+ "type": {
+ "defined": "TokenBilling"
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "PremiumMultiplierWeiPerEthUpdated",
+ "fields": [
+ {
+ "name": "token",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "premiumMultiplierWeiPerEth",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "SourceChainConfigUpdated",
+ "fields": [
+ {
+ "name": "sourceChainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "sourceChainConfig",
+ "type": {
+ "defined": "SourceChainConfig"
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "SourceChainAdded",
+ "fields": [
+ {
+ "name": "sourceChainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "sourceChainConfig",
+ "type": {
+ "defined": "SourceChainConfig"
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "DestChainConfigUpdated",
+ "fields": [
+ {
+ "name": "destChainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "destChainConfig",
+ "type": {
+ "defined": "DestChainConfig"
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "DestChainAdded",
+ "fields": [
+ {
+ "name": "destChainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "destChainConfig",
+ "type": {
+ "defined": "DestChainConfig"
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "AdministratorRegistered",
+ "fields": [
+ {
+ "name": "tokenMint",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "administrator",
+ "type": "publicKey",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "ConfigSet",
+ "fields": [
+ {
+ "name": "ocrPluginType",
+ "type": "u8",
+ "index": false
+ },
+ {
+ "name": "configDigest",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "signers",
+ "type": {
+ "vec": {
+ "array": [
+ "u8",
+ 20
+ ]
+ }
+ },
+ "index": false
+ },
+ {
+ "name": "transmitters",
+ "type": {
+ "vec": "publicKey"
+ },
+ "index": false
+ },
+ {
+ "name": "f",
+ "type": "u8",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "Transmitted",
+ "fields": [
+ {
+ "name": "ocrPluginType",
+ "type": "u8",
+ "index": false
+ },
+ {
+ "name": "configDigest",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "sequenceNumber",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ }
+ ],
+ "errors": [
+ {
+ "code": 6000,
+ "name": "InvalidConfigFMustBePositive",
+ "msg": "Invalid config: F must be positive"
+ },
+ {
+ "code": 6001,
+ "name": "InvalidConfigTooManyTransmitters",
+ "msg": "Invalid config: Too many transmitters"
+ },
+ {
+ "code": 6002,
+ "name": "InvalidConfigTooManySigners",
+ "msg": "Invalid config: Too many signers"
+ },
+ {
+ "code": 6003,
+ "name": "InvalidConfigFIsTooHigh",
+ "msg": "Invalid config: F is too high"
+ },
+ {
+ "code": 6004,
+ "name": "InvalidConfigRepeatedOracle",
+ "msg": "Invalid config: Repeated oracle address"
+ },
+ {
+ "code": 6005,
+ "name": "WrongMessageLength",
+ "msg": "Wrong message length"
+ },
+ {
+ "code": 6006,
+ "name": "ConfigDigestMismatch",
+ "msg": "Config digest mismatch"
+ },
+ {
+ "code": 6007,
+ "name": "WrongNumberOfSignatures",
+ "msg": "Wrong number signatures"
+ },
+ {
+ "code": 6008,
+ "name": "UnauthorizedTransmitter",
+ "msg": "Unauthorized transmitter"
+ },
+ {
+ "code": 6009,
+ "name": "UnauthorizedSigner",
+ "msg": "Unauthorized signer"
+ },
+ {
+ "code": 6010,
+ "name": "NonUniqueSignatures",
+ "msg": "Non unique signatures"
+ },
+ {
+ "code": 6011,
+ "name": "OracleCannotBeZeroAddress",
+ "msg": "Oracle cannot be zero address"
+ },
+ {
+ "code": 6012,
+ "name": "StaticConfigCannotBeChanged",
+ "msg": "Static config cannot be changed"
+ },
+ {
+ "code": 6013,
+ "name": "InvalidPluginType",
+ "msg": "Incorrect plugin type"
+ },
+ {
+ "code": 6014,
+ "name": "InvalidSignature",
+ "msg": "Invalid signature"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/chains/solana/contracts/target/idl/external_program_cpi_stub.json b/chains/solana/contracts/target/idl/external_program_cpi_stub.json
new file mode 100644
index 000000000..1e29fc945
--- /dev/null
+++ b/chains/solana/contracts/target/idl/external_program_cpi_stub.json
@@ -0,0 +1,100 @@
+{
+ "version": "0.0.0-dev",
+ "name": "external_program_cpi_stub",
+ "instructions": [
+ {
+ "name": "initialize",
+ "accounts": [
+ {
+ "name": "u8Value",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "stubCaller",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "empty",
+ "accounts": [],
+ "args": []
+ },
+ {
+ "name": "u8InstructionData",
+ "accounts": [],
+ "args": [
+ {
+ "name": "data",
+ "type": "u8"
+ }
+ ]
+ },
+ {
+ "name": "structInstructionData",
+ "accounts": [],
+ "args": [
+ {
+ "name": "data",
+ "type": {
+ "defined": "Value"
+ }
+ }
+ ]
+ },
+ {
+ "name": "accountRead",
+ "accounts": [
+ {
+ "name": "u8Value",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "accountMut",
+ "accounts": [
+ {
+ "name": "u8Value",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "stubCaller",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": []
+ }
+ ],
+ "accounts": [
+ {
+ "name": "Value",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "value",
+ "type": "u8"
+ }
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/chains/solana/contracts/target/idl/mcm.json b/chains/solana/contracts/target/idl/mcm.json
new file mode 100644
index 000000000..54cf42340
--- /dev/null
+++ b/chains/solana/contracts/target/idl/mcm.json
@@ -0,0 +1,1194 @@
+{
+ "version": "0.1.0-dev",
+ "name": "mcm",
+ "docs": [
+ "This is mcm program supporting multiple instances of multisig configuration",
+ "A single deployed program manages multiple multisig states(configurations) identified by multisig_name"
+ ],
+ "instructions": [
+ {
+ "name": "initialize",
+ "docs": [
+ "initialize a new multisig configuration, store the chain_id and multisig_name",
+ "multisig_name is a unique identifier for the multisig configuration(32 bytes, left-padded)"
+ ],
+ "accounts": [
+ {
+ "name": "multisigConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "program",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "programData",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "rootMetadata",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "expiringRootAndOpCount",
+ "isMut": true,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "chainId",
+ "type": "u64"
+ },
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "transferOwnership",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "acceptOwnership",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "setConfig",
+ "accounts": [
+ {
+ "name": "multisigConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "configSigners",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "signerGroups",
+ "type": "bytes"
+ },
+ {
+ "name": "groupQuorums",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "groupParents",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "clearRoot",
+ "type": "bool"
+ }
+ ]
+ },
+ {
+ "name": "setRoot",
+ "accounts": [
+ {
+ "name": "rootSignatures",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "rootMetadata",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "seenSignedHashes",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "expiringRootAndOpCount",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "multisigConfig",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "root",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "validUntil",
+ "type": "u32"
+ },
+ {
+ "name": "metadata",
+ "type": {
+ "defined": "RootMetadataInput"
+ }
+ },
+ {
+ "name": "metadataProof",
+ "type": {
+ "vec": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "execute",
+ "accounts": [
+ {
+ "name": "multisigConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "rootMetadata",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "expiringRootAndOpCount",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "to",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "multisigSigner",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "chainId",
+ "type": "u64"
+ },
+ {
+ "name": "nonce",
+ "type": "u64"
+ },
+ {
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "name": "proof",
+ "type": {
+ "vec": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "initSigners",
+ "accounts": [
+ {
+ "name": "multisigConfig",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "configSigners",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "totalSigners",
+ "type": "u8"
+ }
+ ]
+ },
+ {
+ "name": "appendSigners",
+ "accounts": [
+ {
+ "name": "multisigConfig",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "configSigners",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "signersBatch",
+ "type": {
+ "vec": {
+ "array": [
+ "u8",
+ 20
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "clearSigners",
+ "accounts": [
+ {
+ "name": "multisigConfig",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "configSigners",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "finalizeSigners",
+ "accounts": [
+ {
+ "name": "multisigConfig",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "configSigners",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "initSignatures",
+ "accounts": [
+ {
+ "name": "signatures",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "root",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "validUntil",
+ "type": "u32"
+ },
+ {
+ "name": "totalSignatures",
+ "type": "u8"
+ }
+ ]
+ },
+ {
+ "name": "appendSignatures",
+ "accounts": [
+ {
+ "name": "signatures",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "root",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "validUntil",
+ "type": "u32"
+ },
+ {
+ "name": "signaturesBatch",
+ "type": {
+ "vec": {
+ "defined": "Signature"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "clearSignatures",
+ "accounts": [
+ {
+ "name": "signatures",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "root",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "validUntil",
+ "type": "u32"
+ }
+ ]
+ },
+ {
+ "name": "finalizeSignatures",
+ "accounts": [
+ {
+ "name": "signatures",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "root",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "validUntil",
+ "type": "u32"
+ }
+ ]
+ }
+ ],
+ "accounts": [
+ {
+ "name": "ConfigSigners",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "signerAddresses",
+ "type": {
+ "vec": {
+ "array": [
+ "u8",
+ 20
+ ]
+ }
+ }
+ },
+ {
+ "name": "totalSigners",
+ "type": "u8"
+ },
+ {
+ "name": "isFinalized",
+ "type": "bool"
+ },
+ {
+ "name": "bump",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "MultisigConfig",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "chainId",
+ "type": "u64"
+ },
+ {
+ "name": "multisigName",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "owner",
+ "type": "publicKey"
+ },
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ },
+ {
+ "name": "groupQuorums",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "groupParents",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "signers",
+ "type": {
+ "vec": {
+ "defined": "McmSigner"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "RootSignatures",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "totalSignatures",
+ "type": "u8"
+ },
+ {
+ "name": "signatures",
+ "type": {
+ "vec": {
+ "defined": "Signature"
+ }
+ }
+ },
+ {
+ "name": "isFinalized",
+ "type": "bool"
+ },
+ {
+ "name": "bump",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "RootMetadata",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "chainId",
+ "type": "u64"
+ },
+ {
+ "name": "multisig",
+ "type": "publicKey"
+ },
+ {
+ "name": "preOpCount",
+ "type": "u64"
+ },
+ {
+ "name": "postOpCount",
+ "type": "u64"
+ },
+ {
+ "name": "overridePreviousRoot",
+ "type": "bool"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ExpiringRootAndOpCount",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "root",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "validUntil",
+ "type": "u32"
+ },
+ {
+ "name": "opCount",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "SeenSignedHash",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "seen",
+ "type": "bool"
+ }
+ ]
+ }
+ }
+ ],
+ "types": [
+ {
+ "name": "Signature",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "v",
+ "type": "u8"
+ },
+ {
+ "name": "r",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "s",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "McmSigner",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "evmAddress",
+ "type": {
+ "array": [
+ "u8",
+ 20
+ ]
+ }
+ },
+ {
+ "name": "index",
+ "type": "u8"
+ },
+ {
+ "name": "group",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "RootMetadataInput",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "chainId",
+ "type": "u64"
+ },
+ {
+ "name": "multisig",
+ "type": "publicKey"
+ },
+ {
+ "name": "preOpCount",
+ "type": "u64"
+ },
+ {
+ "name": "postOpCount",
+ "type": "u64"
+ },
+ {
+ "name": "overridePreviousRoot",
+ "type": "bool"
+ }
+ ]
+ }
+ }
+ ],
+ "events": [
+ {
+ "name": "NewRoot",
+ "fields": [
+ {
+ "name": "root",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "validUntil",
+ "type": "u32",
+ "index": false
+ },
+ {
+ "name": "metadataChainId",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "metadataMultisig",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "metadataPreOpCount",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "metadataPostOpCount",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "metadataOverridePreviousRoot",
+ "type": "bool",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "ConfigSet",
+ "fields": [
+ {
+ "name": "groupParents",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "groupQuorums",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "isRootCleared",
+ "type": "bool",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "OpExecuted",
+ "fields": [
+ {
+ "name": "nonce",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "to",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "data",
+ "type": "bytes",
+ "index": false
+ }
+ ]
+ }
+ ],
+ "errors": [
+ {
+ "code": 6000,
+ "name": "WrongMultiSig",
+ "msg": "Invalid multisig"
+ },
+ {
+ "code": 6001,
+ "name": "WrongChainId",
+ "msg": "Invalid chainID"
+ },
+ {
+ "code": 6002,
+ "name": "Unauthorized",
+ "msg": "The signer is unauthorized"
+ },
+ {
+ "code": 6003,
+ "name": "InvalidInputs",
+ "msg": "Invalid inputs"
+ },
+ {
+ "code": 6004,
+ "name": "Overflow",
+ "msg": "overflow occurred."
+ },
+ {
+ "code": 6005,
+ "name": "InvalidSignature",
+ "msg": "Invalid signature"
+ },
+ {
+ "code": 6006,
+ "name": "FailedEcdsaRecover",
+ "msg": "Failed ECDSA recover"
+ },
+ {
+ "code": 6007,
+ "name": "InvalidRootLen",
+ "msg": "Invalid root length"
+ },
+ {
+ "code": 6008,
+ "name": "SignersNotFinalized",
+ "msg": "Config signers not finalized"
+ },
+ {
+ "code": 6009,
+ "name": "SignersAlreadyFinalized",
+ "msg": "Config signers already finalized"
+ },
+ {
+ "code": 6010,
+ "name": "SignaturesAlreadyFinalized",
+ "msg": "Signatures already finalized"
+ },
+ {
+ "code": 6011,
+ "name": "SignatureCountMismatch",
+ "msg": "Uploaded signatures count mismatch"
+ },
+ {
+ "code": 6012,
+ "name": "TooManySignatures",
+ "msg": "Too many signatures"
+ },
+ {
+ "code": 6013,
+ "name": "SignaturesNotFinalized",
+ "msg": "Signatures not finalized"
+ },
+ {
+ "code": 6014,
+ "name": "SignaturesRootMismatch",
+ "msg": "Signatures root mismatch"
+ },
+ {
+ "code": 6015,
+ "name": "SignaturesValidUntilMismatch",
+ "msg": "Signatures valid until mismatch"
+ },
+ {
+ "code": 6200,
+ "name": "MismatchedInputSignerVectorsLength",
+ "msg": "The input vectors for signer addresses and signer groups must have the same length"
+ },
+ {
+ "code": 6201,
+ "name": "OutOfBoundsNumOfSigners",
+ "msg": "The number of signers is 0 or greater than MAX_NUM_SIGNERS"
+ },
+ {
+ "code": 6202,
+ "name": "MismatchedInputGroupArraysLength",
+ "msg": "The input arrays for group parents and group quorums must be of length NUM_GROUPS"
+ },
+ {
+ "code": 6203,
+ "name": "GroupTreeNotWellFormed",
+ "msg": "the group tree isn't well-formed."
+ },
+ {
+ "code": 6204,
+ "name": "SignerInDisabledGroup",
+ "msg": "a disabled group contains a signer."
+ },
+ {
+ "code": 6205,
+ "name": "OutOfBoundsGroupQuorum",
+ "msg": "the quorum of some group is larger than the number of signers in it."
+ },
+ {
+ "code": 6206,
+ "name": "SignersAddressesMustBeStrictlyIncreasing",
+ "msg": "the signers' addresses are not a strictly increasing monotone sequence."
+ },
+ {
+ "code": 6207,
+ "name": "SignedHashAlreadySeen",
+ "msg": "The combination of signature and valid_until has already been seen"
+ },
+ {
+ "code": 6208,
+ "name": "InvalidSigner",
+ "msg": "Invalid signer"
+ },
+ {
+ "code": 6209,
+ "name": "MissingConfig",
+ "msg": "Missing configuration"
+ },
+ {
+ "code": 6210,
+ "name": "InsufficientSigners",
+ "msg": "Insufficient signers"
+ },
+ {
+ "code": 6211,
+ "name": "ValidUntilHasAlreadyPassed",
+ "msg": "Valid until has already passed"
+ },
+ {
+ "code": 6212,
+ "name": "ProofCannotBeVerified",
+ "msg": "Proof cannot be verified"
+ },
+ {
+ "code": 6213,
+ "name": "PendingOps",
+ "msg": "Pending operations"
+ },
+ {
+ "code": 6214,
+ "name": "WrongPreOpCount",
+ "msg": "Wrong pre-operation count"
+ },
+ {
+ "code": 6215,
+ "name": "WrongPostOpCount",
+ "msg": "Wrong post-operation count"
+ },
+ {
+ "code": 6216,
+ "name": "PostOpCountReached",
+ "msg": "Post-operation count reached"
+ },
+ {
+ "code": 6217,
+ "name": "RootExpired",
+ "msg": "Root expired"
+ },
+ {
+ "code": 6218,
+ "name": "WrongNonce",
+ "msg": "Wrong nonce"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/chains/solana/contracts/target/idl/timelock.json b/chains/solana/contracts/target/idl/timelock.json
new file mode 100644
index 000000000..0ed86a94b
--- /dev/null
+++ b/chains/solana/contracts/target/idl/timelock.json
@@ -0,0 +1,917 @@
+{
+ "version": "0.0.1-dev",
+ "name": "timelock",
+ "instructions": [
+ {
+ "name": "initialize",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "program",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "programData",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "accessControllerProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "proposerRoleAccessController",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "executorRoleAccessController",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "cancellerRoleAccessController",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "bypasserRoleAccessController",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "minDelay",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "batchAddAccess",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "accessControllerProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "roleAccessController",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "role",
+ "type": {
+ "defined": "Role"
+ }
+ }
+ ]
+ },
+ {
+ "name": "scheduleBatch",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "operation",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "roleAccessController",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "delay",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "initializeOperation",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "operation",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "proposer",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "predecessor",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "salt",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "instructionCount",
+ "type": "u32"
+ }
+ ]
+ },
+ {
+ "name": "appendInstructions",
+ "accounts": [
+ {
+ "name": "operation",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "instructionsBatch",
+ "type": {
+ "vec": {
+ "defined": "InstructionData"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "clearOperation",
+ "accounts": [
+ {
+ "name": "operation",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "finalizeOperation",
+ "accounts": [
+ {
+ "name": "operation",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "cancel",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "operation",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "roleAccessController",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "executeBatch",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "timelockSigner",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "operation",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "predecessorOperation",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "roleAccessController",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "bypasserExecuteBatch",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "timelockSigner",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "operation",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "roleAccessController",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "updateDelay",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "delay",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "blockFunctionSelector",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "selector",
+ "type": {
+ "array": [
+ "u8",
+ 8
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "unblockFunctionSelector",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "selector",
+ "type": {
+ "array": [
+ "u8",
+ 8
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "transferOwnership",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "acceptOwnership",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": []
+ }
+ ],
+ "accounts": [
+ {
+ "name": "Config",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "owner",
+ "type": "publicKey"
+ },
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ },
+ {
+ "name": "proposerRoleAccessController",
+ "type": "publicKey"
+ },
+ {
+ "name": "executorRoleAccessController",
+ "type": "publicKey"
+ },
+ {
+ "name": "cancellerRoleAccessController",
+ "type": "publicKey"
+ },
+ {
+ "name": "bypasserRoleAccessController",
+ "type": "publicKey"
+ },
+ {
+ "name": "minDelay",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Operation",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "timestamp",
+ "type": "u64"
+ },
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "predecessor",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "salt",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "authority",
+ "type": "publicKey"
+ },
+ {
+ "name": "isFinalized",
+ "type": "bool"
+ },
+ {
+ "name": "totalInstructions",
+ "type": "u32"
+ },
+ {
+ "name": "instructions",
+ "type": {
+ "vec": {
+ "defined": "InstructionData"
+ }
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "types": [
+ {
+ "name": "InstructionData",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "programId",
+ "type": "publicKey"
+ },
+ {
+ "name": "data",
+ "type": "bytes"
+ },
+ {
+ "name": "accounts",
+ "type": {
+ "vec": {
+ "defined": "InstructionAccount"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "InstructionAccount",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "pubkey",
+ "type": "publicKey"
+ },
+ {
+ "name": "isSigner",
+ "type": "bool"
+ },
+ {
+ "name": "isWritable",
+ "type": "bool"
+ }
+ ]
+ }
+ },
+ {
+ "name": "TimelockError",
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "Unauthorized"
+ },
+ {
+ "name": "InvalidInput"
+ },
+ {
+ "name": "Overflow"
+ },
+ {
+ "name": "InvalidId"
+ },
+ {
+ "name": "OperationNotFinalized"
+ },
+ {
+ "name": "OperationAlreadyFinalized"
+ },
+ {
+ "name": "TooManyInstructions"
+ },
+ {
+ "name": "OperationAlreadyScheduled"
+ },
+ {
+ "name": "DelayInsufficient"
+ },
+ {
+ "name": "OperationNotCancellable"
+ },
+ {
+ "name": "OperationNotReady"
+ },
+ {
+ "name": "MissingDependency"
+ },
+ {
+ "name": "BlockedSelector"
+ },
+ {
+ "name": "InvalidAccessController"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Role",
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "Admin"
+ },
+ {
+ "name": "Proposer"
+ },
+ {
+ "name": "Executor"
+ },
+ {
+ "name": "Canceller"
+ },
+ {
+ "name": "Bypasser"
+ }
+ ]
+ }
+ }
+ ],
+ "events": [
+ {
+ "name": "CallScheduled",
+ "fields": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "index",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "target",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "data",
+ "type": "bytes",
+ "index": false
+ },
+ {
+ "name": "predecessor",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "salt",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "delay",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "CallExecuted",
+ "fields": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ },
+ {
+ "name": "index",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "target",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "data",
+ "type": "bytes",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "BypasserCallExecuted",
+ "fields": [
+ {
+ "name": "index",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "target",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "data",
+ "type": "bytes",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "Cancelled",
+ "fields": [
+ {
+ "name": "id",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "MinDelayChange",
+ "fields": [
+ {
+ "name": "oldDuration",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "newDuration",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "FunctionSelectorBlocked",
+ "fields": [
+ {
+ "name": "selector",
+ "type": {
+ "array": [
+ "u8",
+ 8
+ ]
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "FunctionSelectorUnblocked",
+ "fields": [
+ {
+ "name": "selector",
+ "type": {
+ "array": [
+ "u8",
+ 8
+ ]
+ },
+ "index": false
+ }
+ ]
+ }
+ ],
+ "errors": [
+ {
+ "code": 6000,
+ "name": "Placeholder",
+ "msg": "Todo generate error type with anchor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/chains/solana/contracts/target/idl/token_pool.json b/chains/solana/contracts/target/idl/token_pool.json
new file mode 100644
index 000000000..045c1f4a9
--- /dev/null
+++ b/chains/solana/contracts/target/idl/token_pool.json
@@ -0,0 +1,845 @@
+{
+ "version": "0.1.0-dev",
+ "name": "token_pool",
+ "instructions": [
+ {
+ "name": "initialize",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "mint",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "poolSigner",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "poolType",
+ "type": {
+ "defined": "PoolType"
+ }
+ },
+ {
+ "name": "rampAuthority",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "transferOwnership",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "acceptOwnership",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "setRampAuthority",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ }
+ ],
+ "args": [
+ {
+ "name": "newAuthority",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "setChainRemoteConfig",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "chainConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "remoteChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "mint",
+ "type": "publicKey"
+ },
+ {
+ "name": "cfg",
+ "type": {
+ "defined": "RemoteConfig"
+ }
+ }
+ ]
+ },
+ {
+ "name": "setChainRateLimit",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "chainConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "remoteChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "mint",
+ "type": "publicKey"
+ },
+ {
+ "name": "inbound",
+ "type": {
+ "defined": "RateLimitConfig"
+ }
+ },
+ {
+ "name": "outbound",
+ "type": {
+ "defined": "RateLimitConfig"
+ }
+ }
+ ]
+ },
+ {
+ "name": "deleteChainConfig",
+ "accounts": [
+ {
+ "name": "config",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "chainConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "authority",
+ "isMut": true,
+ "isSigner": true
+ },
+ {
+ "name": "systemProgram",
+ "isMut": false,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "remoteChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "mint",
+ "type": "publicKey"
+ }
+ ]
+ },
+ {
+ "name": "releaseOrMintTokens",
+ "accounts": [
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "tokenProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "mint",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "poolSigner",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "poolTokenAccount",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "chainConfig",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "receiverTokenAccount",
+ "isMut": true,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "releaseOrMint",
+ "type": {
+ "defined": "ReleaseOrMintInV1"
+ }
+ }
+ ],
+ "returns": {
+ "defined": "ReleaseOrMintOutV1"
+ }
+ },
+ {
+ "name": "lockOrBurnTokens",
+ "accounts": [
+ {
+ "name": "authority",
+ "isMut": false,
+ "isSigner": true
+ },
+ {
+ "name": "config",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "tokenProgram",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "mint",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "poolSigner",
+ "isMut": false,
+ "isSigner": false
+ },
+ {
+ "name": "poolTokenAccount",
+ "isMut": true,
+ "isSigner": false
+ },
+ {
+ "name": "chainConfig",
+ "isMut": true,
+ "isSigner": false
+ }
+ ],
+ "args": [
+ {
+ "name": "lockOrBurn",
+ "type": {
+ "defined": "LockOrBurnInV1"
+ }
+ }
+ ],
+ "returns": {
+ "defined": "LockOrBurnOutV1"
+ }
+ }
+ ],
+ "accounts": [
+ {
+ "name": "Config",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "version",
+ "type": "u8"
+ },
+ {
+ "name": "poolType",
+ "type": {
+ "defined": "PoolType"
+ }
+ },
+ {
+ "name": "tokenProgram",
+ "type": "publicKey"
+ },
+ {
+ "name": "mint",
+ "type": "publicKey"
+ },
+ {
+ "name": "decimals",
+ "type": "u8"
+ },
+ {
+ "name": "poolSigner",
+ "type": "publicKey"
+ },
+ {
+ "name": "poolTokenAccount",
+ "type": "publicKey"
+ },
+ {
+ "name": "owner",
+ "type": "publicKey"
+ },
+ {
+ "name": "proposedOwner",
+ "type": "publicKey"
+ },
+ {
+ "name": "rampAuthority",
+ "type": "publicKey"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ExternalExecutionConfig",
+ "type": {
+ "kind": "struct",
+ "fields": []
+ }
+ },
+ {
+ "name": "ChainConfig",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "remote",
+ "type": {
+ "defined": "RemoteConfig"
+ }
+ },
+ {
+ "name": "inboundRateLimit",
+ "type": {
+ "defined": "RateLimitTokenBucket"
+ }
+ },
+ {
+ "name": "outboundRateLimit",
+ "type": {
+ "defined": "RateLimitTokenBucket"
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "types": [
+ {
+ "name": "RateLimitTokenBucket",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "tokens",
+ "type": "u64"
+ },
+ {
+ "name": "lastUpdated",
+ "type": "u64"
+ },
+ {
+ "name": "cfg",
+ "type": {
+ "defined": "RateLimitConfig"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "RateLimitConfig",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "enabled",
+ "type": "bool"
+ },
+ {
+ "name": "capacity",
+ "type": "u64"
+ },
+ {
+ "name": "rate",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "LockOrBurnInV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "receiver",
+ "type": "bytes"
+ },
+ {
+ "name": "remoteChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "originalSender",
+ "type": "publicKey"
+ },
+ {
+ "name": "amount",
+ "type": "u64"
+ },
+ {
+ "name": "localToken",
+ "type": "publicKey"
+ }
+ ]
+ }
+ },
+ {
+ "name": "LockOrBurnOutV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "destTokenAddress",
+ "type": "bytes"
+ },
+ {
+ "name": "destPoolData",
+ "type": "bytes"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ReleaseOrMintInV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "originalSender",
+ "type": "bytes"
+ },
+ {
+ "name": "remoteChainSelector",
+ "type": "u64"
+ },
+ {
+ "name": "receiver",
+ "type": "publicKey"
+ },
+ {
+ "name": "amount",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ },
+ {
+ "name": "localToken",
+ "type": "publicKey"
+ },
+ {
+ "name": "sourcePoolAddress",
+ "docs": [
+ "@dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the",
+ "expected pool address for the given remoteChainSelector."
+ ],
+ "type": "bytes"
+ },
+ {
+ "name": "sourcePoolData",
+ "type": "bytes"
+ },
+ {
+ "name": "offchainTokenData",
+ "docs": [
+ "@dev WARNING: offchainTokenData is untrusted data."
+ ],
+ "type": "bytes"
+ }
+ ]
+ }
+ },
+ {
+ "name": "ReleaseOrMintOutV1",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "destinationAmount",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "RemoteConfig",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "poolAddress",
+ "type": "bytes"
+ },
+ {
+ "name": "tokenAddress",
+ "type": "bytes"
+ },
+ {
+ "name": "decimals",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "PoolType",
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "LockAndRelease"
+ },
+ {
+ "name": "BurnAndMint"
+ },
+ {
+ "name": "Wrapped"
+ }
+ ]
+ }
+ }
+ ],
+ "events": [
+ {
+ "name": "Burned",
+ "fields": [
+ {
+ "name": "sender",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "amount",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "Minted",
+ "fields": [
+ {
+ "name": "sender",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "recipient",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "amount",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "Locked",
+ "fields": [
+ {
+ "name": "sender",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "amount",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "Released",
+ "fields": [
+ {
+ "name": "sender",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "recipient",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "amount",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "RemoteChainConfigured",
+ "fields": [
+ {
+ "name": "chainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "token",
+ "type": "bytes",
+ "index": false
+ },
+ {
+ "name": "previousToken",
+ "type": "bytes",
+ "index": false
+ },
+ {
+ "name": "poolAddress",
+ "type": "bytes",
+ "index": false
+ },
+ {
+ "name": "previousPoolAddress",
+ "type": "bytes",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "RateLimitConfigured",
+ "fields": [
+ {
+ "name": "chainSelector",
+ "type": "u64",
+ "index": false
+ },
+ {
+ "name": "outboundRateLimit",
+ "type": {
+ "defined": "RateLimitConfig"
+ },
+ "index": false
+ },
+ {
+ "name": "inboundRateLimit",
+ "type": {
+ "defined": "RateLimitConfig"
+ },
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "RemoteChainRemoved",
+ "fields": [
+ {
+ "name": "chainSelector",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "RouterUpdated",
+ "fields": [
+ {
+ "name": "oldAuthority",
+ "type": "publicKey",
+ "index": false
+ },
+ {
+ "name": "newAuthority",
+ "type": "publicKey",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "TokensConsumed",
+ "fields": [
+ {
+ "name": "tokens",
+ "type": "u64",
+ "index": false
+ }
+ ]
+ },
+ {
+ "name": "ConfigChanged",
+ "fields": [
+ {
+ "name": "config",
+ "type": {
+ "defined": "RateLimitConfig"
+ },
+ "index": false
+ }
+ ]
+ }
+ ],
+ "errors": [
+ {
+ "code": 6000,
+ "name": "InvalidInitPoolPermissions",
+ "msg": "Pool authority does not match token mint owner"
+ },
+ {
+ "code": 6001,
+ "name": "Unauthorized",
+ "msg": "Unauthorized"
+ },
+ {
+ "code": 6002,
+ "name": "InvalidInputs",
+ "msg": "Invalid inputs"
+ },
+ {
+ "code": 6003,
+ "name": "InvalidPoolCaller",
+ "msg": "Caller is not ramp on router"
+ },
+ {
+ "code": 6004,
+ "name": "InvalidSourcePoolAddress",
+ "msg": "Invalid source pool address"
+ },
+ {
+ "code": 6005,
+ "name": "InvalidToken",
+ "msg": "Invalid token"
+ },
+ {
+ "code": 6006,
+ "name": "InvalidTokenAmountConversion",
+ "msg": "Invalid token amount conversion"
+ },
+ {
+ "code": 6007,
+ "name": "RLBucketOverfilled",
+ "msg": "RateLimit: bucket overfilled"
+ },
+ {
+ "code": 6008,
+ "name": "RLMaxCapacityExceeded",
+ "msg": "RateLimit: max capacity exceeded"
+ },
+ {
+ "code": 6009,
+ "name": "RLRateLimitReached",
+ "msg": "RateLimit: rate limit reached"
+ },
+ {
+ "code": 6010,
+ "name": "RLInvalidRateLimitRate",
+ "msg": "RateLimit: invalid rate limit rate"
+ },
+ {
+ "code": 6011,
+ "name": "RLDisabledNonZeroRateLimit",
+ "msg": "RateLimit: disabled non-zero rate limit"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/chains/solana/contracts/tests/ccip/ccip_errors.go b/chains/solana/contracts/tests/ccip/ccip_errors.go
new file mode 100644
index 000000000..8d39e8b2e
--- /dev/null
+++ b/chains/solana/contracts/tests/ccip/ccip_errors.go
@@ -0,0 +1,63 @@
+package contracts
+
+import (
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+type Ocr3Error ag_binary.BorshEnum
+
+// This Errors should be automatically generated by Anchor-Go but they only support one error per program
+const (
+ Ocr3ErrorInvalidConfigFMustBePositive Ocr3Error = iota
+ Ocr3ErrorInvalidConfigTooManyTransmitters
+ Ocr3ErrorInvalidConfigTooManySigners
+ Ocr3ErrorInvalidConfigFIsTooHigh
+ Ocr3ErrorInvalidConfigRepeatedOracle
+ Ocr3ErrorWrongMessageLength
+ Ocr3ErrorConfigDigestMismatch
+ Ocr3ErrorWrongNumberOfSignatures
+ Ocr3ErrorUnauthorizedTransmitter
+ Ocr3ErrorUnauthorizedSigner
+ Ocr3ErrorNonUniqueSignatures
+ Ocr3ErrorOracleCannotBeZeroAddress
+ Ocr3ErrorStaticConfigCannotBeChanged
+ Ocr3ErrorInvalidPluginType
+ Ocr3ErrorInvalidSignature
+)
+
+func (value Ocr3Error) String() string {
+ switch value {
+ case Ocr3ErrorInvalidConfigFMustBePositive:
+ return "InvalidConfigFMustBePositive"
+ case Ocr3ErrorInvalidConfigTooManyTransmitters:
+ return "InvalidConfigTooManyTransmitters"
+ case Ocr3ErrorInvalidConfigTooManySigners:
+ return "InvalidConfigTooManySigners"
+ case Ocr3ErrorInvalidConfigFIsTooHigh:
+ return "InvalidConfigFIsTooHigh"
+ case Ocr3ErrorInvalidConfigRepeatedOracle:
+ return "InvalidConfigRepeatedOracle"
+ case Ocr3ErrorWrongMessageLength:
+ return "WrongMessageLength"
+ case Ocr3ErrorConfigDigestMismatch:
+ return "ConfigDigestMismatch"
+ case Ocr3ErrorWrongNumberOfSignatures:
+ return "WrongNumberOfSignatures"
+ case Ocr3ErrorUnauthorizedTransmitter:
+ return "UnauthorizedTransmitter"
+ case Ocr3ErrorUnauthorizedSigner:
+ return "UnauthorizedSigner"
+ case Ocr3ErrorNonUniqueSignatures:
+ return "NonUniqueSignatures"
+ case Ocr3ErrorOracleCannotBeZeroAddress:
+ return "OracleCannotBeZeroAddress"
+ case Ocr3ErrorStaticConfigCannotBeChanged:
+ return "StaticConfigCannotBeChanged"
+ case Ocr3ErrorInvalidPluginType:
+ return "InvalidPluginType"
+ case Ocr3ErrorInvalidSignature:
+ return "InvalidSignature"
+ default:
+ return ""
+ }
+}
diff --git a/chains/solana/contracts/tests/ccip/ccip_events.go b/chains/solana/contracts/tests/ccip/ccip_events.go
new file mode 100644
index 000000000..d586da53b
--- /dev/null
+++ b/chains/solana/contracts/tests/ccip/ccip_events.go
@@ -0,0 +1,67 @@
+package contracts
+
+import (
+ "github.com/gagliardetto/solana-go"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router"
+)
+
+// Events - temporary event struct to decode
+// anchor-go does not support events
+// https://github.com/fragmetric-labs/solana-anchor-go does but requires upgrade to anchor >= v0.30.0
+type EventCCIPMessageSent struct {
+ Discriminator [8]byte
+ DestinationChainSelector uint64
+ SequenceNumber uint64
+ Message ccip_router.Solana2AnyRampMessage
+}
+
+type EventCommitReportAccepted struct {
+ Discriminator [8]byte
+ Report ccip_router.MerkleRoot
+}
+
+type EventTransmitted struct {
+ Discriminator [8]byte
+ OcrPluginType uint8
+ ConfigDigest [32]byte
+ SequenceNumber uint64
+}
+
+type EventExecutionStateChanged struct {
+ Discriminator [8]byte
+ SourceChainSelector uint64
+ SequenceNumber uint64
+ MessageID [32]byte
+ MessageHash [32]byte
+ State ccip_router.MessageExecutionState
+}
+
+type EventSkippedAlreadyExecutedMessage struct {
+ Discriminator [8]byte
+ SourceChainSelector uint64
+ SequenceNumber uint64
+}
+
+type EventConfigSet struct {
+ Discriminator [8]byte
+ OcrPluginType uint8
+ ConfigDigest [32]byte
+ Signers [][20]uint8
+ Transmitters []solana.PublicKey
+ F uint8
+}
+
+type UsdPerTokenUpdated struct {
+ Discriminator [8]byte
+ Token solana.PublicKey
+ Value [28]byte
+ Timestamp int64
+}
+
+type UsdPerUnitGasUpdated struct {
+ Discriminator [8]byte
+ DestChain uint64
+ Value [28]byte
+ Timestamp int64
+}
diff --git a/chains/solana/contracts/tests/ccip/ccip_messages.go b/chains/solana/contracts/tests/ccip/ccip_messages.go
new file mode 100644
index 000000000..8909f94e5
--- /dev/null
+++ b/chains/solana/contracts/tests/ccip/ccip_messages.go
@@ -0,0 +1,192 @@
+package contracts
+
+import (
+ "bytes"
+ "context"
+ "crypto/sha256"
+ "encoding/binary"
+ "encoding/hex"
+ "testing"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router"
+)
+
+func HashCommitReport(ctx [3][32]byte, report ccip_router.CommitInput) ([]byte, error) {
+ hash := sha256.New()
+ encodedReport, err := bin.MarshalBorsh(report)
+ if err != nil {
+ return nil, err
+ }
+
+ reportLen := uint16(len(encodedReport)) //nolint:gosec // max U16 larger than solana transaction size
+ if err := binary.Write(hash, binary.LittleEndian, reportLen); err != nil {
+ return nil, err
+ }
+ if _, err := hash.Write(encodedReport); err != nil {
+ return nil, err
+ }
+ if _, err := hash.Write(ctx[0][:]); err != nil {
+ return nil, err
+ }
+ if _, err := hash.Write(ctx[1][:]); err != nil {
+ return nil, err
+ }
+ if _, err := hash.Write(ctx[2][:]); err != nil {
+ return nil, err
+ }
+ return hash.Sum(nil), nil
+}
+
+func CreateNextMessage(ctx context.Context, solanaGoClient *rpc.Client, t *testing.T) (ccip_router.Any2SolanaRampMessage, [32]byte) {
+ nextSeq := NextSequenceNumber(ctx, solanaGoClient, config.EvmChainStatePDA, t)
+ msg := CreateDefaultMessageWith(config.EvmChainSelector, nextSeq)
+
+ hash, err := HashEvmToSolanaMessage(msg, config.OnRampAddress)
+ require.NoError(t, err)
+ return msg, [32]byte(hash)
+}
+
+func NextSequenceNumber(ctx context.Context, solanaGoClient *rpc.Client, chainStatePDA solana.PublicKey, t *testing.T) uint64 {
+ var chainStateAccount ccip_router.ChainState
+ err := utils.GetAccountDataBorshInto(ctx, solanaGoClient, chainStatePDA, config.DefaultCommitment, &chainStateAccount)
+ require.NoError(t, err)
+ return chainStateAccount.SourceChain.State.MinSeqNr
+}
+
+func CreateDefaultMessageWith(sourceChainSelector uint64, sequenceNumber uint64) ccip_router.Any2SolanaRampMessage {
+ sourceHash, _ := hex.DecodeString("4571dc5d4711693551f54a96307bf71121e2a1abd21d8ae04b8e05f447821064")
+ var messageID [32]byte
+ copy(messageID[:], sourceHash)
+
+ message := ccip_router.Any2SolanaRampMessage{
+ Header: ccip_router.RampMessageHeader{
+ MessageId: messageID,
+ SourceChainSelector: sourceChainSelector,
+ DestChainSelector: config.SolanaChainSelector,
+ SequenceNumber: sequenceNumber,
+ Nonce: 0,
+ },
+ Sender: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ Receiver: config.ReceiverExternalExecutionConfigPDA,
+ ExtraArgs: ccip_router.SolanaExtraArgs{
+ ComputeUnits: 1000,
+ Accounts: []ccip_router.SolanaAccountMeta{
+ {Pubkey: config.CcipReceiverProgram},
+ {Pubkey: config.ReceiverTargetAccountPDA, IsWritable: true},
+ {Pubkey: solana.SystemProgramID, IsWritable: false},
+ },
+ },
+ }
+ return message
+}
+
+func MakeEvmToSolanaMessage(t *testing.T, ccipReceiver solana.PublicKey, evmChainSelector uint64, solanaChainSelector uint64, data []byte) (ccip_router.Any2SolanaRampMessage, [32]byte) {
+ msg := CreateDefaultMessageWith(evmChainSelector, 1)
+ msg.Header.DestChainSelector = solanaChainSelector
+ msg.Receiver = ccipReceiver
+ msg.Data = data
+
+ hash, err := HashEvmToSolanaMessage(msg, config.OnRampAddress)
+ require.NoError(t, err)
+ msg.Header.MessageId = [32]byte(hash)
+ return msg, msg.Header.MessageId
+}
+
+func HashEvmToSolanaMessage(msg ccip_router.Any2SolanaRampMessage, onRampAddress []byte) ([]byte, error) {
+ hash := sha256.New()
+
+ hash.Write([]byte("Any2SolanaMessageHashV1"))
+
+ if err := binary.Write(hash, binary.BigEndian, msg.Header.SourceChainSelector); err != nil {
+ return nil, err
+ }
+ if err := binary.Write(hash, binary.BigEndian, msg.Header.DestChainSelector); err != nil {
+ return nil, err
+ }
+ // Push OnRamp Size to ensure that the hash is unique
+ if _, err := hash.Write([]byte{uint8(len(onRampAddress))}); err != nil { //nolint:gosec
+ return nil, err
+ }
+ if _, err := hash.Write(onRampAddress); err != nil {
+ return nil, err
+ }
+ if _, err := hash.Write(msg.Header.MessageId[:]); err != nil {
+ return nil, err
+ }
+ if _, err := hash.Write(msg.Receiver[:]); err != nil {
+ return nil, err
+ }
+ if err := binary.Write(hash, binary.BigEndian, msg.Header.SequenceNumber); err != nil {
+ return nil, err
+ }
+ if err := binary.Write(hash, binary.BigEndian, msg.ExtraArgs.ComputeUnits); err != nil {
+ return nil, err
+ }
+ // Push accounts size
+ if _, err := hash.Write([]byte{uint8(len(msg.ExtraArgs.Accounts))}); err != nil { //nolint:gosec
+ return nil, err
+ }
+ accountsBytes, err := bin.MarshalBorsh(msg.ExtraArgs.Accounts)
+ if err != nil {
+ return nil, err
+ }
+ if _, err := hash.Write(accountsBytes); err != nil {
+ return nil, err
+ }
+ if err := binary.Write(hash, binary.BigEndian, msg.Header.Nonce); err != nil {
+ return nil, err
+ }
+ // Push Sender Size to ensure that the hash is unique
+ if _, err := hash.Write([]byte{uint8(len(msg.Sender))}); err != nil { //nolint:gosec
+ return nil, err
+ }
+ if _, err := hash.Write(msg.Sender); err != nil {
+ return nil, err
+ }
+ // Push Data Size to ensure that the hash is unique
+ dataLen := uint16(len(msg.Data)) //nolint:gosec // max U16 larger than solana transaction size
+ if err := binary.Write(hash, binary.BigEndian, dataLen); err != nil {
+ return nil, err
+ }
+ if _, err := hash.Write(msg.Data); err != nil {
+ return nil, err
+ }
+
+ return hash.Sum(nil), nil
+}
+
+// hashPair hashes two byte slices and returns the result as a byte slice.
+func hashPair(a, b []byte) []byte {
+ h := sha256.New()
+ if bytes.Compare(a, b) < 0 {
+ h.Write(a)
+ h.Write(b)
+ } else {
+ h.Write(b)
+ h.Write(a)
+ }
+ return h.Sum(nil)
+}
+
+// merkleFrom computes the Merkle root from a slice of byte slices.
+func MerkleFrom(data [][]byte) []byte {
+ if len(data) == 1 {
+ return data[0]
+ }
+
+ hash := hashPair(data[0], data[1])
+
+ for i := 2; i < len(data); i++ {
+ hash = hashPair(hash, data[i])
+ }
+
+ return hash
+}
diff --git a/chains/solana/contracts/tests/ccip/ccip_messages_test.go b/chains/solana/contracts/tests/ccip/ccip_messages_test.go
new file mode 100644
index 000000000..1f333053e
--- /dev/null
+++ b/chains/solana/contracts/tests/ccip/ccip_messages_test.go
@@ -0,0 +1,43 @@
+package contracts
+
+import (
+ "encoding/hex"
+ "testing"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router"
+)
+
+func TestHashEvmToSolanaMessage(t *testing.T) {
+ t.Parallel()
+
+ sender := make([]byte, 32)
+ copy(sender, []byte{1, 2, 3})
+
+ h, err := HashEvmToSolanaMessage(ccip_router.Any2SolanaRampMessage{
+ Sender: sender,
+ Receiver: solana.MustPublicKeyFromBase58("DS2tt4BX7YwCw7yrDNwbAdnYrxjeCPeGJbHmZEYC8RTb"),
+ Data: []byte{4, 5, 6},
+ Header: ccip_router.RampMessageHeader{
+ MessageId: [32]uint8{8, 5, 3},
+ SourceChainSelector: 67,
+ DestChainSelector: 78,
+ SequenceNumber: 89,
+ Nonce: 90,
+ },
+ ExtraArgs: ccip_router.SolanaExtraArgs{
+ ComputeUnits: 1000,
+ Accounts: []ccip_router.SolanaAccountMeta{
+ {
+ Pubkey: solana.MustPublicKeyFromBase58("DS2tt4BX7YwCw7yrDNwbAdnYrxjeCPeGJbHmZEYC8RTb"),
+ IsWritable: true,
+ },
+ },
+ },
+ }, config.OnRampAddress)
+ require.NoError(t, err)
+ require.Equal(t, "03da97f96c82237d8a8ab0f68d4f7ba02afe188b4a876f348278fbf2226312ed", hex.EncodeToString(h))
+}
diff --git a/chains/solana/contracts/tests/ccip/ccip_router_test.go b/chains/solana/contracts/tests/ccip/ccip_router_test.go
new file mode 100644
index 000000000..8460d27c1
--- /dev/null
+++ b/chains/solana/contracts/tests/ccip/ccip_router_test.go
@@ -0,0 +1,4705 @@
+package contracts
+
+import (
+ "bytes"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "sort"
+ "testing"
+
+ "github.com/decred/dcrd/dcrec/secp256k1/v4"
+ "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_receiver"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/token_pool"
+)
+
+const MaxCU = 1_400_000 // this is Solana's hard max Compute Unit limit
+
+func TestCCIPRouter(t *testing.T) {
+ t.Parallel()
+
+ ccip_router.SetProgramID(config.CcipRouterProgram)
+ ccip_receiver.SetProgramID(config.CcipReceiverProgram)
+ token_pool.SetProgramID(config.CcipTokenPoolProgram)
+
+ ctx := tests.Context(t)
+
+ user, gerr := solana.NewRandomPrivateKey()
+ require.NoError(t, gerr)
+ anotherUser, gerr := solana.NewRandomPrivateKey()
+ require.NoError(t, gerr)
+ admin, gerr := solana.NewRandomPrivateKey()
+ require.NoError(t, gerr)
+ anotherAdmin, gerr := solana.NewRandomPrivateKey()
+ require.NoError(t, gerr)
+ tokenPoolAdmin, gerr := solana.NewRandomPrivateKey()
+ require.NoError(t, gerr)
+ anotherTokenPoolAdmin, gerr := solana.NewRandomPrivateKey()
+ require.NoError(t, gerr)
+
+ var nonceEvmPDA solana.PublicKey
+
+ // billing
+ type AccountsPerToken struct {
+ name string
+ program solana.PublicKey
+ mint solana.PublicKey
+ billingATA solana.PublicKey
+ userATA solana.PublicKey
+ anotherUserATA solana.PublicKey
+ billingConfigPDA solana.PublicKey
+ // add other accounts as needed
+ }
+ wsol := AccountsPerToken{name: "WSOL (pre-2022)"}
+ token2022 := AccountsPerToken{name: "Token2022 sample token"}
+ billingTokens := []*AccountsPerToken{&wsol, &token2022}
+
+ solanaGoClient := utils.DeployAllPrograms(t, utils.PathToAnchorConfig, admin)
+
+ // token addresses
+ token0, gerr := NewTokenPool(config.Token2022Program)
+ require.NoError(t, gerr)
+ token1, gerr := NewTokenPool(config.Token2022Program)
+ require.NoError(t, gerr)
+
+ signers, transmitters, getTransmitter := utils.GenerateSignersAndTransmitters(t, config.MaxOracles)
+
+ signerAddresses := [][20]byte{}
+ transmitterPubKeys := []solana.PublicKey{}
+ for _, v := range signers {
+ signerAddresses = append(signerAddresses, v.Address)
+ }
+ for _, v := range transmitters {
+ transmitterPubKeys = append(transmitterPubKeys, v.PublicKey())
+ }
+ // sort to match onchain sort
+ sort.SliceStable(signerAddresses, func(i, j int) bool {
+ return bytes.Compare(signerAddresses[i][:], signerAddresses[j][:]) == 1
+ })
+ sort.SliceStable(transmitterPubKeys, func(i, j int) bool {
+ return bytes.Compare(transmitterPubKeys[i].Bytes(), transmitterPubKeys[j].Bytes()) == 1
+ })
+
+ getBalance := func(tokenAccount solana.PublicKey) uint64 {
+ _, amount, berr := utils.TokenBalance(ctx, solanaGoClient, tokenAccount, config.DefaultCommitment)
+ require.NoError(t, berr)
+ return uint64(amount)
+ }
+
+ getTokenConfigPDA := func(mint solana.PublicKey) solana.PublicKey {
+ tokenBillingPDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("fee_billing_token_config"), mint.Bytes()}, config.CcipRouterProgram)
+ return tokenBillingPDA
+ }
+
+ validSourceChainConfig := ccip_router.SourceChainConfig{
+ OnRamp: config.OnRampAddress,
+ IsEnabled: true,
+ }
+ validDestChainConfig := ccip_router.DestChainConfig{
+ IsEnabled: true,
+
+ // minimal valid config
+ DefaultTxGasLimit: 1,
+ MaxPerMsgGasLimit: 100,
+ ChainFamilySelector: [4]uint8{0, 1, 2, 3},
+ }
+
+ var commitLookupTable map[solana.PublicKey]solana.PublicKeySlice
+
+ t.Run("setup", func(t *testing.T) {
+ t.Run("funding", func(t *testing.T) {
+ utils.FundAccounts(ctx, append(transmitters, user, anotherUser, admin, anotherAdmin, tokenPoolAdmin, anotherTokenPoolAdmin), solanaGoClient, t)
+ })
+
+ t.Run("receiver", func(t *testing.T) {
+ instruction, ixErr := ccip_receiver.NewInitializeInstruction(
+ config.ReceiverTargetAccountPDA,
+ config.ReceiverExternalExecutionConfigPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, ixErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment)
+ })
+
+ t.Run("token", func(t *testing.T) {
+ ixs, ixErr := utils.CreateToken(ctx, token0.Program, token0.Mint.PublicKey(), tokenPoolAdmin.PublicKey(), 0, solanaGoClient, config.DefaultCommitment)
+ require.NoError(t, ixErr)
+
+ ixsAnotherToken, anotherTokenErr := utils.CreateToken(ctx, token1.Program, token1.Mint.PublicKey(), anotherTokenPoolAdmin.PublicKey(), 0, solanaGoClient, config.DefaultCommitment)
+ require.NoError(t, anotherTokenErr)
+
+ // mint tokens to user
+ ixAta, addr, ataErr := utils.CreateAssociatedTokenAccount(token0.Program, token0.Mint.PublicKey(), user.PublicKey(), tokenPoolAdmin.PublicKey())
+ require.NoError(t, ataErr)
+ ixMintTo, mintErr := utils.MintTo(10000000, token0.Program, token0.Mint.PublicKey(), addr, tokenPoolAdmin.PublicKey())
+ require.NoError(t, mintErr)
+
+ // create ATA for receiver (receiver program address)
+ ixAtaReceiver, recAddr, recErr := utils.CreateAssociatedTokenAccount(token0.Program, token0.Mint.PublicKey(), config.ReceiverExternalExecutionConfigPDA, tokenPoolAdmin.PublicKey())
+ require.NoError(t, recErr)
+
+ token0.User[user.PublicKey()] = addr
+ token0.User[config.ReceiverExternalExecutionConfigPDA] = recAddr
+ ixs = append(ixs, ixAta, ixMintTo, ixAtaReceiver)
+ ixs = append(ixs, ixsAnotherToken...)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, ixs, tokenPoolAdmin, config.DefaultCommitment, utils.AddSigners(token0.Mint, token1.Mint, anotherTokenPoolAdmin))
+ })
+
+ t.Run("token-pool", func(t *testing.T) {
+ token0.PoolProgram = config.CcipTokenPoolProgram
+ token0.AdditionalAccounts = append(token0.AdditionalAccounts, solana.MemoProgramID) // add test additional accounts in pool interactions
+ var err error
+ token0.PoolConfig, err = TokenPoolConfigAddress(token0.Mint.PublicKey())
+ require.NoError(t, err)
+ token0.PoolSigner, err = TokenPoolSignerAddress(token0.Mint.PublicKey())
+ require.NoError(t, err)
+
+ ixInit, err := token_pool.NewInitializeInstruction(
+ token_pool.BurnAndMint_PoolType,
+ config.ExternalTokenPoolsSignerPDA,
+ token0.PoolConfig,
+ token0.Mint.PublicKey(),
+ token0.PoolSigner,
+ tokenPoolAdmin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ ixAta, addr, err := utils.CreateAssociatedTokenAccount(token0.Program, token0.Mint.PublicKey(), token0.PoolSigner, tokenPoolAdmin.PublicKey())
+ require.NoError(t, err)
+ token0.PoolTokenAccount = addr
+ token0.User[token0.PoolSigner] = token0.PoolTokenAccount
+
+ ixAuth, err := utils.SetTokenMintAuthority(token0.Program, token0.PoolSigner, token0.Mint.PublicKey(), tokenPoolAdmin.PublicKey())
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixInit, ixAta, ixAuth}, tokenPoolAdmin, config.DefaultCommitment)
+
+ // Lookup Table for Tokens
+ require.NoError(t, token0.SetupLookupTable(ctx, t, solanaGoClient, tokenPoolAdmin))
+ token0Entries := token0.ToTokenPoolEntries()
+ require.NoError(t, token1.SetupLookupTable(ctx, t, solanaGoClient, anotherTokenPoolAdmin))
+ token1Entries := token1.ToTokenPoolEntries()
+
+ // Verify Lookup tables where correctly initialized
+ lookupTableEntries0, err := utils.GetAddressLookupTable(ctx, solanaGoClient, token0.PoolLookupTable)
+ require.NoError(t, err)
+ require.Equal(t, len(token0Entries), len(lookupTableEntries0))
+ require.Equal(t, token0Entries, lookupTableEntries0)
+
+ lookupTableEntries1, err := utils.GetAddressLookupTable(ctx, solanaGoClient, token1.PoolLookupTable)
+ require.NoError(t, err)
+ require.Equal(t, len(token1Entries), len(lookupTableEntries1))
+ require.Equal(t, token1Entries, lookupTableEntries1)
+ })
+
+ t.Run("billing", func(t *testing.T) {
+ //////////
+ // WSOL //
+ //////////
+
+ wsolPDA, _, aerr := solana.FindProgramAddress([][]byte{config.BillingTokenConfigPrefix, solana.SolMint.Bytes()}, ccip_router.ProgramID)
+ require.NoError(t, aerr)
+ wsolReceiver, _, rerr := utils.FindAssociatedTokenAddress(solana.TokenProgramID, solana.SolMint, config.BillingSignerPDA)
+ require.NoError(t, rerr)
+ wsolUserATA, _, uerr := utils.FindAssociatedTokenAddress(solana.TokenProgramID, solana.SolMint, user.PublicKey())
+ require.NoError(t, uerr)
+ wsolAnotherUserATA, _, auerr := utils.FindAssociatedTokenAddress(solana.TokenProgramID, solana.SolMint, anotherUser.PublicKey())
+ require.NoError(t, auerr)
+
+ // persist the WSOL config for later use
+ wsol.program = solana.TokenProgramID
+ wsol.mint = solana.SolMint
+ wsol.billingConfigPDA = wsolPDA
+ wsol.userATA = wsolUserATA
+ wsol.anotherUserATA = wsolAnotherUserATA
+ wsol.billingATA = wsolReceiver
+
+ ///////////////
+ // Token2022 //
+ ///////////////
+
+ // Create Token2022 token, managed by "admin" (not "anotherAdmin" who manages CCIP).
+ // Random-generated key, but fixing it adds determinism to tests to make it easier to debug.
+ mintPrivK := solana.MustPrivateKeyFromBase58("32YVeJArcWWWV96fztfkRQhohyFz5Hwno93AeGVrN4g2LuFyvwznrNd9A6tbvaTU6BuyBsynwJEMLre8vSy3CrVU")
+
+ mintPubK := mintPrivK.PublicKey()
+ ixToken, terr := utils.CreateToken(ctx, config.Token2022Program, mintPubK, admin.PublicKey(), 9, solanaGoClient, config.DefaultCommitment)
+ require.NoError(t, terr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, ixToken, admin, config.DefaultCommitment, utils.AddSigners(mintPrivK))
+
+ token2022PDA, _, aerr := solana.FindProgramAddress([][]byte{config.BillingTokenConfigPrefix, mintPubK.Bytes()}, ccip_router.ProgramID)
+ require.NoError(t, aerr)
+ token2022Receiver, _, rerr := utils.FindAssociatedTokenAddress(config.Token2022Program, mintPubK, config.BillingSignerPDA)
+ require.NoError(t, rerr)
+ token2022UserATA, _, uerr := utils.FindAssociatedTokenAddress(config.Token2022Program, mintPubK, user.PublicKey())
+ require.NoError(t, uerr)
+ token2022AnotherUserATA, _, auerr := utils.FindAssociatedTokenAddress(config.Token2022Program, mintPubK, anotherUser.PublicKey())
+ require.NoError(t, auerr)
+
+ // persist the Token2022 billing config for later use
+ token2022.program = config.Token2022Program
+ token2022.mint = mintPubK
+ token2022.billingConfigPDA = token2022PDA
+ token2022.userATA = token2022UserATA
+ token2022.anotherUserATA = token2022AnotherUserATA
+ token2022.billingATA = token2022Receiver
+ })
+
+ t.Run("Commit price updates address lookup table", func(t *testing.T) {
+ // Create single Address Lookup Table, to be used in all commit tests.
+ // Create it early in the test suite (a "setup" step) to let it warm up with more than enough time,
+ // as otherwise it can slow down tests for ~20 seconds.
+
+ lookupEntries := []solana.PublicKey{
+ // static accounts that are always needed
+ ccip_router.ProgramID,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+
+ // remaining_accounts that are only sometimes needed
+ wsol.billingConfigPDA,
+ token2022.billingConfigPDA,
+ config.EvmChainStatePDA,
+ config.SolanaChainStatePDA,
+ }
+ lookupTableAddr, err := utils.SetupLookupTable(ctx, t, solanaGoClient, admin, lookupEntries)
+ require.NoError(t, err)
+
+ commitLookupTable = map[solana.PublicKey]solana.PublicKeySlice{
+ lookupTableAddr: lookupEntries,
+ }
+ })
+ })
+
+ //////////////////////////
+ // Config Account Tests //
+ //////////////////////////
+
+ t.Run("Config", func(t *testing.T) {
+ t.Run("Is initialized", func(t *testing.T) {
+ invalidSolanaChainSelector := uint64(17)
+ defaultGasLimit := bin.Uint128{Lo: 3000, Hi: 0, Endianness: nil}
+ allowOutOfOrderExecution := true
+
+ // get program data account
+ data, err := solanaGoClient.GetAccountInfoWithOpts(ctx, config.CcipRouterProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, err)
+
+ // Decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ instruction, err := ccip_router.NewInitializeInstruction(
+ invalidSolanaChainSelector,
+ defaultGasLimit,
+ allowOutOfOrderExecution,
+ config.EnableExecutionAfter,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.CcipRouterProgram,
+ programData.Address,
+ config.ExternalExecutionConfigPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // Fetch account data
+ var configAccount ccip_router.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.RouterConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ require.Equal(t, uint64(17), configAccount.SolanaChainSelector)
+ require.Equal(t, defaultGasLimit, configAccount.DefaultGasLimit)
+ require.Equal(t, uint8(1), configAccount.DefaultAllowOutOfOrderExecution)
+
+ nonceEvmPDA, err = getNoncePDA(config.EvmChainSelector, user.PublicKey())
+ require.NoError(t, err)
+ })
+
+ t.Run("When admin updates the default gas limit it's updated", func(t *testing.T) {
+ newGasLimit := bin.Uint128{Lo: 5000, Hi: 0}
+
+ instruction, err := ccip_router.NewUpdateDefaultGasLimitInstruction(
+ newGasLimit,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var configAccount ccip_router.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.RouterConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, uint64(17), configAccount.SolanaChainSelector)
+ require.Equal(t, newGasLimit, configAccount.DefaultGasLimit)
+ require.Equal(t, uint8(1), configAccount.DefaultAllowOutOfOrderExecution)
+ })
+
+ t.Run("When admin updates the default allow out of order execution it's updated", func(t *testing.T) {
+ instruction, err := ccip_router.NewUpdateDefaultAllowOutOfOrderExecutionInstruction(
+ false,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var configAccount ccip_router.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.RouterConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, uint64(17), configAccount.SolanaChainSelector)
+ require.Equal(t, bin.Uint128{Lo: 5000, Hi: 0}, configAccount.DefaultGasLimit)
+ require.Equal(t, uint8(0), configAccount.DefaultAllowOutOfOrderExecution)
+ })
+
+ t.Run("When admin updates the solana chain selector it's updated", func(t *testing.T) {
+ instruction, err := ccip_router.NewUpdateSolanaChainSelectorInstruction(
+ config.SolanaChainSelector,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var configAccount ccip_router.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.RouterConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, config.SolanaChainSelector, configAccount.SolanaChainSelector)
+ require.Equal(t, bin.Uint128{Lo: 5000, Hi: 0}, configAccount.DefaultGasLimit)
+ require.Equal(t, uint8(0), configAccount.DefaultAllowOutOfOrderExecution)
+ })
+
+ type InvalidChainBillingInputTest struct {
+ Name string
+ Selector uint64
+ Conf ccip_router.DestChainConfig
+ SkipOnUpdate bool
+ }
+ invalidInputTests := []InvalidChainBillingInputTest{
+ {
+ Name: "Zero DefaultTxGasLimit",
+ Selector: config.EvmChainSelector,
+ Conf: ccip_router.DestChainConfig{
+ DefaultTxGasLimit: 0,
+ MaxPerMsgGasLimit: validDestChainConfig.MaxPerMsgGasLimit,
+ ChainFamilySelector: validDestChainConfig.ChainFamilySelector,
+ },
+ },
+ {
+ Name: "Zero DestChainSelector",
+ Selector: 0,
+ Conf: validDestChainConfig,
+ SkipOnUpdate: true, // as the 0-selector is invalid, the config account can never be initialized
+ },
+ {
+ Name: "Zero ChainFamilySelector",
+ Selector: config.EvmChainSelector,
+ Conf: ccip_router.DestChainConfig{
+ DefaultTxGasLimit: validDestChainConfig.DefaultTxGasLimit,
+ MaxPerMsgGasLimit: validDestChainConfig.MaxPerMsgGasLimit,
+ ChainFamilySelector: [4]uint8{0, 0, 0, 0},
+ },
+ },
+ {
+ Name: "DefaultTxGasLimit > MaxPerMsgGasLimit",
+ Selector: config.EvmChainSelector,
+ Conf: ccip_router.DestChainConfig{
+ DefaultTxGasLimit: 100,
+ MaxPerMsgGasLimit: 1,
+ ChainFamilySelector: validDestChainConfig.ChainFamilySelector,
+ },
+ },
+ }
+ getChainStatePDA := func(selector uint64) solana.PublicKey {
+ chainStatePDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("chain_state"), binary.LittleEndian.AppendUint64([]byte{}, selector)}, config.CcipRouterProgram)
+ return chainStatePDA
+ }
+
+ t.Run("When and admin adds a chain selector with invalid dest chain config, it fails", func(t *testing.T) {
+ for _, test := range invalidInputTests {
+ t.Run(test.Name, func(t *testing.T) {
+ instruction, err := ccip_router.NewAddChainSelectorInstruction(
+ test.Selector,
+ validSourceChainConfig,
+ test.Conf, // here is the invalid dest config data
+ getChainStatePDA(test.Selector),
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment, []string{"Error Code: " + ccip_router.InvalidInputs_CcipRouterError.String()})
+ require.NotNil(t, result)
+ })
+ }
+ })
+
+ t.Run("When an unauthorized user tries to add a chain selector, it fails", func(t *testing.T) {
+ instruction, err := ccip_router.NewAddChainSelectorInstruction(
+ config.EvmChainSelector,
+ validSourceChainConfig,
+ validDestChainConfig,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ user.PublicKey(), // not an admin
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + ccip_router.Unauthorized_CcipRouterError.String()})
+ require.NotNil(t, result)
+ })
+
+ t.Run("When admin adds a chain selector it's added on the list", func(t *testing.T) {
+ instruction, err := ccip_router.NewAddChainSelectorInstruction(
+ config.EvmChainSelector,
+ validSourceChainConfig,
+ validDestChainConfig,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var chainStateAccount ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &chainStateAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, uint64(1), chainStateAccount.SourceChain.State.MinSeqNr)
+ require.Equal(t, true, chainStateAccount.SourceChain.Config.IsEnabled)
+ require.Equal(t, config.OnRampAddress, chainStateAccount.SourceChain.Config.OnRamp)
+ require.Equal(t, uint64(0), chainStateAccount.DestChain.State.SequenceNumber)
+ require.Equal(t, validDestChainConfig, chainStateAccount.DestChain.Config)
+ })
+
+ t.Run("When admin adds another chain selector it's also added on the list", func(t *testing.T) {
+ // Using another chain, solana as an example (which allows Solana -> Solana messages)
+ // Regardless of whether we allow Solana -> Solana in mainnet, it's easy to use for tests here
+ instruction, err := ccip_router.NewAddChainSelectorInstruction(
+ config.SolanaChainSelector,
+ ccip_router.SourceChainConfig{
+ OnRamp: config.CcipRouterProgram[:], // the router is the Solana onramp
+ IsEnabled: true,
+ },
+ ccip_router.DestChainConfig{
+ IsEnabled: true,
+ // minimal valid config
+ DefaultTxGasLimit: 1,
+ MaxPerMsgGasLimit: 100,
+ ChainFamilySelector: [4]uint8{3, 2, 1, 0}},
+ config.SolanaChainStatePDA,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var chainStateAccount ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.SolanaChainStatePDA, config.DefaultCommitment, &chainStateAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, uint64(1), chainStateAccount.SourceChain.State.MinSeqNr)
+ require.Equal(t, true, chainStateAccount.SourceChain.Config.IsEnabled)
+ require.Equal(t, config.CcipRouterProgram[:], chainStateAccount.SourceChain.Config.OnRamp)
+ require.Equal(t, uint64(0), chainStateAccount.DestChain.State.SequenceNumber)
+ })
+
+ t.Run("When a non-admin tries to disable the chain selector, it fails", func(t *testing.T) {
+ t.Run("Source", func(t *testing.T) {
+ ix, err := ccip_router.NewDisableSourceChainSelectorInstruction(
+ config.EvmChainSelector,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, user, config.DefaultCommitment, []string{"Error Code: " + ccip_router.Unauthorized_CcipRouterError.String()})
+ require.NotNil(t, result)
+ })
+
+ t.Run("Dest", func(t *testing.T) {
+ ix, err := ccip_router.NewDisableDestChainSelectorInstruction(
+ config.EvmChainSelector,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, user, config.DefaultCommitment, []string{"Error Code: " + ccip_router.Unauthorized_CcipRouterError.String()})
+ require.NotNil(t, result)
+ })
+ })
+
+ t.Run("When an admin disables the chain selector, it is no longer enabled", func(t *testing.T) {
+ t.Run("Source", func(t *testing.T) {
+ var initial ccip_router.ChainState
+ err := utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &initial)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, true, initial.SourceChain.Config.IsEnabled)
+
+ ix, err := ccip_router.NewDisableSourceChainSelectorInstruction(
+ config.EvmChainSelector,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+
+ var final ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &final)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, false, final.SourceChain.Config.IsEnabled)
+ })
+
+ t.Run("Dest", func(t *testing.T) {
+ var initial ccip_router.ChainState
+ err := utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &initial)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, true, initial.DestChain.Config.IsEnabled)
+
+ ix, err := ccip_router.NewDisableDestChainSelectorInstruction(
+ config.EvmChainSelector,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+
+ var final ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &final)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, false, final.DestChain.Config.IsEnabled)
+ })
+ })
+
+ t.Run("When an admin tries to update the chain state with invalid destination chain config, it fails", func(t *testing.T) {
+ for _, test := range invalidInputTests {
+ if test.SkipOnUpdate {
+ continue
+ }
+ t.Run(test.Name, func(t *testing.T) {
+ instruction, err := ccip_router.NewUpdateDestChainConfigInstruction(
+ test.Selector,
+ test.Conf,
+ getChainStatePDA(test.Selector),
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment, []string{"Error Code: " + ccip_router.InvalidInputs_CcipRouterError.String()})
+ require.NotNil(t, result)
+ })
+ }
+ })
+
+ t.Run("When an unauthorized user tries to update the chain state config, it fails", func(t *testing.T) {
+ t.Run("Source", func(t *testing.T) {
+ instruction, err := ccip_router.NewUpdateSourceChainConfigInstruction(
+ config.EvmChainSelector,
+ validSourceChainConfig,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ user.PublicKey(), // unauthorized
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + ccip_router.Unauthorized_CcipRouterError.String()})
+ require.NotNil(t, result)
+ })
+
+ t.Run("Dest", func(t *testing.T) {
+ instruction, err := ccip_router.NewUpdateDestChainConfigInstruction(
+ config.EvmChainSelector,
+ validDestChainConfig,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ user.PublicKey(), // unauthorized
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + ccip_router.Unauthorized_CcipRouterError.String()})
+ require.NotNil(t, result)
+ })
+ })
+
+ t.Run("When an admin updates the chain state config, it is configured", func(t *testing.T) {
+ var initial ccip_router.ChainState
+ err := utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &initial)
+ require.NoError(t, err, "failed to get account info")
+
+ t.Run("Source", func(t *testing.T) {
+ updated := initial.SourceChain.Config
+ updated.IsEnabled = true
+ require.NotEqual(t, initial.SourceChain.Config, updated) // at this point, onchain is disabled and we'll re-enable it
+
+ instruction, err := ccip_router.NewUpdateSourceChainConfigInstruction(
+ config.EvmChainSelector,
+ updated,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var final ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &final)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, updated, final.SourceChain.Config)
+ })
+
+ t.Run("Dest", func(t *testing.T) {
+ updated := initial.DestChain.Config
+ updated.IsEnabled = true
+ require.NotEqual(t, initial.DestChain.Config, updated) // at this point, onchain is disabled and we'll re-enable it
+
+ instruction, err := ccip_router.NewUpdateDestChainConfigInstruction(
+ config.EvmChainSelector,
+ updated,
+ config.EvmChainStatePDA,
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var chainState ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &chainState)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, updated, chainState.DestChain.Config)
+ })
+ })
+
+ t.Run("Can transfer ownership", func(t *testing.T) {
+ // Fail to transfer ownership when not owner
+ instruction, err := ccip_router.NewTransferOwnershipInstruction(
+ anotherAdmin.PublicKey(),
+ config.RouterConfigPDA,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + ccip_router.Unauthorized_CcipRouterError.String()})
+ require.NotNil(t, result)
+
+ // successfully transfer ownership
+ instruction, err = ccip_router.NewTransferOwnershipInstruction(
+ anotherAdmin.PublicKey(),
+ config.RouterConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // Fail to accept ownership when not proposed_owner
+ instruction, err = ccip_router.NewAcceptOwnershipInstruction(
+ config.RouterConfigPDA,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + ccip_router.Unauthorized_CcipRouterError.String()})
+ require.NotNil(t, result)
+
+ // Successfully accept ownership
+ // anotherAdmin becomes owner for remaining tests
+ instruction, err = ccip_router.NewAcceptOwnershipInstruction(
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // Current owner cannot propose self
+ instruction, err = ccip_router.NewTransferOwnershipInstruction(
+ anotherAdmin.PublicKey(),
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + ccip_router.InvalidInputs_CcipRouterError.String()})
+ require.NotNil(t, result)
+
+ // Validate proposed set to 0-address
+ var configAccount ccip_router.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.RouterConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ require.Equal(t, solana.PublicKey{}, configAccount.ProposedOwner)
+ })
+ })
+
+ //////////////////////////
+ // Billing Config Tests //
+ //////////////////////////
+
+ t.Run("Billing", func(t *testing.T) {
+ t.Run("setup:add_tokens", func(t *testing.T) {
+ type TestToken struct {
+ Config ccip_router.BillingTokenConfig
+ Accounts AccountsPerToken
+ }
+
+ testTokens := []TestToken{
+ {
+ Accounts: wsol,
+ Config: ccip_router.BillingTokenConfig{
+ Enabled: true,
+ Mint: solana.SolMint,
+ UsdPerToken: ccip_router.TimestampedPackedU224{
+ Value: [28]uint8{},
+ Timestamp: 0,
+ },
+ PremiumMultiplierWeiPerEth: 0,
+ }},
+ {
+ Accounts: token2022,
+ Config: ccip_router.BillingTokenConfig{
+ Enabled: true,
+ Mint: token2022.mint,
+ UsdPerToken: ccip_router.TimestampedPackedU224{
+ Value: [28]uint8{},
+ Timestamp: 0,
+ },
+ PremiumMultiplierWeiPerEth: 0,
+ }},
+ }
+
+ for _, token := range testTokens {
+ t.Run("add_"+token.Accounts.name, func(t *testing.T) {
+ ixConfig, cerr := ccip_router.NewAddBillingTokenConfigInstruction(
+ token.Config,
+ config.RouterConfigPDA,
+ token.Accounts.billingConfigPDA,
+ token.Accounts.program,
+ token.Accounts.mint,
+ token.Accounts.billingATA,
+ anotherAdmin.PublicKey(),
+ config.BillingSignerPDA,
+ utils.AssociatedTokenProgramID,
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, cerr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixConfig}, anotherAdmin, config.DefaultCommitment)
+ })
+ }
+ })
+
+ t.Run("setup:funding_and_approvals", func(t *testing.T) {
+ type Item struct {
+ user solana.PrivateKey
+ getATA func(apt *AccountsPerToken) solana.PublicKey
+ }
+ list := []Item{
+ {
+ user: user,
+ getATA: func(apt *AccountsPerToken) solana.PublicKey { return apt.userATA },
+ },
+ {
+ user: anotherUser,
+ getATA: func(apt *AccountsPerToken) solana.PublicKey { return apt.anotherUserATA },
+ },
+ }
+
+ for _, it := range list {
+ for _, token := range billingTokens {
+ // create ATA for user
+ ixAtaUser, addrUser, uerr := utils.CreateAssociatedTokenAccount(token.program, token.mint, it.user.PublicKey(), it.user.PublicKey())
+ require.NoError(t, uerr)
+ require.Equal(t, it.getATA(token), addrUser)
+
+ // Approve CCIP to transfer the user's token for billing
+ ixApprove, aerr := utils.TokenApproveChecked(1e9, 9, token.program, it.getATA(token), token.mint, config.BillingSignerPDA, it.user.PublicKey(), []solana.PublicKey{})
+ require.NoError(t, aerr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixAtaUser, ixApprove}, it.user, config.DefaultCommitment)
+ }
+
+ // fund user token2022 (mint directly to user ATA)
+ ixMint, merr := utils.MintTo(1e9, token2022.program, token2022.mint, it.getATA(&token2022), admin.PublicKey())
+ require.NoError(t, merr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixMint}, admin, config.DefaultCommitment)
+
+ // fund user WSOL (transfer SOL + syncNative)
+ transferAmount := 1.0 * solana.LAMPORTS_PER_SOL
+ ixTransfer, terr := utils.NativeTransfer(wsol.program, transferAmount, it.user.PublicKey(), it.getATA(&wsol))
+ require.NoError(t, terr)
+ ixSync, serr := utils.SyncNative(wsol.program, it.getATA(&wsol))
+ require.NoError(t, serr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixTransfer, ixSync}, it.user, config.DefaultCommitment)
+ }
+ })
+
+ t.Run("Billing Token Config", func(t *testing.T) {
+ t.Run("Pre-condition: Does not support token0 by default", func(t *testing.T) {
+ token0BillingPDA := getTokenConfigPDA(token0.Mint.PublicKey())
+ var token0ConfigAccount ccip_router.BillingTokenConfigWrapper
+ err := utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0BillingPDA, config.DefaultCommitment, &token0ConfigAccount)
+ require.EqualError(t, err, "not found")
+ })
+
+ t.Run("When admin adds token0 with valid input it is configured", func(t *testing.T) {
+ token0Config := ccip_router.BillingTokenConfig{
+ Enabled: true,
+ Mint: token0.Mint.PublicKey(),
+ UsdPerToken: ccip_router.TimestampedPackedU224{},
+ PremiumMultiplierWeiPerEth: 0,
+ }
+
+ token0BillingPDA := getTokenConfigPDA(token0.Mint.PublicKey())
+ token0Receiver, _, ferr := utils.FindAssociatedTokenAddress(token0.Program, token0.Mint.PublicKey(), config.BillingSignerPDA)
+ require.NoError(t, ferr)
+
+ ixConfig, cerr := ccip_router.NewAddBillingTokenConfigInstruction(
+ token0Config,
+ config.RouterConfigPDA,
+ token0BillingPDA,
+ token0.Program,
+ token0.Mint.PublicKey(),
+ token0Receiver,
+ anotherAdmin.PublicKey(),
+ config.BillingSignerPDA,
+ utils.AssociatedTokenProgramID,
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, cerr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixConfig}, anotherAdmin, config.DefaultCommitment)
+
+ var token0ConfigAccount ccip_router.BillingTokenConfigWrapper
+ aerr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0BillingPDA, config.DefaultCommitment, &token0ConfigAccount)
+ require.NoError(t, aerr)
+
+ require.Equal(t, token0Config, token0ConfigAccount.Config)
+ })
+
+ t.Run("When an unauthorized user updates token0 with correct configuration it fails", func(t *testing.T) {
+ token0BillingPDA := getTokenConfigPDA(token0.Mint.PublicKey())
+ var initial ccip_router.BillingTokenConfigWrapper
+ ierr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0BillingPDA, config.DefaultCommitment, &initial)
+ require.NoError(t, ierr)
+
+ token0Config := initial.Config
+ token0Config.PremiumMultiplierWeiPerEth = initial.Config.PremiumMultiplierWeiPerEth*2 + 1 // updating something valid
+
+ ixConfig, cerr := ccip_router.NewUpdateBillingTokenConfigInstruction(token0Config, config.RouterConfigPDA, token0BillingPDA, admin.PublicKey()).ValidateAndBuild() // wrong admin
+ require.NoError(t, cerr)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ixConfig}, admin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+
+ var final ccip_router.BillingTokenConfigWrapper
+ ferr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0BillingPDA, config.DefaultCommitment, &final)
+ require.NoError(t, ferr)
+
+ require.Equal(t, initial.Config, final.Config) // it was not updated, same values as initial
+ })
+
+ t.Run("When admin updates token0 it is updated", func(t *testing.T) {
+ token0BillingPDA := getTokenConfigPDA(token0.Mint.PublicKey())
+ var initial ccip_router.BillingTokenConfigWrapper
+ ierr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0BillingPDA, config.DefaultCommitment, &initial)
+ require.NoError(t, ierr)
+
+ token0Config := initial.Config
+ token0Config.PremiumMultiplierWeiPerEth = initial.Config.PremiumMultiplierWeiPerEth*2 + 1 // updating something else
+
+ ixConfig, cerr := ccip_router.NewUpdateBillingTokenConfigInstruction(token0Config, config.RouterConfigPDA, token0BillingPDA, anotherAdmin.PublicKey()).ValidateAndBuild()
+ require.NoError(t, cerr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixConfig}, anotherAdmin, config.DefaultCommitment)
+
+ var final ccip_router.BillingTokenConfigWrapper
+ ferr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0BillingPDA, rpc.CommitmentProcessed, &final)
+ require.NoError(t, ferr)
+
+ require.NotEqual(t, initial.Config.PremiumMultiplierWeiPerEth, final.Config.PremiumMultiplierWeiPerEth) // it was updated
+ require.Equal(t, token0Config.PremiumMultiplierWeiPerEth, final.Config.PremiumMultiplierWeiPerEth)
+ })
+
+ t.Run("Can remove token config", func(t *testing.T) {
+ token0BillingPDA := getTokenConfigPDA(token0.Mint.PublicKey())
+
+ var initial ccip_router.BillingTokenConfigWrapper
+ ierr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0BillingPDA, config.DefaultCommitment, &initial)
+ require.NoError(t, ierr) // it exists, initially
+
+ receiver, _, aerr := utils.FindAssociatedTokenAddress(token0.Program, token0.Mint.PublicKey(), config.BillingSignerPDA)
+ require.NoError(t, aerr)
+
+ ixConfig, cerr := ccip_router.NewRemoveBillingTokenConfigInstruction(
+ config.RouterConfigPDA,
+ token0BillingPDA,
+ token0.Program,
+ token0.Mint.PublicKey(),
+ receiver,
+ config.BillingSignerPDA,
+ anotherAdmin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, cerr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixConfig}, anotherAdmin, config.DefaultCommitment)
+
+ var final ccip_router.BillingTokenConfigWrapper
+ ferr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0BillingPDA, rpc.CommitmentProcessed, &final)
+ require.EqualError(t, ferr, "not found") // it no longer exists
+ })
+
+ t.Run("Can remove a pre-2022 token too", func(t *testing.T) {
+ mintPriv, kerr := solana.NewRandomPrivateKey()
+ require.NoError(t, kerr)
+ mint := mintPriv.PublicKey()
+
+ // use old (pre-2022) token program
+ ixToken, terr := utils.CreateToken(ctx, solana.TokenProgramID, mint, admin.PublicKey(), 9, solanaGoClient, config.DefaultCommitment)
+ require.NoError(t, terr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, ixToken, admin, config.DefaultCommitment, utils.AddSigners(mintPriv))
+
+ configPDA, _, perr := solana.FindProgramAddress([][]byte{config.BillingTokenConfigPrefix, mint.Bytes()}, ccip_router.ProgramID)
+ require.NoError(t, perr)
+ receiver, _, terr := utils.FindAssociatedTokenAddress(solana.TokenProgramID, mint, config.BillingSignerPDA)
+ require.NoError(t, terr)
+
+ tokenConfig := ccip_router.BillingTokenConfig{
+ Enabled: true,
+ Mint: mint,
+ UsdPerToken: ccip_router.TimestampedPackedU224{},
+ PremiumMultiplierWeiPerEth: 0,
+ }
+
+ // add it first
+ ixConfig, cerr := ccip_router.NewAddBillingTokenConfigInstruction(
+ tokenConfig,
+ config.RouterConfigPDA,
+ configPDA,
+ solana.TokenProgramID,
+ mint,
+ receiver,
+ anotherAdmin.PublicKey(),
+ config.BillingSignerPDA,
+ utils.AssociatedTokenProgramID,
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, cerr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixConfig}, anotherAdmin, config.DefaultCommitment)
+
+ var tokenConfigAccount ccip_router.BillingTokenConfigWrapper
+ aerr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, configPDA, config.DefaultCommitment, &tokenConfigAccount)
+ require.NoError(t, aerr)
+
+ require.Equal(t, tokenConfig, tokenConfigAccount.Config)
+
+ // now, remove the added pre-2022 token, which has a balance of 0 in the receiver
+ ixConfig, cerr = ccip_router.NewRemoveBillingTokenConfigInstruction(
+ config.RouterConfigPDA,
+ configPDA,
+ solana.TokenProgramID,
+ mint,
+ receiver,
+ config.BillingSignerPDA,
+ anotherAdmin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, cerr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixConfig}, anotherAdmin, config.DefaultCommitment)
+
+ var final ccip_router.BillingTokenConfigWrapper
+ ferr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, configPDA, rpc.CommitmentProcessed, &final)
+ require.EqualError(t, ferr, "not found") // it no longer exists
+ })
+ })
+ })
+
+ //////////////////////////
+ // setOcrConfig Tests //
+ //////////////////////////
+
+ t.Run("Config SetOcrConfig", func(t *testing.T) {
+ t.Run("Successfully configures commit & execute DON ocr config for maximum signers and transmitters", func(t *testing.T) {
+ // Check owner permissions
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ 0,
+ ccip_router.Ocr3ConfigInfo{},
+ [][20]byte{},
+ []solana.PublicKey{},
+ config.RouterConfigPDA,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + ccip_router.Unauthorized_CcipRouterError.String()})
+
+ inputs := []struct {
+ plugin utils.OcrPlugin
+ signers [][20]byte
+ transmitters []solana.PublicKey
+ verifySig uint8 // use as bool
+ }{
+ {
+ utils.OcrCommitPlugin,
+ signerAddresses,
+ transmitterPubKeys,
+ 1, // true
+ },
+ {
+ utils.OcrExecutePlugin,
+ nil,
+ transmitterPubKeys,
+ 0, // no sign verify needed for execute
+ },
+ }
+
+ for _, v := range inputs {
+ t.Run(v.plugin.String(), func(t *testing.T) {
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(v.plugin),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: config.OcrF,
+ IsSignatureVerificationEnabled: v.verifySig,
+ },
+ v.signers,
+ v.transmitters,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // Check event ConfigSet
+ configSetEvent := EventConfigSet{}
+ require.NoError(t, utils.ParseEvent(result.Meta.LogMessages, "ConfigSet", &configSetEvent, config.PrintEvents))
+ require.Equal(t, uint8(v.plugin), configSetEvent.OcrPluginType)
+ require.Equal(t, config.ConfigDigest, configSetEvent.ConfigDigest)
+ require.Equal(t, config.OcrF, configSetEvent.F)
+ require.Equal(t, v.signers, configSetEvent.Signers)
+ require.Equal(t, v.transmitters, configSetEvent.Transmitters)
+
+ // check config state
+ var configAccount ccip_router.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.RouterConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ pluginState := configAccount.Ocr3[v.plugin]
+ require.Equal(t, config.ConfigDigest, pluginState.ConfigInfo.ConfigDigest)
+ require.Equal(t, config.OcrF, pluginState.ConfigInfo.F)
+ require.Equal(t, len(v.signers), int(pluginState.ConfigInfo.N))
+ require.Equal(t, v.verifySig, pluginState.ConfigInfo.IsSignatureVerificationEnabled)
+ require.Equal(t, config.MaxSignersAndTransmitters, len(pluginState.Signers))
+ require.Equal(t, config.MaxSignersAndTransmitters, len(pluginState.Transmitters))
+ for i := 0; i < config.MaxSignersAndTransmitters; i++ {
+ // check signers (and zero values)
+ signer := [20]byte{}
+ if i < len(v.signers) {
+ signer = v.signers[i]
+ }
+ require.Equal(t, signer, pluginState.Signers[i])
+
+ // check transmitters (and zero values)
+ transmitter := solana.PublicKey{}
+ if i < len(v.transmitters) {
+ transmitter = v.transmitters[i]
+ }
+ require.Equal(t, transmitter.Bytes(), pluginState.Transmitters[i][:])
+ }
+ })
+ }
+ })
+
+ t.Run("SetOcrConfig edge cases", func(t *testing.T) {
+ t.Run("It rejects an invalid plugin type", func(t *testing.T) {
+ t.Parallel()
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(100),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: config.OcrF,
+ },
+ signerAddresses,
+ transmitterPubKeys,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + ccip_router.InvalidInputs_CcipRouterError.String()})
+ })
+
+ t.Run("It rejects F = 0", func(t *testing.T) {
+ t.Parallel()
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(utils.OcrCommitPlugin),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: 0,
+ },
+ signerAddresses,
+ transmitterPubKeys,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorInvalidConfigFMustBePositive.String()})
+ })
+
+ t.Run("It rejects too many transmitters", func(t *testing.T) {
+ t.Parallel()
+ invalidTransmitters := make([]solana.PublicKey, config.MaxOracles+1)
+ for i := range invalidTransmitters {
+ invalidTransmitters[i] = getTransmitter().PublicKey()
+ }
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(utils.OcrCommitPlugin),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: config.OcrF,
+ },
+ signerAddresses,
+ invalidTransmitters,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorInvalidConfigTooManyTransmitters.String()})
+ })
+
+ t.Run("It rejects too many signers", func(t *testing.T) {
+ t.Parallel()
+ invalidSigners := make([][20]byte, config.MaxOracles+1)
+ for i := range invalidSigners {
+ invalidSigners[i] = signerAddresses[0]
+ }
+
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(utils.OcrCommitPlugin),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: config.OcrF,
+ },
+ invalidSigners,
+ transmitterPubKeys,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorInvalidConfigTooManySigners.String()})
+ })
+
+ t.Run("It rejects too high of F for signers", func(t *testing.T) {
+ t.Parallel()
+ invalidSigners := make([][20]byte, 1)
+ invalidSigners[0] = signerAddresses[0]
+
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(utils.OcrCommitPlugin),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: config.OcrF,
+ },
+ invalidSigners,
+ transmitterPubKeys,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorInvalidConfigFIsTooHigh.String()})
+ })
+
+ t.Run("It rejects duplicate transmitters", func(t *testing.T) {
+ t.Parallel()
+ transmitter := getTransmitter().PublicKey()
+
+ invalidTransmitters := make([]solana.PublicKey, 2)
+ for i := range invalidTransmitters {
+ invalidTransmitters[i] = transmitter
+ }
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(utils.OcrCommitPlugin),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: config.OcrF,
+ },
+ signerAddresses,
+ invalidTransmitters,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorInvalidConfigRepeatedOracle.String()})
+ })
+
+ t.Run("It rejects duplicate signers", func(t *testing.T) {
+ t.Parallel()
+ repeatedSignerAddresses := [][20]byte{}
+ for range signers {
+ repeatedSignerAddresses = append(repeatedSignerAddresses, signers[0].Address)
+ }
+ oneTransmitter := []solana.PublicKey{transmitterPubKeys[0]}
+
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(utils.OcrCommitPlugin),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: config.OcrF,
+ },
+ repeatedSignerAddresses,
+ oneTransmitter,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorInvalidConfigRepeatedOracle.String()})
+ })
+
+ t.Run("It rejects zero transmitter address", func(t *testing.T) {
+ t.Parallel()
+ invalidTransmitterPubKeys := []solana.PublicKey{transmitterPubKeys[0], utils.ZeroAddress}
+
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(utils.OcrCommitPlugin),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: config.OcrF,
+ },
+ signerAddresses,
+ invalidTransmitterPubKeys,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorOracleCannotBeZeroAddress.String()})
+ })
+
+ t.Run("It rejects zero signer address", func(t *testing.T) {
+ t.Parallel()
+ invalidSignerAddresses := [][20]byte{{}}
+ for _, v := range signers[1:] {
+ invalidSignerAddresses = append(invalidSignerAddresses, v.Address)
+ }
+ instruction, err := ccip_router.NewSetOcrConfigInstruction(
+ uint8(utils.OcrCommitPlugin),
+ ccip_router.Ocr3ConfigInfo{
+ ConfigDigest: config.ConfigDigest,
+ F: config.OcrF,
+ },
+ invalidSignerAddresses,
+ transmitterPubKeys,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorOracleCannotBeZeroAddress.String()})
+ })
+ })
+ })
+
+ ////////////////////////////////
+ // Token Admin Registry Tests //
+ ////////////////////////////////
+
+ t.Run("Token Admin Registry", func(t *testing.T) {
+ t.Run("Token Admin Registry by Admin", func(t *testing.T) {
+ t.Run("register token admin registry via get ccip admin", func(t *testing.T) {
+ t.Run("When any user wants to set up the token admin registry, it fails", func(t *testing.T) {
+ instruction, err := ccip_router.NewRegisterTokenAdminRegistryViaGetCcipAdminInstruction(
+ token0.Mint.PublicKey(),
+ tokenPoolAdmin.PublicKey(),
+ config.RouterConfigPDA,
+ token0.AdminRegistry,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When transmitter wants to set up the token admin registry, it fails", func(t *testing.T) {
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewRegisterTokenAdminRegistryViaGetCcipAdminInstruction(
+ token0.Mint.PublicKey(),
+ tokenPoolAdmin.PublicKey(),
+ config.RouterConfigPDA,
+ token0.AdminRegistry,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When admin wants to set up the token admin registry, it succeeds", func(t *testing.T) {
+ instruction, err := ccip_router.NewRegisterTokenAdminRegistryViaGetCcipAdminInstruction(
+ token0.Mint.PublicKey(),
+ tokenPoolAdmin.PublicKey(),
+ config.RouterConfigPDA,
+ token0.AdminRegistry,
+ anotherAdmin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+
+ // Validate Token Pool Registry PDA
+ tokenAdminRegistry := ccip_router.TokenAdminRegistry{}
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0.AdminRegistry, config.DefaultCommitment, &tokenAdminRegistry)
+ require.NoError(t, err)
+ require.Equal(t, tokenPoolAdmin.PublicKey(), tokenAdminRegistry.Administrator)
+ require.Equal(t, uint8(1), tokenAdminRegistry.Version)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.PendingAdministrator)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.LookupTable)
+ })
+ })
+
+ t.Run("set pool", func(t *testing.T) {
+ t.Run("When any user wants to set up the pool, it fails", func(t *testing.T) {
+ instruction, err := ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ token0.PoolLookupTable,
+ token0.AdminRegistry,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When transmitter wants to set up the pool, it fails", func(t *testing.T) {
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ token0.PoolLookupTable,
+ token0.AdminRegistry,
+ transmitter.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When admin wants to set up the pool, it fails", func(t *testing.T) {
+ instruction, err := ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ token0.PoolLookupTable,
+ token0.AdminRegistry,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When Token Pool Admin wants to set up the pool, it succeeds", func(t *testing.T) {
+ base := ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ token0.PoolLookupTable,
+ token0.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ )
+
+ base.AccountMetaSlice = append(base.AccountMetaSlice, solana.Meta(token0.PoolLookupTable))
+ instruction, err := base.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment)
+
+ // Validate Token Pool Registry PDA
+ tokenAdminRegistry := ccip_router.TokenAdminRegistry{}
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0.AdminRegistry, config.DefaultCommitment, &tokenAdminRegistry)
+ require.NoError(t, err)
+ require.Equal(t, tokenPoolAdmin.PublicKey(), tokenAdminRegistry.Administrator)
+ require.Equal(t, uint8(1), tokenAdminRegistry.Version)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.PendingAdministrator)
+ require.Equal(t, token0.PoolLookupTable, tokenAdminRegistry.LookupTable)
+ })
+
+ t.Run("When Token Pool Admin wants to set up the pool again to zero, it is none", func(t *testing.T) {
+ instruction, err := ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ solana.PublicKey{},
+ token0.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment)
+
+ // Validate Token Pool Registry PDA
+ tokenAdminRegistry := ccip_router.TokenAdminRegistry{}
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0.AdminRegistry, config.DefaultCommitment, &tokenAdminRegistry)
+ require.NoError(t, err)
+ require.Equal(t, tokenPoolAdmin.PublicKey(), tokenAdminRegistry.Administrator)
+ require.Equal(t, uint8(1), tokenAdminRegistry.Version)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.PendingAdministrator)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.LookupTable)
+
+ // Rollback to previous state
+ instruction, err = ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ token0.PoolLookupTable,
+ token0.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment)
+ })
+ })
+
+ t.Run("Transfer admin role for token admin registry", func(t *testing.T) {
+ t.Run("When any user wants to transfer the token admin registry, it fails", func(t *testing.T) {
+ instruction, err := ccip_router.NewTransferAdminRoleTokenAdminRegistryInstruction(
+ token0.Mint.PublicKey(),
+ anotherTokenPoolAdmin.PublicKey(),
+ token0.AdminRegistry,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When admin wants to transfer the token admin registry, it succeeds and permissions stay with no changes", func(t *testing.T) {
+ instruction, err := ccip_router.NewTransferAdminRoleTokenAdminRegistryInstruction(
+ token0.Mint.PublicKey(),
+ anotherTokenPoolAdmin.PublicKey(),
+ token0.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment)
+
+ // Validate Token Pool Registry PDA
+ tokenAdminRegistry := ccip_router.TokenAdminRegistry{}
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0.AdminRegistry, config.DefaultCommitment, &tokenAdminRegistry)
+ require.NoError(t, err)
+ require.Equal(t, tokenPoolAdmin.PublicKey(), tokenAdminRegistry.Administrator)
+ require.Equal(t, uint8(1), tokenAdminRegistry.Version)
+ require.Equal(t, anotherTokenPoolAdmin.PublicKey(), tokenAdminRegistry.PendingAdministrator)
+ require.Equal(t, token0.PoolLookupTable, tokenAdminRegistry.LookupTable)
+
+ // check if the admin is still the same
+ instruction, err = ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ token0.PoolLookupTable,
+ token0.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment)
+
+ // new one cant make changes yet
+ instruction, err = ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ token0.PoolLookupTable,
+ token0.AdminRegistry,
+ anotherTokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherTokenPoolAdmin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When new admin accepts the token admin registry, it succeeds and permissions are updated", func(t *testing.T) {
+ instruction, err := ccip_router.NewAcceptAdminRoleTokenAdminRegistryInstruction(
+ token0.Mint.PublicKey(),
+ token0.AdminRegistry,
+ anotherTokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherTokenPoolAdmin, config.DefaultCommitment)
+
+ // Validate Token Pool Registry PDA
+ tokenAdminRegistry := ccip_router.TokenAdminRegistry{}
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, token0.AdminRegistry, config.DefaultCommitment, &tokenAdminRegistry)
+ require.NoError(t, err)
+ require.Equal(t, anotherTokenPoolAdmin.PublicKey(), tokenAdminRegistry.Administrator)
+ require.Equal(t, uint8(1), tokenAdminRegistry.Version)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.PendingAdministrator)
+ require.Equal(t, token0.PoolLookupTable, tokenAdminRegistry.LookupTable)
+
+ // check old admin can not make changes anymore
+ instruction, err = ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ token0.PoolLookupTable,
+ token0.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+
+ // new one can make changes now
+ instruction, err = ccip_router.NewSetPoolInstruction(
+ token0.Mint.PublicKey(),
+ token0.PoolLookupTable,
+ token0.AdminRegistry,
+ anotherTokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherTokenPoolAdmin, config.DefaultCommitment)
+ })
+ })
+ })
+
+ t.Run("Token Admin Registry by Mint Authority", func(t *testing.T) {
+ t.Run("register token admin registry via token mint authority", func(t *testing.T) {
+ t.Run("When any user wants to set up the token admin registry, it fails", func(t *testing.T) {
+ instruction, err := ccip_router.NewRegisterTokenAdminRegistryViaOwnerInstruction(
+ token1.AdminRegistry,
+ token1.Mint.PublicKey(),
+ user.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When transmitter wants to set up the token admin registry, it fails", func(t *testing.T) {
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewRegisterTokenAdminRegistryViaOwnerInstruction(
+ token1.AdminRegistry,
+ token1.Mint.PublicKey(),
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When admin wants to set up the token admin registry, it fails", func(t *testing.T) {
+ instruction, err := ccip_router.NewRegisterTokenAdminRegistryViaOwnerInstruction(
+ token1.AdminRegistry,
+ token1.Mint.PublicKey(),
+ anotherAdmin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When invalid mint_authority wants to set up the token admin registry, it fails", func(t *testing.T) {
+ instruction, err := ccip_router.NewRegisterTokenAdminRegistryViaOwnerInstruction(
+ token1.AdminRegistry,
+ token1.Mint.PublicKey(),
+ tokenPoolAdmin.PublicKey(), // invalid
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When token mint_authority wants to set up the token admin registry, it succeeds", func(t *testing.T) {
+ instruction, err := ccip_router.NewRegisterTokenAdminRegistryViaOwnerInstruction(
+ token1.AdminRegistry,
+ token1.Mint.PublicKey(),
+ anotherTokenPoolAdmin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherTokenPoolAdmin, config.DefaultCommitment)
+
+ // Validate Token Pool Registry PDA
+ tokenAdminRegistry := ccip_router.TokenAdminRegistry{}
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, token1.AdminRegistry, config.DefaultCommitment, &tokenAdminRegistry)
+ require.NoError(t, err)
+ require.Equal(t, anotherTokenPoolAdmin.PublicKey(), tokenAdminRegistry.Administrator)
+ require.Equal(t, uint8(1), tokenAdminRegistry.Version)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.PendingAdministrator)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.LookupTable)
+ })
+ })
+
+ t.Run("set pool", func(t *testing.T) {
+ t.Run("When Mint Authority wants to set up the pool, it succeeds", func(t *testing.T) {
+ instruction, err := ccip_router.NewSetPoolInstruction(
+ token1.Mint.PublicKey(),
+ token1.PoolLookupTable,
+ token1.AdminRegistry,
+ anotherTokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherTokenPoolAdmin, config.DefaultCommitment)
+
+ // Validate Token Pool Registry PDA
+ tokenAdminRegistry := ccip_router.TokenAdminRegistry{}
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, token1.AdminRegistry, config.DefaultCommitment, &tokenAdminRegistry)
+ require.NoError(t, err)
+ require.Equal(t, anotherTokenPoolAdmin.PublicKey(), tokenAdminRegistry.Administrator)
+ require.Equal(t, uint8(1), tokenAdminRegistry.Version)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.PendingAdministrator)
+ require.Equal(t, token1.PoolLookupTable, tokenAdminRegistry.LookupTable)
+ })
+ })
+
+ t.Run("Transfer admin role for token admin registry", func(t *testing.T) {
+ t.Run("When invalid wants to transfer the token admin registry, it fails", func(t *testing.T) {
+ instruction, err := ccip_router.NewTransferAdminRoleTokenAdminRegistryInstruction(
+ token1.Mint.PublicKey(),
+ tokenPoolAdmin.PublicKey(),
+ token1.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+ t.Run("When mint authority wants to transfer the token admin registry, it succeeds and permissions stay with no changes", func(t *testing.T) {
+ instruction, err := ccip_router.NewTransferAdminRoleTokenAdminRegistryInstruction(
+ token1.Mint.PublicKey(),
+ tokenPoolAdmin.PublicKey(),
+ token1.AdminRegistry,
+ anotherTokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherTokenPoolAdmin, config.DefaultCommitment)
+
+ // Validate Token Pool Registry PDA
+ tokenAdminRegistry := ccip_router.TokenAdminRegistry{}
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, token1.AdminRegistry, config.DefaultCommitment, &tokenAdminRegistry)
+ require.NoError(t, err)
+ require.Equal(t, anotherTokenPoolAdmin.PublicKey(), tokenAdminRegistry.Administrator)
+ require.Equal(t, uint8(1), tokenAdminRegistry.Version)
+ require.Equal(t, tokenPoolAdmin.PublicKey(), tokenAdminRegistry.PendingAdministrator)
+ require.Equal(t, token1.PoolLookupTable, tokenAdminRegistry.LookupTable)
+
+ // check if the admin is still the same
+ instruction, err = ccip_router.NewSetPoolInstruction(
+ token1.Mint.PublicKey(),
+ token1.PoolLookupTable,
+ token1.AdminRegistry,
+ anotherTokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherTokenPoolAdmin, config.DefaultCommitment)
+
+ // new one cant make changes yet
+ instruction, err = ccip_router.NewSetPoolInstruction(
+ token1.Mint.PublicKey(),
+ token1.PoolLookupTable,
+ token1.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+
+ t.Run("When new admin accepts the token admin registry, it succeeds and permissions are updated", func(t *testing.T) {
+ instruction, err := ccip_router.NewAcceptAdminRoleTokenAdminRegistryInstruction(
+ token1.Mint.PublicKey(),
+ token1.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment)
+
+ // Validate Token Pool Registry PDA
+ tokenAdminRegistry := ccip_router.TokenAdminRegistry{}
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, token1.AdminRegistry, config.DefaultCommitment, &tokenAdminRegistry)
+ require.NoError(t, err)
+ require.Equal(t, tokenPoolAdmin.PublicKey(), tokenAdminRegistry.Administrator)
+ require.Equal(t, uint8(1), tokenAdminRegistry.Version)
+ require.Equal(t, solana.PublicKey{}, tokenAdminRegistry.PendingAdministrator)
+ require.Equal(t, token1.PoolLookupTable, tokenAdminRegistry.LookupTable)
+
+ // check old admin can not make changes anymore
+ instruction, err = ccip_router.NewSetPoolInstruction(
+ token1.Mint.PublicKey(),
+ token1.PoolLookupTable,
+ token1.AdminRegistry,
+ anotherTokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherTokenPoolAdmin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+
+ // new one can make changes now
+ instruction, err = ccip_router.NewSetPoolInstruction(
+ token1.Mint.PublicKey(),
+ token1.PoolLookupTable,
+ token1.AdminRegistry,
+ tokenPoolAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, tokenPoolAdmin, config.DefaultCommitment)
+ })
+ })
+ })
+ })
+
+ //////////////////////////////
+ // Token Pool Config Tests //
+ /////////////////////////////
+ t.Run("Token Pool Configuration", func(t *testing.T) {
+ t.Run("RemoteConfig", func(t *testing.T) {
+ ix, err := token_pool.NewSetChainRemoteConfigInstruction(config.EvmChainSelector, token0.Mint.PublicKey(), token_pool.RemoteConfig{
+ PoolAddress: []byte{1, 2, 3},
+ TokenAddress: []byte{1, 2, 3},
+ }, token0.PoolConfig, token0.Chain[config.EvmChainSelector], tokenPoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, tokenPoolAdmin, config.DefaultCommitment)
+ })
+
+ t.Run("RateLimit", func(t *testing.T) {
+ ix, err := token_pool.NewSetChainRateLimitInstruction(config.EvmChainSelector, token0.Mint.PublicKey(), token_pool.RateLimitConfig{}, token_pool.RateLimitConfig{}, token0.PoolConfig, token0.Chain[config.EvmChainSelector], tokenPoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, tokenPoolAdmin, config.DefaultCommitment)
+ })
+
+ t.Run("Billing", func(t *testing.T) {
+ ix, err := ccip_router.NewSetTokenBillingInstruction(config.EvmChainSelector, token0.Mint.PublicKey(), ccip_router.TokenBilling{}, config.RouterConfigPDA, token0.Billing[config.EvmChainSelector], anotherAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, anotherAdmin, config.DefaultCommitment)
+ })
+
+ // validate permissions for setting config
+ t.Run("Permissions", func(t *testing.T) {
+ t.Parallel()
+ t.Run("Billing can only be set by CCIP admin", func(t *testing.T) {
+ ix, err := ccip_router.NewSetTokenBillingInstruction(config.EvmChainSelector, token0.Mint.PublicKey(), ccip_router.TokenBilling{}, config.RouterConfigPDA, token0.Billing[config.EvmChainSelector], anotherTokenPoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, anotherTokenPoolAdmin, config.DefaultCommitment, []string{ccip_router.Unauthorized_CcipRouterError.String()})
+ })
+ })
+ })
+
+ //////////////////////////
+ // getFee Tests //
+ //////////////////////////
+ t.Run("getFee", func(t *testing.T) {
+ t.Run("Basic test", func(t *testing.T) {
+ message := ccip_router.Solana2AnyMessage{
+ Receiver: []byte{1, 2, 3},
+ FeeToken: wsol.mint,
+ }
+
+ billingTokenConfigPDA := getTokenConfigPDA(wsol.mint)
+
+ instruction, err := ccip_router.NewGetFeeInstruction(config.EvmChainSelector, message, config.EvmChainStatePDA, billingTokenConfigPDA).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SimulateTransaction(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user)
+ require.NotNil(t, result)
+
+ returned := utils.ExtractTypedReturnValue(ctx, t, result.Value.Logs, config.CcipRouterProgram.String(), binary.LittleEndian.Uint64)
+ require.Equal(t, uint64(1), returned)
+ })
+
+ t.Run("Cannot get fee in invalid chain", func(t *testing.T) {
+ const ConstraintSeedsError = 2006
+
+ message := ccip_router.Solana2AnyMessage{
+ Receiver: []byte{1, 2, 3},
+ FeeToken: wsol.mint,
+ }
+
+ badChainSelector := 1234
+
+ billingTokenConfigPDA := getTokenConfigPDA(wsol.mint)
+ instruction, err := ccip_router.NewGetFeeInstruction(uint64(badChainSelector), message, config.EvmChainStatePDA, billingTokenConfigPDA).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SimulateTransaction(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user)
+ require.NotNil(t, result)
+
+ returnedError := utils.ExtractReturnedError(ctx, t, result.Value.Logs, config.CcipRouterProgram.String())
+ require.NotNil(t, returnedError)
+ require.Equal(t, ConstraintSeedsError, *returnedError)
+ })
+
+ t.Run("Cannot get fee for invalid token", func(t *testing.T) {
+ const AccountNotInitializedError = 3012
+
+ unsupportedToken := token0
+ message := ccip_router.Solana2AnyMessage{
+ Receiver: []byte{1, 2, 3},
+ FeeToken: solana.PublicKey(unsupportedToken.Mint),
+ }
+
+ billingTokenConfigPDA := getTokenConfigPDA(unsupportedToken.Mint.PublicKey())
+ instruction, err := ccip_router.NewGetFeeInstruction(config.EvmChainSelector, message, config.EvmChainStatePDA, billingTokenConfigPDA).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SimulateTransaction(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user)
+ require.NotNil(t, result)
+
+ returnedError := utils.ExtractReturnedError(ctx, t, result.Value.Logs, config.CcipRouterProgram.String())
+ require.NotNil(t, returnedError)
+ require.Equal(t, AccountNotInitializedError, *returnedError)
+ })
+ })
+
+ //////////////////////////
+ // ccipSend Tests //
+ //////////////////////////
+
+ t.Run("OnRamp ccipSend", func(t *testing.T) {
+ t.Parallel()
+ t.Run("When sending to an invalid destination chain selector it fails", func(t *testing.T) {
+ destinationChainSelector := uint64(189)
+ destinationChainStatePDA, err := getChainStatePDA(destinationChainSelector)
+ require.NoError(t, err)
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: wsol.mint,
+ Receiver: []byte{1, 2, 3},
+ }
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ wsol.program,
+ wsol.mint,
+ wsol.billingConfigPDA,
+ wsol.userATA,
+ wsol.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: AccountNotInitialized"})
+ require.NotNil(t, result)
+ })
+
+ t.Run("When sending a Valid CCIP Message Emits CCIPMessageSent", func(t *testing.T) {
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: wsol.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ }
+
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ wsol.program,
+ wsol.mint,
+ wsol.billingConfigPDA,
+ wsol.userATA,
+ wsol.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var chainStateAccount ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, destinationChainStatePDA, config.DefaultCommitment, &chainStateAccount)
+ require.NoError(t, err, "failed to get account info")
+ // Do not check source chain config, as it may have been updated by other tests in ccip offramp
+ require.Equal(t, uint64(1), chainStateAccount.DestChain.State.SequenceNumber)
+
+ var nonceCounterAccount ccip_router.Nonce
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, nonceEvmPDA, config.DefaultCommitment, &nonceCounterAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, uint64(1), nonceCounterAccount.Counter)
+
+ ccipMessageSentEvent := EventCCIPMessageSent{}
+ require.NoError(t, utils.ParseEvent(result.Meta.LogMessages, "CCIPMessageSent", &ccipMessageSentEvent, config.PrintEvents))
+ require.Equal(t, uint64(21), ccipMessageSentEvent.DestinationChainSelector)
+ require.Equal(t, uint64(1), ccipMessageSentEvent.SequenceNumber)
+ require.Equal(t, user.PublicKey(), ccipMessageSentEvent.Message.Sender)
+ require.Equal(t, []byte{1, 2, 3}, ccipMessageSentEvent.Message.Receiver)
+ data := [3]uint8{4, 5, 6}
+ require.Equal(t, data[:], ccipMessageSentEvent.Message.Data)
+ require.Equal(t, bin.Uint128{Lo: 5000, Hi: 0}, ccipMessageSentEvent.Message.ExtraArgs.GasLimit)
+ require.Equal(t, false, ccipMessageSentEvent.Message.ExtraArgs.AllowOutOfOrderExecution)
+ require.Equal(t, uint64(15), ccipMessageSentEvent.Message.Header.SourceChainSelector)
+ require.Equal(t, uint64(21), ccipMessageSentEvent.Message.Header.DestChainSelector)
+ require.Equal(t, uint64(1), ccipMessageSentEvent.Message.Header.SequenceNumber)
+ require.Equal(t, uint64(1), ccipMessageSentEvent.Message.Header.Nonce)
+ })
+
+ t.Run("When sending a CCIP Message with ExtraArgs overrides Emits CCIPMessageSent", func(t *testing.T) {
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+ trueValue := true
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: wsol.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ ExtraArgs: ccip_router.ExtraArgsInput{
+ GasLimit: &bin.Uint128{Lo: 99, Hi: 0},
+ AllowOutOfOrderExecution: &trueValue,
+ },
+ }
+
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ wsol.program,
+ wsol.mint,
+ wsol.billingConfigPDA,
+ wsol.userATA,
+ wsol.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var chainStateAccount ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, destinationChainStatePDA, config.DefaultCommitment, &chainStateAccount)
+ require.NoError(t, err, "failed to get account info")
+ // Do not check source chain config, as it may have been updated by other tests in ccip offramp
+ require.Equal(t, uint64(2), chainStateAccount.DestChain.State.SequenceNumber)
+
+ var nonceCounterAccount ccip_router.Nonce
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, nonceEvmPDA, config.DefaultCommitment, &nonceCounterAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, uint64(1), nonceCounterAccount.Counter)
+
+ ccipMessageSentEvent := EventCCIPMessageSent{}
+ require.NoError(t, utils.ParseEvent(result.Meta.LogMessages, "CCIPMessageSent", &ccipMessageSentEvent, config.PrintEvents))
+ require.Equal(t, uint64(21), ccipMessageSentEvent.DestinationChainSelector)
+ require.Equal(t, uint64(2), ccipMessageSentEvent.SequenceNumber)
+ require.Equal(t, user.PublicKey(), ccipMessageSentEvent.Message.Sender)
+ require.Equal(t, []byte{1, 2, 3}, ccipMessageSentEvent.Message.Receiver)
+ data := [3]uint8{4, 5, 6}
+ require.Equal(t, data[:], ccipMessageSentEvent.Message.Data)
+ require.Equal(t, bin.Uint128{Lo: 99, Hi: 0}, ccipMessageSentEvent.Message.ExtraArgs.GasLimit)
+ require.Equal(t, true, ccipMessageSentEvent.Message.ExtraArgs.AllowOutOfOrderExecution)
+ require.Equal(t, uint64(15), ccipMessageSentEvent.Message.Header.SourceChainSelector)
+ require.Equal(t, uint64(21), ccipMessageSentEvent.Message.Header.DestChainSelector)
+ require.Equal(t, uint64(2), ccipMessageSentEvent.Message.Header.SequenceNumber)
+ require.Equal(t, uint64(0), ccipMessageSentEvent.Message.Header.Nonce) // nonce is not incremented as it is OOO
+ })
+
+ t.Run("When gasLimit is set to zero, it overrides Emits CCIPMessageSent", func(t *testing.T) {
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: token2022.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ ExtraArgs: ccip_router.ExtraArgsInput{
+ GasLimit: &bin.Uint128{Lo: 0, Hi: 0},
+ },
+ }
+
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ token2022.program,
+ token2022.mint,
+ token2022.billingConfigPDA,
+ token2022.userATA,
+ token2022.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var chainStateAccount ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, destinationChainStatePDA, config.DefaultCommitment, &chainStateAccount)
+ require.NoError(t, err, "failed to get account info")
+ // Do not check source chain config, as it may have been updated by other tests in ccip offramp
+ require.Equal(t, uint64(3), chainStateAccount.DestChain.State.SequenceNumber)
+
+ var nonceCounterAccount ccip_router.Nonce
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, nonceEvmPDA, config.DefaultCommitment, &nonceCounterAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, uint64(2), nonceCounterAccount.Counter)
+
+ ccipMessageSentEvent := EventCCIPMessageSent{}
+ require.NoError(t, utils.ParseEvent(result.Meta.LogMessages, "CCIPMessageSent", &ccipMessageSentEvent, config.PrintEvents))
+ require.Equal(t, uint64(21), ccipMessageSentEvent.DestinationChainSelector)
+ require.Equal(t, uint64(3), ccipMessageSentEvent.SequenceNumber)
+ require.Equal(t, user.PublicKey(), ccipMessageSentEvent.Message.Sender)
+ require.Equal(t, []byte{1, 2, 3}, ccipMessageSentEvent.Message.Receiver)
+ data := [3]uint8{4, 5, 6}
+ require.Equal(t, data[:], ccipMessageSentEvent.Message.Data)
+ require.Equal(t, bin.Uint128{Lo: 0, Hi: 0}, ccipMessageSentEvent.Message.ExtraArgs.GasLimit)
+ require.Equal(t, false, ccipMessageSentEvent.Message.ExtraArgs.AllowOutOfOrderExecution)
+ require.Equal(t, uint64(15), ccipMessageSentEvent.Message.Header.SourceChainSelector)
+ require.Equal(t, uint64(21), ccipMessageSentEvent.Message.Header.DestChainSelector)
+ require.Equal(t, uint64(3), ccipMessageSentEvent.Message.Header.SequenceNumber)
+ require.Equal(t, uint64(2), ccipMessageSentEvent.Message.Header.Nonce)
+ })
+
+ t.Run("When sending a message with an invalid nonce account, it fails", func(t *testing.T) {
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: wsol.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ }
+
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ anotherUser.PublicKey(),
+ solana.SystemProgramID,
+ wsol.program,
+ wsol.mint,
+ wsol.billingConfigPDA,
+ wsol.userATA,
+ wsol.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherUser, config.DefaultCommitment, []string{"Error Message: A seeds constraint was violated"})
+ require.NotNil(t, result)
+ })
+
+ t.Run("When sending a message impersonating another user, it fails", func(t *testing.T) {
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: wsol.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ }
+
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ wsol.program,
+ wsol.mint,
+ wsol.billingConfigPDA,
+ wsol.userATA,
+ wsol.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWithRPCError(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherUser, config.DefaultCommitment, []string{"Transaction signature verification failure"})
+ })
+
+ t.Run("When sending a message and paying with inconsistent fee token accounts, it fails", func(t *testing.T) {
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+
+ // These testcases are a quite a lot, this obviously blows up combinatorially and adds many seconds to the suite.
+ // We can remove/reduce this, but I used it during development so for now I'm keeping them here
+ for i, program := range utils.Map(billingTokens, func(t *AccountsPerToken) solana.PublicKey { return t.program }) {
+ for j, mint := range utils.Map(billingTokens, func(t *AccountsPerToken) solana.PublicKey { return t.mint }) {
+ for k, messageMint := range utils.Map(billingTokens, func(t *AccountsPerToken) solana.PublicKey { return t.mint }) {
+ for l, billingConfigPDA := range utils.Map(billingTokens, func(t *AccountsPerToken) solana.PublicKey { return t.billingConfigPDA }) {
+ for m, userATA := range utils.Map(billingTokens, func(t *AccountsPerToken) solana.PublicKey { return t.userATA }) {
+ for n, billingATA := range utils.Map(billingTokens, func(t *AccountsPerToken) solana.PublicKey { return t.billingATA }) {
+ if i == j && j == k && k == l && l == m && m == n {
+ // skip cases where everything aligns well, which work
+ continue
+ }
+ testName := fmt.Sprintf("when using program %v, mint %v, message mint %v, configPDA %v, userATA %v, billingATA %v", i, j, k, l, m, n)
+ t.Run(testName, func(t *testing.T) {
+ t.Parallel()
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ ccip_router.Solana2AnyMessage{
+ FeeToken: messageMint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ },
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ program,
+ mint,
+ billingConfigPDA,
+ userATA,
+ billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ // Given the mixture of inputs, there can be different error types here, so just check that it fails but not each message
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{""})
+ })
+ }
+ }
+ }
+ }
+ }
+ }
+ })
+
+ t.Run("When another user sending a Valid CCIP Message tries to pay with some else's tokens it fails", func(t *testing.T) {
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: token2022.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ }
+ anotherUserNonceEVMPDA, err := getNoncePDA(config.EvmChainSelector, anotherUser.PublicKey())
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ anotherUserNonceEVMPDA,
+ anotherUser.PublicKey(),
+ solana.SystemProgramID,
+ token2022.program,
+ token2022.mint,
+ token2022.billingConfigPDA,
+ token2022.userATA, // token account of a different user
+ token2022.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherUser, config.DefaultCommitment, []string{ccip_router.InvalidInputs_CcipRouterError.String()})
+ })
+
+ t.Run("When another user sending a Valid CCIP Message Emits CCIPMessageSent", func(t *testing.T) {
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: token2022.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ }
+ anotherUserNonceEVMPDA, err := getNoncePDA(config.EvmChainSelector, anotherUser.PublicKey())
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ anotherUserNonceEVMPDA,
+ anotherUser.PublicKey(),
+ solana.SystemProgramID,
+ token2022.program,
+ token2022.mint,
+ token2022.billingConfigPDA,
+ token2022.anotherUserATA,
+ token2022.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherUser, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var chainStateAccount ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, destinationChainStatePDA, config.DefaultCommitment, &chainStateAccount)
+ require.NoError(t, err, "failed to get account info")
+ // Do not check source chain config, as it may have been updated by other tests in ccip offramp
+ require.Equal(t, uint64(4), chainStateAccount.DestChain.State.SequenceNumber)
+
+ var nonceCounterAccount ccip_router.Nonce
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, anotherUserNonceEVMPDA, config.DefaultCommitment, &nonceCounterAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, uint64(1), nonceCounterAccount.Counter)
+
+ ccipMessageSentEvent := EventCCIPMessageSent{}
+ require.NoError(t, utils.ParseEvent(result.Meta.LogMessages, "CCIPMessageSent", &ccipMessageSentEvent, config.PrintEvents))
+ require.Equal(t, uint64(21), ccipMessageSentEvent.DestinationChainSelector)
+ require.Equal(t, uint64(4), ccipMessageSentEvent.SequenceNumber)
+ require.Equal(t, anotherUser.PublicKey(), ccipMessageSentEvent.Message.Sender)
+ require.Equal(t, []byte{1, 2, 3}, ccipMessageSentEvent.Message.Receiver)
+ data := [3]uint8{4, 5, 6}
+ require.Equal(t, data[:], ccipMessageSentEvent.Message.Data)
+ require.Equal(t, bin.Uint128{Lo: 5000, Hi: 0}, ccipMessageSentEvent.Message.ExtraArgs.GasLimit)
+ require.Equal(t, false, ccipMessageSentEvent.Message.ExtraArgs.AllowOutOfOrderExecution)
+ require.Equal(t, uint64(15), ccipMessageSentEvent.Message.Header.SourceChainSelector)
+ require.Equal(t, uint64(21), ccipMessageSentEvent.Message.Header.DestChainSelector)
+ require.Equal(t, uint64(4), ccipMessageSentEvent.Message.Header.SequenceNumber)
+ require.Equal(t, uint64(1), ccipMessageSentEvent.Message.Header.Nonce)
+ })
+
+ t.Run("token happy path", func(t *testing.T) {
+ _, initSupply, err := utils.TokenSupply(ctx, solanaGoClient, token0.Mint.PublicKey(), config.DefaultCommitment)
+ require.NoError(t, err)
+ _, initBal, err := utils.TokenBalance(ctx, solanaGoClient, token0.User[user.PublicKey()], config.DefaultCommitment)
+ require.NoError(t, err)
+
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: wsol.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ TokenAmounts: []ccip_router.SolanaTokenAmount{
+ {
+ Token: token0.Mint.PublicKey(),
+ Amount: 1,
+ },
+ },
+ TokenIndexes: []byte{0}, // starting indices for accounts
+ }
+
+ userTokenAccount, ok := token0.User[user.PublicKey()]
+ require.True(t, ok)
+
+ base := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ wsol.program,
+ wsol.mint,
+ wsol.billingConfigPDA,
+ wsol.userATA,
+ wsol.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+
+ tokenMetas, addressTables, err := ParseTokenLookupTable(ctx, solanaGoClient, token0, userTokenAccount)
+ require.NoError(t, err)
+ base.AccountMetaSlice = append(base.AccountMetaSlice, tokenMetas...)
+
+ ix, err := base.ValidateAndBuild()
+ require.NoError(t, err)
+
+ ixApprove, err := utils.TokenApproveChecked(1, 0, token0.Program, userTokenAccount, token0.Mint.PublicKey(), config.ExternalTokenPoolsSignerPDA, user.PublicKey(), nil)
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirmWithLookupTables(ctx, t, solanaGoClient, []solana.Instruction{ixApprove, ix}, user, config.DefaultCommitment, addressTables, utils.AddComputeUnitLimit(300_000))
+ require.NotNil(t, result)
+
+ // check CCIP event
+ ccipMessageSentEvent := EventCCIPMessageSent{}
+ require.NoError(t, utils.ParseEvent(result.Meta.LogMessages, "CCIPMessageSent", &ccipMessageSentEvent, config.PrintEvents))
+ require.Equal(t, 1, len(ccipMessageSentEvent.Message.TokenAmounts))
+ ta := ccipMessageSentEvent.Message.TokenAmounts[0]
+ require.Equal(t, token0.PoolConfig, ta.SourcePoolAddress)
+ require.Equal(t, []byte{1, 2, 3}, ta.DestTokenAddress)
+ require.Equal(t, 0, len(ta.ExtraData))
+ require.Equal(t, utils.ToLittleEndianU256(1), ta.Amount)
+ require.Equal(t, 0, len(ta.DestExecData))
+
+ // check pool event
+ poolEvent := EventBurnLock{}
+ require.NoError(t, utils.ParseEvent(result.Meta.LogMessages, "Burned", &poolEvent, config.PrintEvents))
+ require.Equal(t, config.ExternalTokenPoolsSignerPDA, poolEvent.Sender)
+ require.Equal(t, uint64(1), poolEvent.Amount)
+
+ // check balances
+ _, currSupply, err := utils.TokenSupply(ctx, solanaGoClient, token0.Mint.PublicKey(), config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 1, initSupply-currSupply) // burned amount
+ _, currBal, err := utils.TokenBalance(ctx, solanaGoClient, token0.User[user.PublicKey()], config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 1, initBal-currBal) // burned amount
+ _, poolBal, err := utils.TokenBalance(ctx, solanaGoClient, token0.PoolTokenAccount, config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 0, poolBal) // pool burned any sent to it
+ })
+
+ t.Run("token pool accounts: validation", func(t *testing.T) {
+ t.Parallel()
+ // base transaction
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: wsol.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ TokenAmounts: []ccip_router.SolanaTokenAmount{
+ {
+ Token: token0.Mint.PublicKey(),
+ Amount: 1,
+ },
+ },
+ TokenIndexes: []byte{0}, // starting indices for accounts
+ }
+
+ userTokenAccount, ok := token0.User[user.PublicKey()]
+ require.True(t, ok)
+
+ inputs := []struct {
+ name string
+ index uint
+ replaceWith solana.PublicKey // default to zero address
+ errorStr ccip_router.CcipRouterError
+ }{
+ {
+ name: "incorrect user token account",
+ index: 0,
+ errorStr: ccip_router.InvalidInputsTokenAccounts_CcipRouterError,
+ },
+ {
+ name: "invalid billing config",
+ index: 1,
+ errorStr: ccip_router.InvalidInputsConfigAccounts_CcipRouterError,
+ },
+ {
+ name: "invalid token pool chain config",
+ index: 2,
+ errorStr: ccip_router.InvalidInputsConfigAccounts_CcipRouterError,
+ },
+ {
+ name: "pool config is not owned by pool program",
+ index: 6,
+ errorStr: ccip_router.InvalidInputsPoolAccounts_CcipRouterError,
+ },
+ {
+ name: "is pool config but for wrong token",
+ index: 6,
+ replaceWith: token1.PoolConfig,
+ errorStr: ccip_router.InvalidInputsPoolAccounts_CcipRouterError,
+ },
+ {
+ name: "incorrect pool signer",
+ index: 8,
+ errorStr: ccip_router.InvalidInputsPoolAccounts_CcipRouterError,
+ },
+ {
+ name: "invalid token program",
+ index: 9,
+ errorStr: ccip_router.InvalidInputsTokenAccounts_CcipRouterError,
+ },
+ {
+ name: "incorrect pool token account",
+ index: 7,
+ errorStr: ccip_router.InvalidInputsTokenAccounts_CcipRouterError,
+ },
+ {
+ name: "incorrect token pool lookup table",
+ index: 3,
+ replaceWith: token1.PoolLookupTable,
+ errorStr: ccip_router.InvalidInputsLookupTableAccounts_CcipRouterError,
+ },
+ {
+ name: "extra accounts not in lookup table",
+ index: 1_000, // large number to indicate append
+ errorStr: ccip_router.InvalidInputsLookupTableAccounts_CcipRouterError,
+ },
+ {
+ name: "remaining accounts mismatch",
+ index: 11, // only works with token0
+ errorStr: ccip_router.InvalidInputsLookupTableAccounts_CcipRouterError,
+ },
+ }
+
+ for _, in := range inputs {
+ t.Run(in.name, func(t *testing.T) {
+ t.Parallel()
+ tx := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ wsol.program,
+ wsol.mint,
+ wsol.billingConfigPDA,
+ wsol.userATA,
+ wsol.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+
+ tokenMetas, addressTables, err := ParseTokenLookupTable(ctx, solanaGoClient, token0, userTokenAccount)
+ require.NoError(t, err)
+ // replace account meta with invalid account to trigger error or append
+ if in.index >= uint(len(tokenMetas)) {
+ tokenMetas = append(tokenMetas, solana.Meta(in.replaceWith))
+ } else {
+ tokenMetas[in.index] = solana.Meta(in.replaceWith)
+ }
+
+ tx.AccountMetaSlice = append(tx.AccountMetaSlice, tokenMetas...)
+ ix, err := tx.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWithLookupTables(ctx, t, solanaGoClient, []solana.Instruction{ix}, user, config.DefaultCommitment, addressTables, []string{in.errorStr.String()})
+ })
+ }
+ })
+
+ t.Run("When sending a Valid CCIP Message it bills the amount that getFee previously returned", func(t *testing.T) {
+ destinationChainSelector := config.EvmChainSelector
+ destinationChainStatePDA := config.EvmChainStatePDA
+
+ for _, token := range billingTokens {
+ t.Run("using "+token.name, func(t *testing.T) {
+ message := ccip_router.Solana2AnyMessage{
+ FeeToken: token.mint,
+ Receiver: []byte{1, 2, 3},
+ Data: []byte{4, 5, 6},
+ }
+
+ // getFee
+ billingTokenConfigPDA := getTokenConfigPDA(token.mint)
+ ix, ferr := ccip_router.NewGetFeeInstruction(config.EvmChainSelector, message, config.EvmChainStatePDA, billingTokenConfigPDA).ValidateAndBuild()
+ require.NoError(t, ferr)
+
+ feeResult := utils.SimulateTransaction(ctx, t, solanaGoClient, []solana.Instruction{ix}, user)
+ require.NotNil(t, feeResult)
+ fee := utils.ExtractTypedReturnValue(ctx, t, feeResult.Value.Logs, config.CcipRouterProgram.String(), binary.LittleEndian.Uint64)
+ require.Greater(t, fee, uint64(0))
+
+ initialBalance := getBalance(token.billingATA)
+
+ // ccipSend
+ instruction, err := ccip_router.NewCcipSendInstruction(
+ destinationChainSelector,
+ message,
+ config.RouterConfigPDA,
+ destinationChainStatePDA,
+ nonceEvmPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ token.program,
+ token.mint,
+ token.billingConfigPDA,
+ token.userATA,
+ token.billingATA,
+ config.BillingSignerPDA,
+ config.ExternalTokenPoolsSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ finalBalance := getBalance(token.billingATA)
+
+ // Check that the billing receiver account balance has increased by the fee amount
+ require.Equal(t, fee, finalBalance-initialBalance)
+ })
+ }
+ })
+
+ ////////////////////
+ // Billing config //
+ ////////////////////
+ // These tests are run at the end as they require previous successful ccip_send executions
+ // (so that there's a billed balance)
+ t.Run("Remove billing token after successful onramp calls", func(t *testing.T) {
+ t.Run("When trying to remove a billing token for which there is still a held balance, it fails", func(t *testing.T) {
+ for _, token := range billingTokens {
+ t.Run(token.name, func(t *testing.T) {
+ balance := getBalance(token.billingATA)
+ require.Greater(t, balance, uint64(0))
+
+ ix, err := ccip_router.NewRemoveBillingTokenConfigInstruction(
+ config.RouterConfigPDA,
+ token.billingConfigPDA,
+ token.program,
+ token.mint,
+ token.billingATA,
+ config.BillingSignerPDA,
+ anotherAdmin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, anotherAdmin, config.DefaultCommitment, []string{ccip_router.InvalidInputs_CcipRouterError.String()})
+ })
+ }
+ })
+ })
+ })
+
+ //////////////////////////
+ // OffRamp //
+ //////////////////////////
+
+ t.Run("OffRamp", func(t *testing.T) {
+ //////////////////////////
+ // commit Tests //
+ //////////////////////////
+ t.Run("Commit", func(t *testing.T) {
+ currentMinSeqNr := uint64(1)
+
+ t.Run("When committing a report with a valid source chain selector, merkle root and interval it succeeds", func(t *testing.T) {
+ priceUpdatesCases := []struct {
+ Name string
+ PriceUpdates ccip_router.PriceUpdates
+ RemainingAccounts []solana.PublicKey
+ RunEventValidations func(t *testing.T, tx *rpc.GetTransactionResult)
+ RunStateValidations func(t *testing.T)
+ }{
+ {
+ Name: "No price updates",
+ PriceUpdates: ccip_router.PriceUpdates{},
+ RemainingAccounts: []solana.PublicKey{},
+ RunEventValidations: func(t *testing.T, tx *rpc.GetTransactionResult) {
+ require.ErrorContains(t, utils.ParseEvent(tx.Meta.LogMessages, "UsdPerTokenUpdated", nil, config.PrintEvents), "event not found")
+ require.ErrorContains(t, utils.ParseEvent(tx.Meta.LogMessages, "UsdPerUnitGasUpdated", nil, config.PrintEvents), "event not found")
+ },
+ RunStateValidations: func(t *testing.T) {},
+ },
+ {
+ Name: "Single token price update",
+ PriceUpdates: ccip_router.PriceUpdates{
+ TokenPriceUpdates: []ccip_router.TokenPriceUpdate{{
+ SourceToken: wsol.mint,
+ UsdPerToken: utils.To28BytesLE(1),
+ }},
+ },
+ RemainingAccounts: []solana.PublicKey{wsol.billingConfigPDA},
+ RunEventValidations: func(t *testing.T, tx *rpc.GetTransactionResult) {
+ // yes token update
+ var update UsdPerTokenUpdated
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "UsdPerTokenUpdated", &update, config.PrintEvents))
+ require.Greater(t, update.Timestamp, int64(0)) // timestamp is set
+ require.Equal(t, utils.To28BytesLE(1), update.Value)
+
+ // no gas updates
+ require.ErrorContains(t, utils.ParseEvent(tx.Meta.LogMessages, "UsdPerUnitGasUpdated", nil, config.PrintEvents), "event not found")
+ },
+ RunStateValidations: func(t *testing.T) {
+ var tokenConfig ccip_router.BillingTokenConfigWrapper
+ require.NoError(t, utils.GetAccountDataBorshInto(ctx, solanaGoClient, wsol.billingConfigPDA, config.DefaultCommitment, &tokenConfig))
+ require.Equal(t, utils.To28BytesLE(1), tokenConfig.Config.UsdPerToken.Value)
+ require.Greater(t, tokenConfig.Config.UsdPerToken.Timestamp, int64(0))
+ },
+ },
+ {
+ Name: "Single gas price update on same chain as commit message",
+ PriceUpdates: ccip_router.PriceUpdates{
+ GasPriceUpdates: []ccip_router.GasPriceUpdate{{
+ DestChainSelector: config.EvmChainSelector,
+ UsdPerUnitGas: utils.To28BytesLE(1),
+ }},
+ },
+ RemainingAccounts: []solana.PublicKey{config.EvmChainStatePDA},
+ RunEventValidations: func(t *testing.T, tx *rpc.GetTransactionResult) {
+ // no token updates
+ require.ErrorContains(t, utils.ParseEvent(tx.Meta.LogMessages, "UsdPerTokenUpdated", nil, config.PrintEvents), "event not found")
+
+ // yes gas update
+ var update UsdPerUnitGasUpdated
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "UsdPerUnitGasUpdated", &update, config.PrintEvents))
+ require.Greater(t, update.Timestamp, int64(0)) // timestamp is set
+ require.Equal(t, utils.To28BytesLE(1), update.Value)
+ },
+ RunStateValidations: func(t *testing.T) {
+ var chainState ccip_router.ChainState
+ require.NoError(t, utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &chainState))
+ require.Equal(t, utils.To28BytesLE(1), chainState.DestChain.State.UsdPerUnitGas.Value)
+ require.Greater(t, chainState.DestChain.State.UsdPerUnitGas.Timestamp, int64(0))
+ },
+ },
+ {
+ Name: "Single gas price update on different chain (Solana) as commit message (EVM)",
+ PriceUpdates: ccip_router.PriceUpdates{
+ GasPriceUpdates: []ccip_router.GasPriceUpdate{{
+ DestChainSelector: config.SolanaChainSelector,
+ UsdPerUnitGas: utils.To28BytesLE(2),
+ }},
+ },
+ RemainingAccounts: []solana.PublicKey{config.SolanaChainStatePDA},
+ RunEventValidations: func(t *testing.T, tx *rpc.GetTransactionResult) {
+ // no token updates
+ require.ErrorContains(t, utils.ParseEvent(tx.Meta.LogMessages, "UsdPerTokenUpdated", nil, config.PrintEvents), "event not found")
+
+ fmt.Print(tx.Meta.LogMessages) // TODO remove
+
+ // yes gas update
+ var update UsdPerUnitGasUpdated
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "UsdPerUnitGasUpdated", &update, config.PrintEvents))
+ require.Greater(t, update.Timestamp, int64(0)) // timestamp is set
+ require.Equal(t, utils.To28BytesLE(2), update.Value)
+ },
+ RunStateValidations: func(t *testing.T) {
+ var chainState ccip_router.ChainState
+ require.NoError(t, utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.SolanaChainStatePDA, config.DefaultCommitment, &chainState))
+ fmt.Printf("chainState: %v\n", chainState) // TODO remove
+ require.Equal(t, utils.To28BytesLE(2), chainState.DestChain.State.UsdPerUnitGas.Value)
+ require.Greater(t, chainState.DestChain.State.UsdPerUnitGas.Timestamp, int64(0))
+ },
+ },
+ {
+ Name: "Multiple token & gas updates",
+ PriceUpdates: ccip_router.PriceUpdates{
+ TokenPriceUpdates: []ccip_router.TokenPriceUpdate{
+ {SourceToken: wsol.mint, UsdPerToken: utils.To28BytesLE(3)},
+ {SourceToken: token2022.mint, UsdPerToken: utils.To28BytesLE(4)},
+ },
+ GasPriceUpdates: []ccip_router.GasPriceUpdate{
+ {DestChainSelector: config.EvmChainSelector, UsdPerUnitGas: utils.To28BytesLE(5)},
+ {DestChainSelector: config.SolanaChainSelector, UsdPerUnitGas: utils.To28BytesLE(6)},
+ },
+ },
+ RemainingAccounts: []solana.PublicKey{wsol.billingConfigPDA, token2022.billingConfigPDA, config.EvmChainStatePDA, config.SolanaChainStatePDA},
+ RunEventValidations: func(t *testing.T, tx *rpc.GetTransactionResult) {
+ // yes multiple token updates
+ tokenUpdates, err := utils.ParseMultipleEvents[UsdPerTokenUpdated](tx.Meta.LogMessages, "UsdPerTokenUpdated", config.PrintEvents)
+ require.NoError(t, err)
+ require.Len(t, tokenUpdates, 2)
+ var eventWsol, eventToken2022 bool
+ for _, tokenUpdate := range tokenUpdates {
+ switch tokenUpdate.Token {
+ case wsol.mint:
+ eventWsol = true
+ require.Equal(t, utils.To28BytesLE(3), tokenUpdate.Value)
+ case token2022.mint:
+ eventToken2022 = true
+ require.Equal(t, utils.To28BytesLE(4), tokenUpdate.Value)
+ default:
+ t.Fatalf("unexpected token update: %v", tokenUpdate)
+ }
+ require.Greater(t, tokenUpdate.Timestamp, int64(0)) // timestamp is set
+ }
+ require.True(t, eventWsol, "missing wsol update event")
+ require.True(t, eventToken2022, "missing token2022 update event")
+
+ // yes gas update
+ gasUpdates, err := utils.ParseMultipleEvents[UsdPerUnitGasUpdated](tx.Meta.LogMessages, "UsdPerUnitGasUpdated", config.PrintEvents)
+ require.NoError(t, err)
+ require.Len(t, gasUpdates, 2)
+ var eventEvm, eventSolana bool
+ for _, gasUpdate := range gasUpdates {
+ switch gasUpdate.DestChain {
+ case config.EvmChainSelector:
+ eventEvm = true
+ require.Equal(t, utils.To28BytesLE(5), gasUpdate.Value)
+ case config.SolanaChainSelector:
+ eventSolana = true
+ require.Equal(t, utils.To28BytesLE(6), gasUpdate.Value)
+ default:
+ t.Fatalf("unexpected gas update: %v", gasUpdate)
+ }
+ require.Greater(t, gasUpdate.Timestamp, int64(0)) // timestamp is set
+ }
+ require.True(t, eventEvm, "missing evm gas update event")
+ require.True(t, eventSolana, "missing solana gas update event")
+ },
+ RunStateValidations: func(t *testing.T) {
+ var wsolTokenConfig ccip_router.BillingTokenConfigWrapper
+ require.NoError(t, utils.GetAccountDataBorshInto(ctx, solanaGoClient, wsol.billingConfigPDA, config.DefaultCommitment, &wsolTokenConfig))
+ require.Equal(t, utils.To28BytesLE(3), wsolTokenConfig.Config.UsdPerToken.Value)
+ require.Greater(t, wsolTokenConfig.Config.UsdPerToken.Timestamp, int64(0))
+
+ var token2022Config ccip_router.BillingTokenConfigWrapper
+ require.NoError(t, utils.GetAccountDataBorshInto(ctx, solanaGoClient, token2022.billingConfigPDA, config.DefaultCommitment, &token2022Config))
+ require.Equal(t, utils.To28BytesLE(4), token2022Config.Config.UsdPerToken.Value)
+ require.Greater(t, token2022Config.Config.UsdPerToken.Timestamp, int64(0))
+
+ var evmChainState ccip_router.ChainState
+ require.NoError(t, utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &evmChainState))
+ require.Equal(t, utils.To28BytesLE(5), evmChainState.DestChain.State.UsdPerUnitGas.Value)
+ require.Greater(t, evmChainState.DestChain.State.UsdPerUnitGas.Timestamp, int64(0))
+
+ var solanaChainState ccip_router.ChainState
+ require.NoError(t, utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.SolanaChainStatePDA, config.DefaultCommitment, &solanaChainState))
+ require.Equal(t, utils.To28BytesLE(6), solanaChainState.DestChain.State.UsdPerUnitGas.Value)
+ require.Greater(t, solanaChainState.DestChain.State.UsdPerUnitGas.Timestamp, int64(0))
+ },
+ },
+ }
+
+ sequenceLength := uint64(5)
+
+ for i, testcase := range priceUpdatesCases {
+ t.Run(testcase.Name, func(t *testing.T) {
+ _, root := MakeEvmToSolanaMessage(t, config.CcipReceiverProgram, config.EvmChainSelector, config.SolanaChainSelector, []byte{1, 2, 3, uint8(i)})
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := currentMinSeqNr
+ maxV := currentMinSeqNr + sequenceLength - 1
+
+ currentMinSeqNr = maxV + 1 // advance the outer sequence counter
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ PriceUpdates: testcase.PriceUpdates,
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+
+ transmitter := getTransmitter()
+
+ raw := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ )
+
+ for _, pubkey := range testcase.RemainingAccounts {
+ raw.AccountMetaSlice.Append(solana.Meta(pubkey).WRITE())
+ }
+
+ instruction, err := raw.ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirmWithLookupTables(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, rpc.CommitmentConfirmed, commitLookupTable, utils.AddComputeUnitLimit(MaxCU))
+
+ commitEvent := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &commitEvent, config.PrintEvents))
+ require.Equal(t, config.EvmChainSelector, commitEvent.Report.SourceChainSelector)
+ require.Equal(t, root, commitEvent.Report.MerkleRoot)
+ require.Equal(t, minV, commitEvent.Report.MinSeqNr)
+ require.Equal(t, maxV, commitEvent.Report.MaxSeqNr)
+
+ transmittedEvent := EventTransmitted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "Transmitted", &transmittedEvent, config.PrintEvents))
+ require.Equal(t, config.ConfigDigest, transmittedEvent.ConfigDigest)
+ require.Equal(t, uint8(utils.OcrCommitPlugin), transmittedEvent.OcrPluginType)
+ require.Equal(t, config.ReportSequence, transmittedEvent.SequenceNumber)
+
+ var chainStateAccount ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &chainStateAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, currentMinSeqNr, chainStateAccount.SourceChain.State.MinSeqNr) // state now holds the "advanced outer" sequence number, which is the minimum for the next report
+ // Do not check dest chain config, as it may have been updated by other tests in ccip onramp
+
+ var rootAccount ccip_router.CommitReport
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, rootPDA, config.DefaultCommitment, &rootAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.NotEqual(t, bin.Uint128{Lo: 0, Hi: 0}, rootAccount.Timestamp)
+
+ testcase.RunEventValidations(t, tx)
+ testcase.RunStateValidations(t)
+ })
+ }
+ })
+
+ t.Run("Edge cases", func(t *testing.T) {
+ t.Run("When committing a report with an invalid source chain selector it fails", func(t *testing.T) {
+ t.Parallel()
+ sourceChainSelector := uint64(34)
+ sourceChainStatePDA, err := getChainStatePDA(sourceChainSelector)
+ require.NoError(t, err)
+ _, root := MakeEvmToSolanaMessage(t, config.CcipReceiverProgram, sourceChainSelector, config.SolanaChainSelector, []byte{4, 5, 6})
+ rootPDA, err := GetCommitReportPDA(sourceChainSelector, root)
+ require.NoError(t, err)
+
+ minV := currentMinSeqNr
+ maxV := currentMinSeqNr + 4
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: sourceChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ sourceChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: AccountNotInitialized"})
+ })
+
+ t.Run("When committing a report with an invalid interval it fails", func(t *testing.T) {
+ t.Parallel()
+ _, root := MakeEvmToSolanaMessage(t, config.CcipReceiverProgram, config.EvmChainSelector, config.SolanaChainSelector, []byte{4, 5, 6})
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := currentMinSeqNr
+ maxV := currentMinSeqNr - 2 // max lower than min
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + ccip_router.InvalidSequenceInterval_CcipRouterError.String()})
+ })
+
+ t.Run("When committing a report with an interval size bigger than supported it fails", func(t *testing.T) {
+ t.Parallel()
+ _, root := MakeEvmToSolanaMessage(t, config.CcipReceiverProgram, config.EvmChainSelector, config.SolanaChainSelector, []byte{4, 5, 6})
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := currentMinSeqNr
+ maxV := currentMinSeqNr + 65 // max - min > 64
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + ccip_router.InvalidSequenceInterval_CcipRouterError.String()})
+ })
+
+ t.Run("When committing a report with a zero merkle root it fails", func(t *testing.T) {
+ t.Parallel()
+ root := [32]byte{}
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := currentMinSeqNr
+ maxV := currentMinSeqNr // max = min
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + ccip_router.InvalidProof_CcipRouterError.String()})
+ })
+
+ t.Run("When committing a report with a repeated merkle root, it fails", func(t *testing.T) {
+ t.Parallel()
+ _, root := MakeEvmToSolanaMessage(t, config.CcipReceiverProgram, config.EvmChainSelector, config.SolanaChainSelector, []byte{1, 2, 3, 1}) // repeated root
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := currentMinSeqNr
+ maxV := currentMinSeqNr + 4
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment,
+ []string{"Allocate: account Address", "already in use", "failed: custom program error: 0x0"})
+ })
+
+ t.Run("When committing a report with an invalid min interval, it fails", func(t *testing.T) {
+ t.Parallel()
+ _, root := MakeEvmToSolanaMessage(t, config.CcipReceiverProgram, config.EvmChainSelector, config.SolanaChainSelector, []byte{4, 5, 6})
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := uint64(8) // this is lower than expected
+ maxV := uint64(10)
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + ccip_router.InvalidSequenceInterval_CcipRouterError.String()})
+ })
+
+ t.Run("Invalid price updates", func(t *testing.T) {
+ randomToken, err := solana.PublicKeyFromBase58("AGDpGy7auzgKT8zt6qhfHFm1rDwvqQGGTYxuYn7MtydQ") // just some non-existing token
+ require.NoError(t, err)
+
+ randomChain := uint64(123456) // just some non-existing chain
+ randomChainPDA, err := getChainStatePDA(randomChain)
+ require.NoError(t, err)
+
+ testcases := []struct {
+ Name string
+ Tokens []solana.PublicKey
+ GasChainSelectors []uint64
+ AccountMetaSlice solana.AccountMetaSlice
+ ExpectedError string
+ }{
+ {
+ Name: "with a price update for a token that doesn't exist",
+ Tokens: []solana.PublicKey{randomToken},
+ AccountMetaSlice: solana.AccountMetaSlice{solana.Meta(getTokenConfigPDA(randomToken)).WRITE()},
+ ExpectedError: "AccountNotInitialized",
+ },
+ {
+ Name: "with a gas price update for a chain that doesn't exist",
+ GasChainSelectors: []uint64{randomChain},
+ AccountMetaSlice: solana.AccountMetaSlice{solana.Meta(randomChainPDA).WRITE()},
+ ExpectedError: "AccountNotInitialized",
+ },
+ {
+ Name: "with a non-writable billing token config account",
+ Tokens: []solana.PublicKey{wsol.mint},
+ AccountMetaSlice: solana.AccountMetaSlice{solana.Meta(wsol.billingConfigPDA)}, // not writable
+ ExpectedError: ccip_router.InvalidInputs_CcipRouterError.String(),
+ },
+ {
+ // when the message source chain is the same as the chain whose gas is updated, then the same chain state is passed
+ // in twice, in which case the resulting permissions are the sum of both instances. As only one is manually constructed here,
+ // the other one is always writable (handled by the auto-generated code).
+ Name: "with a non-writable chain state account (different from the message source chain)",
+ GasChainSelectors: []uint64{config.SolanaChainSelector}, // the message source chain is EVM
+ AccountMetaSlice: solana.AccountMetaSlice{solana.Meta(config.SolanaChainStatePDA)}, // not writable
+ ExpectedError: ccip_router.InvalidInputs_CcipRouterError.String(),
+ },
+ {
+ Name: "with the wrong billing token config account for a valid token",
+ Tokens: []solana.PublicKey{wsol.mint},
+ AccountMetaSlice: solana.AccountMetaSlice{solana.Meta(token2022.billingConfigPDA).WRITE()}, // mismatch token
+ ExpectedError: ccip_router.InvalidInputs_CcipRouterError.String(),
+ },
+ {
+ Name: "with the wrong chain state account for a valid gas update",
+ GasChainSelectors: []uint64{config.SolanaChainSelector},
+ AccountMetaSlice: solana.AccountMetaSlice{solana.Meta(config.EvmChainStatePDA).WRITE()}, // mismatch chain
+ ExpectedError: ccip_router.InvalidInputs_CcipRouterError.String(),
+ },
+ {
+ Name: "with too few accounts",
+ Tokens: []solana.PublicKey{wsol.mint},
+ GasChainSelectors: []uint64{config.EvmChainSelector},
+ AccountMetaSlice: solana.AccountMetaSlice{solana.Meta(wsol.billingConfigPDA).WRITE()}, // missing chain state account
+ ExpectedError: ccip_router.InvalidInputs_CcipRouterError.String(),
+ },
+ // TODO right now I'm allowing sending too many remaining_accounts, but if we want to be restrictive with that we can add a test here
+ }
+ _, root := MakeEvmToSolanaMessage(t, config.CcipReceiverProgram, config.EvmChainSelector, config.SolanaChainSelector, []byte{1, 2, 3})
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ for _, testcase := range testcases {
+ t.Run(testcase.Name, func(t *testing.T) {
+ t.Parallel()
+
+ priceUpdates := ccip_router.PriceUpdates{
+ TokenPriceUpdates: make([]ccip_router.TokenPriceUpdate, len(testcase.Tokens)),
+ GasPriceUpdates: make([]ccip_router.GasPriceUpdate, len(testcase.GasChainSelectors)),
+ }
+ for i, token := range testcase.Tokens {
+ priceUpdates.TokenPriceUpdates[i] = ccip_router.TokenPriceUpdate{
+ SourceToken: token,
+ UsdPerToken: utils.To28BytesLE(uint64(i)),
+ }
+ }
+ for i, chainSelector := range testcase.GasChainSelectors {
+ priceUpdates.GasPriceUpdates[i] = ccip_router.GasPriceUpdate{
+ DestChainSelector: chainSelector,
+ UsdPerUnitGas: utils.To28BytesLE(uint64(i)),
+ }
+ }
+
+ transmitter := getTransmitter()
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: currentMinSeqNr,
+ MaxSeqNr: currentMinSeqNr + 2,
+ MerkleRoot: root,
+ },
+ PriceUpdates: priceUpdates,
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+
+ raw := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ )
+
+ for _, meta := range testcase.AccountMetaSlice {
+ raw.AccountMetaSlice.Append(meta)
+ }
+
+ instruction, err := raw.ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWithLookupTables(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, rpc.CommitmentConfirmed, commitLookupTable, []string{testcase.ExpectedError}, utils.AddComputeUnitLimit(MaxCU))
+ })
+ }
+ })
+ })
+
+ t.Run("When committing a report with the exact next interval, it succeeds", func(t *testing.T) {
+ _, root := MakeEvmToSolanaMessage(t, config.CcipReceiverProgram, config.EvmChainSelector, config.SolanaChainSelector, []byte{4, 5, 6})
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := currentMinSeqNr
+ maxV := currentMinSeqNr + 4
+
+ currentMinSeqNr = maxV + 1 // advance the outer sequence counter as this will succeed
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+ transmitter := getTransmitter()
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ commitEvent := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &commitEvent, config.PrintEvents))
+ require.Equal(t, config.EvmChainSelector, commitEvent.Report.SourceChainSelector)
+ require.Equal(t, root, commitEvent.Report.MerkleRoot)
+ require.Equal(t, minV, commitEvent.Report.MinSeqNr)
+ require.Equal(t, maxV, commitEvent.Report.MaxSeqNr)
+
+ transmittedEvent := EventTransmitted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "Transmitted", &transmittedEvent, config.PrintEvents))
+ require.Equal(t, config.ConfigDigest, transmittedEvent.ConfigDigest)
+ require.Equal(t, uint8(utils.OcrCommitPlugin), transmittedEvent.OcrPluginType)
+ require.Equal(t, config.ReportSequence, transmittedEvent.SequenceNumber)
+
+ var chainStateAccount ccip_router.ChainState
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.EvmChainStatePDA, config.DefaultCommitment, &chainStateAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.Equal(t, currentMinSeqNr, chainStateAccount.SourceChain.State.MinSeqNr)
+ // Do not check dest chain config, as it may have been updated by other tests in ccip onramp
+
+ var rootAccount ccip_router.CommitReport
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, rootPDA, config.DefaultCommitment, &rootAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.NotEqual(t, bin.Uint128{Lo: 0, Hi: 0}, rootAccount.Timestamp)
+ })
+
+ t.Run("Ocr3Base::Transmit edge cases", func(t *testing.T) {
+ t.Run("It rejects mismatch config digest", func(t *testing.T) {
+ t.Parallel()
+ msg, root := CreateNextMessage(ctx, solanaGoClient, t)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := msg.Header.SequenceNumber
+ maxV := msg.Header.SequenceNumber
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+ transmitter := getTransmitter()
+ emptyReportContext := [3][32]byte{}
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ emptyReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorConfigDigestMismatch.String()})
+ })
+
+ t.Run("It rejects unauthorized transmitter", func(t *testing.T) {
+ t.Parallel()
+ msg, root := CreateNextMessage(ctx, solanaGoClient, t)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := msg.Header.SequenceNumber
+ maxV := msg.Header.SequenceNumber
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorUnauthorizedTransmitter.String()})
+ })
+
+ t.Run("It rejects incorrect signature count", func(t *testing.T) {
+ t.Parallel()
+ msg, root := CreateNextMessage(ctx, solanaGoClient, t)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := msg.Header.SequenceNumber
+ maxV := msg.Header.SequenceNumber
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ hash, err := HashCommitReport(config.ReportContext, report)
+ require.NoError(t, err)
+
+ baseSig := ecdsa.SignCompact(secp256k1.PrivKeyFromBytes(signers[0].PrivateKey), hash, false)
+ baseSig[0] = baseSig[0] - 27 // key signs 27 or 28, but verification expects 0 or 1 (remove offset)
+ sigs := [][65]byte{}
+ sigs = append(sigs, [65]byte(baseSig))
+
+ require.NoError(t, err)
+ transmitter := getTransmitter()
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorWrongNumberOfSignatures.String()})
+ })
+
+ t.Run("It rejects invalid signature", func(t *testing.T) {
+ t.Parallel()
+ msg, root := CreateNextMessage(ctx, solanaGoClient, t)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := msg.Header.SequenceNumber
+ maxV := msg.Header.SequenceNumber
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs := [][65]byte{}
+ transmitter := getTransmitter()
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorWrongNumberOfSignatures.String()})
+ })
+
+ t.Run("It rejects unauthorized signer", func(t *testing.T) {
+ t.Parallel()
+ msg, root := CreateNextMessage(ctx, solanaGoClient, t)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := msg.Header.SequenceNumber
+ maxV := msg.Header.SequenceNumber
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+
+ hash, err := HashCommitReport(config.ReportContext, report)
+ require.NoError(t, err)
+ randomPrivateKey, err := secp256k1.GeneratePrivateKey()
+ require.NoError(t, err)
+ baseSig := ecdsa.SignCompact(randomPrivateKey, hash, false)
+ baseSig[0] = baseSig[0] - 27 // key signs 27 or 28, but verification expects 0 or 1 (remove offset)
+
+ sigs[0] = [65]byte(baseSig)
+
+ transmitter := getTransmitter()
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorUnauthorizedSigner.String()})
+ })
+
+ t.Run("It rejects duplicate signatures", func(t *testing.T) {
+ t.Parallel()
+ msg, root := CreateNextMessage(ctx, solanaGoClient, t)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ minV := msg.Header.SequenceNumber
+ maxV := msg.Header.SequenceNumber
+
+ report := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: minV,
+ MaxSeqNr: maxV,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, report, signers)
+ require.NoError(t, err)
+ sigs[0] = sigs[1]
+ transmitter := getTransmitter()
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ report,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + Ocr3ErrorNonUniqueSignatures.String()}, utils.AddComputeUnitLimit(210_000))
+ })
+ })
+ })
+
+ //////////////////////////
+ // execute Tests //
+ //////////////////////////
+
+ t.Run("Execute", func(t *testing.T) {
+ var executedSequenceNumber uint64
+ t.Run("When executing a report with merkle tree of size 1, it succeeds", func(t *testing.T) {
+ transmitter := getTransmitter()
+
+ sourceChainSelector := config.EvmChainSelector
+ message, root := CreateNextMessage(ctx, solanaGoClient, t)
+
+ sequenceNumber := message.Header.SequenceNumber
+
+ executedSequenceNumber = sequenceNumber // persist this number as executed, for later tests
+
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: sourceChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: sequenceNumber,
+ MaxSeqNr: sequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, utils.AddComputeUnitLimit(210_000)) // signature verification compute unit amounts can vary depending on sorting
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: sourceChainSelector,
+ Message: message,
+ Root: root,
+ Proofs: [][32]uint8{}, // single leaf merkle tree
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ tx = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ executionEvent := EventExecutionStateChanged{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "ExecutionStateChanged", &executionEvent, config.PrintEvents))
+
+ require.NoError(t, err)
+ require.NotNil(t, executionEvent)
+ require.Equal(t, config.EvmChainSelector, executionEvent.SourceChainSelector)
+ require.Equal(t, sequenceNumber, executionEvent.SequenceNumber)
+ require.Equal(t, hex.EncodeToString(message.Header.MessageId[:]), hex.EncodeToString(executionEvent.MessageID[:]))
+ require.Equal(t, hex.EncodeToString(root[:]), hex.EncodeToString(executionEvent.MessageHash[:]))
+ require.Equal(t, ccip_router.Success_MessageExecutionState, executionEvent.State)
+
+ var rootAccount ccip_router.CommitReport
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, rootPDA, config.DefaultCommitment, &rootAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.NotEqual(t, bin.Uint128{Lo: 0, Hi: 0}, rootAccount.Timestamp)
+ require.Equal(t, bin.Uint128{Lo: 2, Hi: 0}, rootAccount.ExecutionStates)
+ require.Equal(t, sequenceNumber, rootAccount.MinMsgNr)
+ require.Equal(t, sequenceNumber, rootAccount.MaxMsgNr)
+ })
+
+ t.Run("When executing a report with not matching source chain selector in message, it fails", func(t *testing.T) {
+ transmitter := getTransmitter()
+
+ message, root := CreateNextMessage(ctx, solanaGoClient, t)
+ sequenceNumber := message.Header.SequenceNumber
+
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: sequenceNumber,
+ MaxSeqNr: sequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, utils.AddComputeUnitLimit(210_000)) // signature verification compute unit amounts can vary depending on sorting
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ message.Header.SourceChainSelector = 89
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message,
+ Root: root,
+ Proofs: [][32]uint8{}, // single leaf merkle tree
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Message: Source chain selector not supported."})
+ })
+
+ t.Run("When executing a report with unsupported source chain selector account, it fails", func(t *testing.T) {
+ transmitter := getTransmitter()
+
+ unsupportedChainSelector := uint64(34)
+ message, root := CreateNextMessage(ctx, solanaGoClient, t)
+ sequenceNumber := message.Header.SequenceNumber
+
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: sequenceNumber,
+ MaxSeqNr: sequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, utils.AddComputeUnitLimit(210_000)) // signature verification compute unit amounts can vary depending on sorting
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ unsupportedChainStatePDA, err := getChainStatePDA(unsupportedChainSelector)
+ require.NoError(t, err)
+ message.Header.SourceChainSelector = unsupportedChainSelector
+ message.Header.SequenceNumber = 1
+
+ instruction, err = ccip_router.NewAddChainSelectorInstruction(
+ unsupportedChainSelector,
+ validSourceChainConfig,
+ validDestChainConfig,
+ unsupportedChainStatePDA,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: unsupportedChainSelector,
+ Message: message,
+ Root: root,
+ Proofs: [][32]uint8{}, // single leaf merkle tree
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ unsupportedChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"AnchorError caused by account: commit_report. Error Code: ConstraintSeeds. Error Number: 2006. Error Message: A seeds constraint was violated."})
+ })
+
+ t.Run("When executing a report with incorrect solana chain selector, it fails", func(t *testing.T) {
+ transmitter := getTransmitter()
+
+ message, _ := CreateNextMessage(ctx, solanaGoClient, t)
+ message.Header.DestChainSelector = 89 // invalid dest chain selector
+ sequenceNumber := message.Header.SequenceNumber
+ hash, err := HashEvmToSolanaMessage(message, config.OnRampAddress)
+ require.NoError(t, err)
+ root := [32]byte(hash)
+
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: sequenceNumber,
+ MaxSeqNr: sequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message,
+ Root: root,
+ Proofs: [][32]uint8{}, // single leaf merkle tree
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Code: " + ccip_router.UnsupportedDestinationChainSelector_CcipRouterError.String()})
+ })
+
+ t.Run("When executing a report with nonexisting PDA for the Merkle Root, it fails", func(t *testing.T) {
+ transmitter := getTransmitter()
+
+ message, root := CreateNextMessage(ctx, solanaGoClient, t)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message,
+ Root: root,
+ Proofs: [][32]uint8{}, // single leaf merkle tree
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err := raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Error Message: The program expected this account to be already initialized."})
+ })
+
+ t.Run("When executing a report for an already executed message, it is skipped", func(t *testing.T) {
+ transmitter := getTransmitter()
+
+ sourceChainSelector := config.EvmChainSelector
+
+ message := CreateDefaultMessageWith(sourceChainSelector, executedSequenceNumber) // already executed seq number
+ hash, err := HashEvmToSolanaMessage(message, config.OnRampAddress)
+ require.NoError(t, err)
+ root := [32]byte(hash)
+
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message,
+ Root: root,
+ Proofs: [][32]uint8{}, // single leaf merkle tree
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err := raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ executionEvent := EventSkippedAlreadyExecutedMessage{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "SkippedAlreadyExecutedMessage", &executionEvent, config.PrintEvents))
+
+ require.NoError(t, err)
+ require.NotNil(t, executionEvent)
+ require.Equal(t, config.EvmChainSelector, executionEvent.SourceChainSelector)
+ require.Equal(t, executedSequenceNumber, executionEvent.SequenceNumber)
+ })
+
+ t.Run("When executing a report for an already executed root, but not message, it succeeds", func(t *testing.T) {
+ transmitter := getTransmitter()
+
+ message1, hash1 := CreateNextMessage(ctx, solanaGoClient, t)
+ message2 := CreateDefaultMessageWith(config.EvmChainSelector, message1.Header.SequenceNumber+1)
+ hash2, err := HashEvmToSolanaMessage(message2, config.OnRampAddress)
+ require.NoError(t, err)
+
+ root := [32]byte(MerkleFrom([][]byte{hash1[:], hash2[:]}))
+
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: message1.Header.SequenceNumber,
+ MaxSeqNr: message2.Header.SequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ executionReport1 := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message2, // execute out of order
+ Root: root,
+ Proofs: [][32]uint8{hash1},
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport1,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ tx = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ executionEvent := EventExecutionStateChanged{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "ExecutionStateChanged", &executionEvent, config.PrintEvents))
+
+ executionReport2 := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message1,
+ Root: root,
+ Proofs: [][32]uint8{[32]byte(hash2)},
+ }
+ raw = ccip_router.NewExecuteInstruction(
+ executionReport2,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ tx = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ executionEvent = EventExecutionStateChanged{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "ExecutionStateChanged", &executionEvent, config.PrintEvents))
+
+ require.NoError(t, err)
+ require.NotNil(t, executionEvent)
+ require.Equal(t, config.EvmChainSelector, executionEvent.SourceChainSelector)
+ require.Equal(t, message1.Header.SequenceNumber, executionEvent.SequenceNumber)
+ require.Equal(t, hex.EncodeToString(message1.Header.MessageId[:]), hex.EncodeToString(executionEvent.MessageID[:]))
+ require.Equal(t, hex.EncodeToString(hash1[:]), hex.EncodeToString(executionEvent.MessageHash[:]))
+
+ require.Equal(t, ccip_router.Success_MessageExecutionState, executionEvent.State)
+
+ var rootAccount ccip_router.CommitReport
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, rootPDA, config.DefaultCommitment, &rootAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.NotEqual(t, bin.Uint128{Lo: 0, Hi: 0}, rootAccount.Timestamp)
+ require.Equal(t, bin.Uint128{Lo: 10, Hi: 0}, rootAccount.ExecutionStates)
+ require.Equal(t, message1.Header.SequenceNumber, rootAccount.MinMsgNr)
+ require.Equal(t, message2.Header.SequenceNumber, rootAccount.MaxMsgNr)
+ })
+
+ t.Run("When executing a report that receiver program needs to init an account, it fails", func(t *testing.T) {
+ transmitter := getTransmitter()
+
+ stubAccountPDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("counter")}, config.CcipInvalidReceiverProgram)
+
+ message, _ := CreateNextMessage(ctx, solanaGoClient, t)
+ message.Receiver = stubAccountPDA
+ sequenceNumber := message.Header.SequenceNumber
+ message.ExtraArgs.Accounts = []ccip_router.SolanaAccountMeta{
+ {Pubkey: config.CcipInvalidReceiverProgram},
+ {Pubkey: solana.SystemProgramID, IsWritable: false},
+ }
+
+ hash, err := HashEvmToSolanaMessage(message, config.OnRampAddress)
+ require.NoError(t, err)
+ root := [32]byte(hash)
+
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: sequenceNumber,
+ MaxSeqNr: sequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message,
+ Root: root,
+ Proofs: [][32]uint8{}, // single leaf merkle tree
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipInvalidReceiverProgram, false, false),
+ solana.NewAccountMeta(stubAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ // failed ccipReceiver - init account requires mutable authority
+ // ccipSigner is not a mutable account
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"writable privilege escalated", "Cross-program invocation with unauthorized signer or writable account"})
+ })
+
+ t.Run("token happy path", func(t *testing.T) {
+ _, initSupply, err := utils.TokenSupply(ctx, solanaGoClient, token0.Mint.PublicKey(), config.DefaultCommitment)
+ require.NoError(t, err)
+ _, initBal, err := utils.TokenBalance(ctx, solanaGoClient, token0.User[config.ReceiverExternalExecutionConfigPDA], config.DefaultCommitment)
+ require.NoError(t, err)
+
+ transmitter := getTransmitter()
+
+ sourceChainSelector := config.EvmChainSelector
+ message, _ := CreateNextMessage(ctx, solanaGoClient, t)
+ message.TokenAmounts = []ccip_router.Any2SolanaTokenTransfer{{
+ SourcePoolAddress: []byte{1, 2, 3},
+ DestTokenAddress: token0.Mint.PublicKey(),
+ Amount: utils.ToLittleEndianU256(1),
+ }}
+ rootBytes, err := HashEvmToSolanaMessage(message, config.OnRampAddress)
+ require.NoError(t, err)
+
+ root := [32]byte(rootBytes)
+ sequenceNumber := message.Header.SequenceNumber
+
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: sourceChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: sequenceNumber,
+ MaxSeqNr: sequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: sourceChainSelector,
+ Message: message,
+ OffchainTokenData: [][]byte{{}},
+ Root: root,
+ Proofs: [][32]uint8{},
+ TokenIndexes: []uint8{4},
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+
+ tokenMetas, addressTables, err := ParseTokenLookupTable(ctx, solanaGoClient, token0, token0.User[config.ReceiverExternalExecutionConfigPDA])
+ require.NoError(t, err)
+ raw.AccountMetaSlice = append(raw.AccountMetaSlice, tokenMetas...)
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ tx = utils.SendAndConfirmWithLookupTables(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, addressTables, utils.AddComputeUnitLimit(300_000))
+ executionEvent := EventExecutionStateChanged{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "ExecutionStateChanged", &executionEvent, config.PrintEvents))
+ require.Equal(t, ccip_router.Success_MessageExecutionState, executionEvent.State)
+
+ mintEvent := EventMintRelease{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "Minted", &mintEvent, config.PrintEvents))
+ require.Equal(t, config.ReceiverExternalExecutionConfigPDA, mintEvent.Recipient)
+ require.Equal(t, token0.PoolSigner, mintEvent.Sender)
+ require.Equal(t, uint64(1), mintEvent.Amount)
+
+ _, finalSupply, err := utils.TokenSupply(ctx, solanaGoClient, token0.Mint.PublicKey(), config.DefaultCommitment)
+ require.NoError(t, err)
+ _, finalBal, err := utils.TokenBalance(ctx, solanaGoClient, token0.User[config.ReceiverExternalExecutionConfigPDA], config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 1, finalSupply-initSupply)
+ require.Equal(t, 1, finalBal-initBal)
+ })
+
+ t.Run("OffRamp Manual Execution: when executing a non-committed report, it fails", func(t *testing.T) {
+ message, root := CreateNextMessage(ctx, solanaGoClient, t)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message,
+ Root: root,
+ Proofs: [][32]uint8{}, // single leaf merkle tree
+ }
+
+ raw := ccip_router.NewManuallyExecuteInstruction(
+ executionReport,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err := raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"The program expected this account to be already initialized"})
+ })
+
+ t.Run("OffRamp Manual execution", func(t *testing.T) {
+ transmitter := getTransmitter()
+
+ message1, _ := CreateNextMessage(ctx, solanaGoClient, t)
+ hash1, err := HashEvmToSolanaMessage(message1, config.OnRampAddress)
+ require.NoError(t, err)
+
+ message2 := CreateDefaultMessageWith(config.EvmChainSelector, message1.Header.SequenceNumber+1)
+ hash2, err := HashEvmToSolanaMessage(message2, config.OnRampAddress)
+ require.NoError(t, err)
+
+ root := [32]byte(MerkleFrom([][]byte{hash1, hash2}))
+
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: config.EvmChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: message1.Header.SequenceNumber,
+ MaxSeqNr: message2.Header.SequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, utils.AddComputeUnitLimit(210_000))
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ t.Run("Before elapsed time", func(t *testing.T) {
+ t.Run("When user manually executing before the period of time has passed, it fails", func(t *testing.T) {
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message1,
+ Root: root,
+ Proofs: [][32]uint8{[32]byte(hash2)},
+ }
+
+ raw := ccip_router.NewManuallyExecuteInstruction(
+ executionReport,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ fmt.Printf("User: %s\n", user.PublicKey().String())
+ fmt.Printf("Transmitter: %s\n", transmitter.PublicKey().String())
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{ccip_router.ManualExecutionNotAllowed_CcipRouterError.String()})
+ })
+
+ t.Run("When transmitter manually executing before the period of time has passed, it fails", func(t *testing.T) {
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message1,
+ Root: root,
+ Proofs: [][32]uint8{[32]byte(hash2)},
+ }
+
+ raw := ccip_router.NewManuallyExecuteInstruction(
+ executionReport,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{ccip_router.ManualExecutionNotAllowed_CcipRouterError.String()})
+ })
+ })
+
+ t.Run("Given the period of time has passed", func(t *testing.T) {
+ instruction, err = ccip_router.NewUpdateEnableManualExecutionAfterInstruction(
+ -1,
+ config.RouterConfigPDA,
+ anotherAdmin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ t.Run("When user manually executing after the period of time has passed, it succeeds", func(t *testing.T) {
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message1,
+ Root: root,
+ Proofs: [][32]uint8{[32]byte(hash2)},
+ }
+
+ raw := ccip_router.NewManuallyExecuteInstruction(
+ executionReport,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ tx = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment)
+ executionEvent := EventExecutionStateChanged{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "ExecutionStateChanged", &executionEvent, config.PrintEvents))
+
+ require.NoError(t, err)
+ require.NotNil(t, executionEvent)
+ require.Equal(t, config.EvmChainSelector, executionEvent.SourceChainSelector)
+ require.Equal(t, message1.Header.SequenceNumber, executionEvent.SequenceNumber)
+ require.Equal(t, hex.EncodeToString(message1.Header.MessageId[:]), hex.EncodeToString(executionEvent.MessageID[:]))
+ require.Equal(t, hex.EncodeToString(hash1[:]), hex.EncodeToString(executionEvent.MessageHash[:]))
+ require.Equal(t, ccip_router.Success_MessageExecutionState, executionEvent.State)
+
+ var rootAccount ccip_router.CommitReport
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, rootPDA, config.DefaultCommitment, &rootAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.NotEqual(t, bin.Uint128{Lo: 0, Hi: 0}, rootAccount.Timestamp)
+ require.Equal(t, bin.Uint128{Lo: 2, Hi: 0}, rootAccount.ExecutionStates)
+ require.Equal(t, commitReport.MerkleRoot.MinSeqNr, rootAccount.MinMsgNr)
+ require.Equal(t, commitReport.MerkleRoot.MaxSeqNr, rootAccount.MaxMsgNr)
+ })
+
+ t.Run("When transmitter executing after the period of time has passed, it succeeds", func(t *testing.T) {
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: config.EvmChainSelector,
+ Message: message2,
+ Root: root,
+ Proofs: [][32]uint8{[32]byte(hash1)},
+ }
+
+ raw := ccip_router.NewManuallyExecuteInstruction(
+ executionReport,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ tx = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ executionEvent := EventExecutionStateChanged{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "ExecutionStateChanged", &executionEvent, config.PrintEvents))
+
+ require.NoError(t, err)
+ require.NotNil(t, executionEvent)
+ require.Equal(t, config.EvmChainSelector, executionEvent.SourceChainSelector)
+ require.Equal(t, message2.Header.SequenceNumber, executionEvent.SequenceNumber)
+ require.Equal(t, hex.EncodeToString(message2.Header.MessageId[:]), hex.EncodeToString(executionEvent.MessageID[:]))
+ require.Equal(t, hex.EncodeToString(hash2[:]), hex.EncodeToString(executionEvent.MessageHash[:]))
+ require.Equal(t, ccip_router.Success_MessageExecutionState, executionEvent.State)
+
+ var rootAccount ccip_router.CommitReport
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, rootPDA, config.DefaultCommitment, &rootAccount)
+ require.NoError(t, err, "failed to get account info")
+ require.NotEqual(t, bin.Uint128{Lo: 0, Hi: 0}, rootAccount.Timestamp)
+ require.Equal(t, bin.Uint128{Lo: 10, Hi: 0}, rootAccount.ExecutionStates)
+ require.Equal(t, commitReport.MerkleRoot.MinSeqNr, rootAccount.MinMsgNr)
+ require.Equal(t, commitReport.MerkleRoot.MaxSeqNr, rootAccount.MaxMsgNr)
+ })
+ })
+ })
+
+ // solana re-entry is limited by a simple self-recursion and a limited depth
+ // https://defisec.info/solana_top_vulnerabilities
+ // note: simple recursion execute -> ccipSend is currently not possible as the router does not implement the ccipReceive method signature
+ t.Run("failed reentrancy A (execute) -> B (ccipReceive) -> A (ccipSend)", func(t *testing.T) {
+ transmitter := getTransmitter()
+ receiverContractEvmPDA, err := getNoncePDA(config.EvmChainSelector, config.ReceiverExternalExecutionConfigPDA)
+ require.NoError(t, err)
+
+ message, _ := CreateNextMessage(ctx, solanaGoClient, t)
+
+ // To make the message go through the validations we need to specify all additional accounts used when executing the CPI
+ message.ExtraArgs.Accounts = []ccip_router.SolanaAccountMeta{
+ {Pubkey: config.CcipReceiverProgram},
+ {Pubkey: config.ReceiverTargetAccountPDA, IsWritable: true},
+ {Pubkey: solana.SystemProgramID, IsWritable: false},
+ {Pubkey: config.CcipRouterProgram, IsWritable: false},
+ {Pubkey: config.RouterConfigPDA, IsWritable: false},
+ {Pubkey: config.ReceiverExternalExecutionConfigPDA, IsWritable: true},
+ {Pubkey: config.EvmChainStatePDA, IsWritable: true},
+ {Pubkey: receiverContractEvmPDA, IsWritable: true},
+ {Pubkey: solana.SystemProgramID, IsWritable: false},
+ }
+
+ hash, err := HashEvmToSolanaMessage(message, config.OnRampAddress)
+ require.NoError(t, err)
+ root := [32]byte(hash)
+
+ sourceChainSelector := config.EvmChainSelector
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: sourceChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: message.Header.SequenceNumber,
+ MaxSeqNr: message.Header.SequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("commit_report"), config.EvmChainLE, root[:]}, config.CcipRouterProgram)
+
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: sourceChainSelector,
+ Message: message,
+ Root: root,
+ Proofs: [][32]uint8{}, // single leaf merkle tree
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+ raw.AccountMetaSlice = append(
+ raw.AccountMetaSlice,
+ solana.NewAccountMeta(config.CcipReceiverProgram, false, false),
+ // accounts for base CPI call
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+
+ // accounts for receiver -> router re-entrant CPI call
+ solana.NewAccountMeta(config.CcipRouterProgram, false, false),
+ solana.NewAccountMeta(config.RouterConfigPDA, false, false),
+ solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false),
+ solana.NewAccountMeta(config.EvmChainStatePDA, true, false),
+ solana.NewAccountMeta(receiverContractEvmPDA, true, false),
+ solana.NewAccountMeta(solana.SystemProgramID, false, false),
+ )
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, []string{"Cross-program invocation reentrancy not allowed for this instruction"})
+ })
+
+ t.Run("uninitialized token account can be manually executed", func(t *testing.T) {
+ // create new token receiver + find address (does not actually create account, just instruction)
+ receiver, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+ ixATA, ata, err := utils.CreateAssociatedTokenAccount(token0.Program, token0.Mint.PublicKey(), receiver.PublicKey(), admin.PublicKey())
+ require.NoError(t, err)
+ token0.User[receiver.PublicKey()] = ata
+
+ // create commit report ---------------------
+ transmitter := getTransmitter()
+ sourceChainSelector := config.EvmChainSelector
+ message, _ := CreateNextMessage(ctx, solanaGoClient, t)
+ message.TokenAmounts = []ccip_router.Any2SolanaTokenTransfer{{
+ SourcePoolAddress: []byte{1, 2, 3},
+ DestTokenAddress: token0.Mint.PublicKey(),
+ Amount: utils.ToLittleEndianU256(1),
+ }}
+ message.Receiver = receiver.PublicKey()
+ rootBytes, err := HashEvmToSolanaMessage(message, config.OnRampAddress)
+ require.NoError(t, err)
+
+ root := [32]byte(rootBytes)
+ sequenceNumber := message.Header.SequenceNumber
+ commitReport := ccip_router.CommitInput{
+ MerkleRoot: ccip_router.MerkleRoot{
+ SourceChainSelector: sourceChainSelector,
+ OnRampAddress: config.OnRampAddress,
+ MinSeqNr: sequenceNumber,
+ MaxSeqNr: sequenceNumber,
+ MerkleRoot: root,
+ },
+ }
+ sigs, err := SignCommitReport(config.ReportContext, commitReport, signers)
+ require.NoError(t, err)
+ rootPDA, err := GetCommitReportPDA(config.EvmChainSelector, root)
+ require.NoError(t, err)
+ instruction, err := ccip_router.NewCommitInstruction(
+ config.ReportContext,
+ commitReport,
+ sigs,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment)
+ event := EventCommitReportAccepted{}
+ require.NoError(t, utils.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents))
+
+ // try to execute report ----------------------
+ // should fail because token account does not exist
+ executionReport := ccip_router.ExecutionReportSingleChain{
+ SourceChainSelector: sourceChainSelector,
+ Message: message,
+ OffchainTokenData: [][]byte{{}},
+ Root: root,
+ Proofs: [][32]uint8{},
+ TokenIndexes: []uint8{0}, // only token transfer message
+ }
+ raw := ccip_router.NewExecuteInstruction(
+ executionReport,
+ config.ReportContext,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ transmitter.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+
+ tokenMetas, addressTables, err := ParseTokenLookupTable(ctx, solanaGoClient, token0, token0.User[receiver.PublicKey()])
+ require.NoError(t, err)
+ raw.AccountMetaSlice = append(raw.AccountMetaSlice, tokenMetas...)
+ instruction, err = raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWithLookupTables(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, addressTables, []string{"AccountNotInitialized"})
+
+ // create associated token account for user --------------------
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixATA}, admin, config.DefaultCommitment)
+ _, initBal, err := utils.TokenBalance(ctx, solanaGoClient, token0.User[receiver.PublicKey()], config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 0, initBal)
+
+ // manual re-execution is successful -----------------------------------
+ // NOTE: expects re-execution time to be instantaneous
+ rawManual := ccip_router.NewManuallyExecuteInstruction(
+ executionReport,
+ config.RouterConfigPDA,
+ config.EvmChainStatePDA,
+ rootPDA,
+ config.ExternalExecutionConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ solana.SysVarInstructionsPubkey,
+ config.ExternalTokenPoolsSignerPDA,
+ )
+
+ tokenMetas, addressTables, err = ParseTokenLookupTable(ctx, solanaGoClient, token0, token0.User[receiver.PublicKey()])
+ require.NoError(t, err)
+ rawManual.AccountMetaSlice = append(rawManual.AccountMetaSlice, tokenMetas...)
+ instruction, err = rawManual.ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirmWithLookupTables(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment, addressTables)
+
+ _, finalBal, err := utils.TokenBalance(ctx, solanaGoClient, token0.User[receiver.PublicKey()], config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 1, finalBal-initBal)
+ })
+ })
+ })
+}
diff --git a/chains/solana/contracts/tests/ccip/ccip_transactions.go b/chains/solana/contracts/tests/ccip/ccip_transactions.go
new file mode 100644
index 000000000..534cbd6d6
--- /dev/null
+++ b/chains/solana/contracts/tests/ccip/ccip_transactions.go
@@ -0,0 +1,55 @@
+package contracts
+
+import (
+ "math/rand"
+
+ "github.com/decred/dcrd/dcrec/secp256k1/v4"
+ "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
+ "github.com/gagliardetto/solana-go"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router"
+)
+
+func SignCommitReport(ctx [3][32]byte, report ccip_router.CommitInput, baseSigners []eth.Signer) (sigs [][65]byte, err error) {
+ hash, err := HashCommitReport(ctx, report)
+ if err != nil {
+ return nil, err
+ }
+
+ // make copy to avoid race flakiness when randomizing with parallel tests
+ signers := make([]eth.Signer, len(baseSigners))
+ copy(signers, baseSigners)
+
+ // randomize signers
+ rand.Shuffle(len(signers), func(i, j int) {
+ signers[i], signers[j] = signers[j], signers[i]
+ })
+
+ for i := uint8(0); i < config.OcrF+1; i++ {
+ baseSig := ecdsa.SignCompact(secp256k1.PrivKeyFromBytes(signers[i].PrivateKey), hash, false)
+ baseSig[0] = baseSig[0] - 27 // key signs 27 or 28, but verification expects 0 or 1 (remove offset)
+ sigs = append(sigs, [65]byte(baseSig))
+ }
+ return sigs, nil
+}
+
+func getChainStatePDA(chainSelector uint64) (solana.PublicKey, error) {
+ chainSelectorLE := utils.Uint64ToLE(chainSelector)
+ p, _, err := solana.FindProgramAddress([][]byte{[]byte("chain_state"), chainSelectorLE}, config.CcipRouterProgram)
+ return p, err
+}
+
+func GetCommitReportPDA(chainSelector uint64, root [32]byte) (solana.PublicKey, error) {
+ chainSelectorLE := utils.Uint64ToLE(chainSelector)
+ p, _, err := solana.FindProgramAddress([][]byte{[]byte("commit_report"), chainSelectorLE, root[:]}, config.CcipRouterProgram)
+ return p, err
+}
+
+func getNoncePDA(chainSelector uint64, user solana.PublicKey) (solana.PublicKey, error) {
+ chainSelectorLE := utils.Uint64ToLE(chainSelector)
+ p, _, err := solana.FindProgramAddress([][]byte{[]byte("nonce"), chainSelectorLE, user.Bytes()}, config.CcipRouterProgram)
+ return p, err
+}
diff --git a/chains/solana/contracts/tests/ccip/tokenpool.go b/chains/solana/contracts/tests/ccip/tokenpool.go
new file mode 100644
index 000000000..7a269b703
--- /dev/null
+++ b/chains/solana/contracts/tests/ccip/tokenpool.go
@@ -0,0 +1,191 @@
+package contracts
+
+import (
+ "context"
+ "encoding/binary"
+ "strings"
+ "testing"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/rpc"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/token_pool"
+)
+
+type TokenPool struct {
+ // token details
+ Program solana.PublicKey
+ Mint solana.PrivateKey
+
+ // admin registry PDA
+ AdminRegistry solana.PublicKey
+
+ // pool details
+ PoolProgram, PoolConfig, PoolSigner, PoolTokenAccount solana.PublicKey
+ PoolLookupTable solana.PublicKey
+
+ AdditionalAccounts solana.PublicKeySlice
+
+ // AssociatedTokenAddress Lookups
+ User map[solana.PublicKey]solana.PublicKey
+
+ // remote chain config lookup
+ Chain map[uint64]solana.PublicKey
+
+ // billing config lookup
+ Billing map[uint64]solana.PublicKey
+}
+
+func (tp TokenPool) ToTokenPoolEntries() []solana.PublicKey {
+ list := solana.PublicKeySlice{
+ tp.PoolLookupTable,
+ tp.AdminRegistry,
+ tp.PoolProgram,
+ tp.PoolConfig,
+ tp.PoolTokenAccount,
+ tp.PoolSigner,
+ tp.Program,
+ tp.Mint.PublicKey(),
+ }
+ return append(list, tp.AdditionalAccounts...)
+}
+
+// NewTokenPool returns token + pool addresses. however, the token still needs to be deployed
+func NewTokenPool(program solana.PublicKey) (TokenPool, error) {
+ mint, err := solana.NewRandomPrivateKey()
+ if err != nil {
+ return TokenPool{}, err
+ }
+ tokenAdminRegistryPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("token_admin_registry"), mint.PublicKey().Bytes()}, config.CcipRouterProgram)
+ if err != nil {
+ return TokenPool{}, err
+ }
+
+ // preload with defined config.EvmChainSelector
+ chainPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("ccip_tokenpool_chainconfig"), binary.LittleEndian.AppendUint64([]byte{}, config.EvmChainSelector), mint.PublicKey().Bytes()}, config.CcipTokenPoolProgram)
+ if err != nil {
+ return TokenPool{}, err
+ }
+ billingPDA, _, err := solana.FindProgramAddress([][]byte{[]byte("ccip_tokenpool_billing"), binary.LittleEndian.AppendUint64([]byte{}, config.EvmChainSelector), mint.PublicKey().Bytes()}, config.CcipRouterProgram)
+ if err != nil {
+ return TokenPool{}, err
+ }
+
+ p := TokenPool{
+ Program: program,
+ Mint: mint,
+ AdminRegistry: tokenAdminRegistryPDA,
+ PoolLookupTable: solana.PublicKey{},
+ User: map[solana.PublicKey]solana.PublicKey{},
+ Chain: map[uint64]solana.PublicKey{},
+ Billing: map[uint64]solana.PublicKey{},
+ }
+ p.Chain[config.EvmChainSelector] = chainPDA
+ p.Billing[config.EvmChainSelector] = billingPDA
+ return p, nil
+}
+
+func (tp *TokenPool) SetupLookupTable(ctx context.Context, t *testing.T, client *rpc.Client, admin solana.PrivateKey) error {
+ table, err := utils.CreateLookupTable(ctx, t, client, admin)
+ if err != nil {
+ return err
+ }
+ tp.PoolLookupTable = table // the LUT entries will include this, so set it before adding the addresses to the LUT
+ utils.ExtendLookupTable(ctx, t, client, table, admin, tp.ToTokenPoolEntries())
+ return utils.AwaitSlotChange(ctx, client)
+}
+
+func TokenPoolConfigAddress(token solana.PublicKey) (solana.PublicKey, error) {
+ addr, _, err := solana.FindProgramAddress([][]byte{[]byte("ccip_tokenpool_config"), token.Bytes()}, config.CcipTokenPoolProgram)
+ return addr, err
+}
+
+func TokenPoolSignerAddress(token solana.PublicKey) (solana.PublicKey, error) {
+ addr, _, err := solana.FindProgramAddress([][]byte{[]byte("ccip_tokenpool_signer"), token.Bytes()}, config.CcipTokenPoolProgram)
+ return addr, err
+}
+
+type EventBurnLock struct {
+ Discriminator [8]byte
+ Sender solana.PublicKey
+ Amount uint64
+}
+
+type EventMintRelease struct {
+ Discriminator [8]byte
+ Sender solana.PublicKey
+ Recipient solana.PublicKey
+ Amount uint64
+}
+
+type EventChainConfigured struct {
+ Discriminator [8]byte
+ ChainSelector uint64
+ Token []byte
+ PreviousToken []byte
+ PoolAddress []byte
+ PreviousPoolAddress []byte
+}
+
+type EventRateLimitConfigured struct {
+ Discriminator [8]byte
+ ChainSelector uint64
+ OutboundRateLimit token_pool.RateLimitConfig
+ InboundRateLimit token_pool.RateLimitConfig
+}
+
+type EventChainRemoved struct {
+ Discriminator [8]byte
+ ChainSelector uint64
+}
+
+type EventRouterUpdated struct {
+ Discriminator [8]byte
+ OldAuthority solana.PublicKey
+ NewAuthority solana.PublicKey
+}
+
+func MethodToEvent(m string) string {
+ mapping := map[string]string{
+ "lock": "Locked",
+ "release": "Released",
+ "burn": "Burned",
+ "mint": "Minted",
+ }
+ return mapping[strings.ToLower(m)]
+}
+
+func ParseTokenLookupTable(ctx context.Context, client *rpc.Client, token TokenPool, userTokenAccount solana.PublicKey) (solana.AccountMetaSlice, map[solana.PublicKey]solana.PublicKeySlice, error) {
+ tokenBillingConfig := token.Billing[config.EvmChainSelector]
+ poolChainConfig := token.Chain[config.EvmChainSelector]
+
+ lookupTableEntries, err := utils.GetAddressLookupTable(ctx, client, token.PoolLookupTable)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ list := []*solana.AccountMeta{
+ solana.Meta(userTokenAccount).WRITE(),
+ solana.Meta(tokenBillingConfig),
+ solana.Meta(poolChainConfig).WRITE(),
+ solana.Meta(lookupTableEntries[0]), // lookup table
+ solana.Meta(lookupTableEntries[1]), // token admin registry
+ solana.Meta(lookupTableEntries[2]), // PoolProgram
+ solana.Meta(lookupTableEntries[3]).WRITE(), // PoolConfig
+ solana.Meta(lookupTableEntries[4]).WRITE(), // PoolTokenAccount
+ solana.Meta(lookupTableEntries[5]), // PoolSigner
+ solana.Meta(lookupTableEntries[6]), // TokenProgram
+ solana.Meta(lookupTableEntries[7]).WRITE(), // Mint
+ }
+
+ for _, v := range token.AdditionalAccounts {
+ list = append(list, solana.Meta(v))
+ }
+
+ addressTables := make(map[solana.PublicKey]solana.PublicKeySlice)
+ addressTables[token.PoolLookupTable] = lookupTableEntries
+
+ return list, addressTables, nil
+}
diff --git a/chains/solana/contracts/tests/ccip/tokenpool_test.go b/chains/solana/contracts/tests/ccip/tokenpool_test.go
new file mode 100644
index 000000000..f6ba3b9de
--- /dev/null
+++ b/chains/solana/contracts/tests/ccip/tokenpool_test.go
@@ -0,0 +1,425 @@
+package contracts
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/token_pool"
+)
+
+func TestTokenPool(t *testing.T) {
+ t.Parallel()
+
+ token_pool.SetProgramID(config.CcipTokenPoolProgram)
+
+ admin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+ anotherAdmin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+ ctx := tests.Context(t)
+
+ solanaGoClient := utils.DeployAllPrograms(t, utils.PathToAnchorConfig, admin)
+ getBalance := func(account solana.PublicKey) string {
+ balanceRes, err := solanaGoClient.GetTokenAccountBalance(ctx, account, config.DefaultCommitment)
+ require.NoError(t, err)
+ return balanceRes.Value.Amount
+ }
+
+ remotePool := []byte{1, 2, 3}
+ remoteToken := []byte{4, 5, 6}
+
+ t.Run("setup:funding", func(t *testing.T) {
+ utils.FundAccounts(ctx, []solana.PrivateKey{admin, anotherAdmin}, solanaGoClient, t)
+ })
+
+ // test functionality with token & token-2022 standards
+ for _, v := range []struct {
+ tokenName string
+ tokenProgram solana.PublicKey
+ }{
+ {tokenName: "spl-token", tokenProgram: solana.TokenProgramID},
+ {tokenName: "spl-token-2022", tokenProgram: config.Token2022Program},
+ } {
+ t.Run(v.tokenName, func(t *testing.T) {
+ t.Parallel()
+ decimals := uint8(0)
+ amount := uint64(1000)
+
+ for _, poolType := range []token_pool.PoolType{token_pool.LockAndRelease_PoolType, token_pool.BurnAndMint_PoolType} {
+ p, err := NewTokenPool(v.tokenProgram)
+ require.NoError(t, err)
+ mint := p.Mint.PublicKey()
+
+ t.Run("setup:token", func(t *testing.T) {
+ // create token
+ instructions, err := utils.CreateToken(ctx, v.tokenProgram, mint, admin.PublicKey(), decimals, solanaGoClient, config.DefaultCommitment)
+ require.NoError(t, err)
+
+ // create admin associated token account
+ createI, tokenAccount, err := utils.CreateAssociatedTokenAccount(v.tokenProgram, mint, admin.PublicKey(), admin.PublicKey())
+ require.NoError(t, err)
+ p.User[admin.PublicKey()] = tokenAccount // set admin token account
+
+ // mint tokens to admin
+ mintToI, err := utils.MintTo(amount, v.tokenProgram, mint, tokenAccount, admin.PublicKey())
+ require.NoError(t, err)
+
+ instructions = append(instructions, createI, mintToI)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, instructions, admin, config.DefaultCommitment, utils.AddSigners(p.Mint))
+
+ // validate
+ outDec, outVal, err := utils.TokenBalance(ctx, solanaGoClient, p.User[admin.PublicKey()], config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, int(amount), outVal)
+ require.Equal(t, decimals, outDec)
+ })
+
+ t.Run("pool:"+poolType.String(), func(t *testing.T) {
+ poolConfig, err := TokenPoolConfigAddress(mint)
+ require.NoError(t, err)
+ poolSigner, err := TokenPoolSignerAddress(mint)
+ require.NoError(t, err)
+ createI, poolTokenAccount, err := utils.CreateAssociatedTokenAccount(v.tokenProgram, mint, poolSigner, admin.PublicKey())
+ require.NoError(t, err)
+
+ // LockAndRelease => [Lock, Release]
+ // BurnAndMint => [Burn, Mint]
+ poolMethodName := strings.Split(poolType.String(), "And")
+ require.Equal(t, 2, len(poolMethodName))
+ lockOrBurn := poolMethodName[0]
+ releaseOrMint := poolMethodName[1]
+
+ t.Run("setup", func(t *testing.T) {
+ poolInitI, err := token_pool.NewInitializeInstruction(poolType, admin.PublicKey(), poolConfig, mint, poolSigner, admin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+
+ // make pool mint_authority for token (required for burn/mint)
+ authI, err := utils.SetTokenMintAuthority(v.tokenProgram, poolSigner, mint, admin.PublicKey())
+ require.NoError(t, err)
+
+ // set pool config
+ ixConfigure, err := token_pool.NewSetChainRemoteConfigInstruction(config.EvmChainSelector, p.Mint.PublicKey(), token_pool.RemoteConfig{
+ PoolAddress: remotePool,
+ TokenAddress: remoteToken,
+ Decimals: 9,
+ }, poolConfig, p.Chain[config.EvmChainSelector], admin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+ // set rate limit
+ ixRates, err := token_pool.NewSetChainRateLimitInstruction(config.EvmChainSelector, p.Mint.PublicKey(), token_pool.RateLimitConfig{
+ Enabled: true,
+ Capacity: amount,
+ Rate: 1, // slow refill
+ }, token_pool.RateLimitConfig{
+ Enabled: false,
+ Capacity: 0,
+ Rate: 0,
+ }, poolConfig, p.Chain[config.EvmChainSelector], admin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+
+ res := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{poolInitI, createI, authI, ixConfigure, ixRates}, admin, config.DefaultCommitment)
+ require.NotNil(t, res)
+
+ var configAccount token_pool.Config
+ require.NoError(t, utils.GetAccountDataBorshInto(ctx, solanaGoClient, poolConfig, config.DefaultCommitment, &configAccount))
+ require.Equal(t, poolTokenAccount, configAccount.PoolTokenAccount)
+
+ eventConfigured := EventChainConfigured{}
+ require.NoError(t, utils.ParseEvent(res.Meta.LogMessages, "RemoteChainConfigured", &eventConfigured, config.PrintEvents))
+ require.Equal(t, config.EvmChainSelector, eventConfigured.ChainSelector)
+ require.Equal(t, remotePool, eventConfigured.PoolAddress)
+ require.Equal(t, 0, len(eventConfigured.PreviousPoolAddress))
+ require.Equal(t, remoteToken, eventConfigured.Token)
+ require.Equal(t, 0, len(eventConfigured.PreviousToken))
+
+ eventRateLimit := EventRateLimitConfigured{}
+ require.NoError(t, utils.ParseEvent(res.Meta.LogMessages, "RateLimitConfigured", &eventRateLimit, config.PrintEvents))
+ require.Equal(t, config.EvmChainSelector, eventRateLimit.ChainSelector)
+ require.Equal(t, true, eventRateLimit.InboundRateLimit.Enabled)
+ require.Equal(t, amount, eventRateLimit.InboundRateLimit.Capacity)
+ require.Equal(t, uint64(1), eventRateLimit.InboundRateLimit.Rate)
+ require.Equal(t, false, eventRateLimit.OutboundRateLimit.Enabled)
+ require.Equal(t, uint64(0), eventRateLimit.OutboundRateLimit.Capacity)
+ require.Equal(t, uint64(0), eventRateLimit.OutboundRateLimit.Rate)
+ })
+
+ t.Run("admin:ownership", func(t *testing.T) {
+ // successfully transfer ownership
+ instruction, err := token_pool.NewTransferOwnershipInstruction(
+ anotherAdmin.PublicKey(),
+ poolConfig,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // Successfully accept ownership
+ // anotherAdmin becomes owner for remaining tests
+ instruction, err = token_pool.NewAcceptOwnershipInstruction(
+ poolConfig,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, result)
+ })
+
+ t.Run(lockOrBurn, func(t *testing.T) {
+ require.Equal(t, fmt.Sprintf("%d", amount), getBalance(p.User[admin.PublicKey()]))
+
+ transferI, err := utils.TokenTransferChecked(amount, decimals, v.tokenProgram, p.User[admin.PublicKey()], mint, poolTokenAccount, admin.PublicKey(), solana.PublicKeySlice{})
+ require.NoError(t, err)
+
+ lbI, err := token_pool.NewLockOrBurnTokensInstruction(token_pool.LockOrBurnInV1{
+ LocalToken: mint,
+ Amount: amount,
+ RemoteChainSelector: config.EvmChainSelector,
+ }, admin.PublicKey(), poolConfig, v.tokenProgram, mint, poolSigner, poolTokenAccount, p.Chain[config.EvmChainSelector]).ValidateAndBuild()
+ require.NoError(t, err)
+
+ res := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{transferI, lbI}, admin, config.DefaultCommitment)
+ require.NotNil(t, res)
+
+ event := EventBurnLock{}
+ require.NoError(t, utils.ParseEvent(res.Meta.LogMessages, MethodToEvent(lockOrBurn), &event))
+ require.Equal(t, admin.PublicKey(), event.Sender)
+ require.Equal(t, amount, event.Amount)
+
+ // validate balances
+ require.Equal(t, "0", getBalance(p.User[admin.PublicKey()]))
+ expectedPoolBal := uint64(0)
+ if poolType == token_pool.LockAndRelease_PoolType {
+ expectedPoolBal = amount
+ }
+ require.Equal(t, fmt.Sprintf("%d", expectedPoolBal), getBalance(poolTokenAccount))
+ })
+
+ t.Run(releaseOrMint, func(t *testing.T) {
+ require.Equal(t, "0", getBalance(p.User[admin.PublicKey()]))
+
+ rmI, err := token_pool.NewReleaseOrMintTokensInstruction(token_pool.ReleaseOrMintInV1{
+ LocalToken: mint,
+ SourcePoolAddress: remotePool,
+ Amount: utils.ToLittleEndianU256(amount * 1e9), // scale to proper decimals
+ Receiver: admin.PublicKey(),
+ RemoteChainSelector: config.EvmChainSelector,
+ }, admin.PublicKey(), poolConfig, v.tokenProgram, mint, poolSigner, poolTokenAccount, p.Chain[config.EvmChainSelector], p.User[admin.PublicKey()]).ValidateAndBuild()
+ require.NoError(t, err)
+
+ res := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{rmI}, admin, config.DefaultCommitment)
+ require.NotNil(t, res)
+
+ event := EventMintRelease{}
+ require.NoError(t, utils.ParseEvent(res.Meta.LogMessages, MethodToEvent(releaseOrMint), &event))
+ require.Equal(t, admin.PublicKey(), event.Recipient)
+ require.Equal(t, poolSigner, event.Sender)
+ require.Equal(t, amount, event.Amount)
+
+ // validate balances
+ require.Equal(t, fmt.Sprintf("%d", amount), getBalance(p.User[admin.PublicKey()]))
+ require.Equal(t, "0", getBalance(poolTokenAccount))
+ })
+
+ t.Run("invalid", func(t *testing.T) {
+ t.Run("config", func(t *testing.T) {
+ t.Parallel()
+
+ cfgs := []struct {
+ name string
+ c token_pool.RateLimitConfig
+ errStr string
+ }{
+ {
+ name: "enabled-zero-rate",
+ c: token_pool.RateLimitConfig{
+ Enabled: true,
+ Capacity: 10,
+ Rate: 0,
+ },
+ errStr: "invalid rate limit rate",
+ },
+ {
+ name: "enabled-rate-larger-than-capacity",
+ c: token_pool.RateLimitConfig{
+ Enabled: true,
+ Capacity: 1,
+ Rate: 100,
+ },
+ errStr: "invalid rate limit rate",
+ },
+ {
+ name: "disabled-nonzero-rate",
+ c: token_pool.RateLimitConfig{
+ Enabled: false,
+ Capacity: 0,
+ Rate: 100,
+ },
+ errStr: "disabled non-zero rate limit",
+ },
+ {
+ name: "disabled-nonzero-capacity",
+ c: token_pool.RateLimitConfig{
+ Enabled: false,
+ Capacity: 10,
+ Rate: 0,
+ },
+ errStr: "disabled non-zero rate limit",
+ },
+ }
+
+ for _, cfg := range cfgs {
+ t.Run(cfg.name, func(t *testing.T) {
+ t.Parallel()
+
+ ixRates, err := token_pool.NewSetChainRateLimitInstruction(config.EvmChainSelector, p.Mint.PublicKey(), cfg.c, token_pool.RateLimitConfig{}, poolConfig, p.Chain[config.EvmChainSelector], anotherAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ixRates}, anotherAdmin, config.DefaultCommitment, []string{cfg.errStr})
+ })
+ }
+ })
+
+ t.Run("exceed-rate-limit", func(t *testing.T) {
+ t.Parallel()
+
+ // exceed capacity of bucket
+ rmI, err := token_pool.NewReleaseOrMintTokensInstruction(token_pool.ReleaseOrMintInV1{
+ LocalToken: mint,
+ SourcePoolAddress: remotePool,
+ Amount: utils.ToLittleEndianU256(amount * amount * 1e9),
+ Receiver: admin.PublicKey(),
+ RemoteChainSelector: config.EvmChainSelector,
+ }, admin.PublicKey(), poolConfig, v.tokenProgram, mint, poolSigner, poolTokenAccount, p.Chain[config.EvmChainSelector], p.User[admin.PublicKey()]).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{rmI}, admin, config.DefaultCommitment, []string{"max capacity exceeded"})
+
+ // exceed rate limit of transfer
+ // request two release/mint of max capacity
+ // if first does not exceed limit, the second one should
+ transferI, err := utils.TokenTransferChecked(amount, decimals, v.tokenProgram, p.User[admin.PublicKey()], mint, poolTokenAccount, admin.PublicKey(), solana.PublicKeySlice{}) // ensure pool is funded
+ require.NoError(t, err)
+ rmI, err = token_pool.NewReleaseOrMintTokensInstruction(token_pool.ReleaseOrMintInV1{
+ LocalToken: mint,
+ SourcePoolAddress: remotePool,
+ Amount: utils.ToLittleEndianU256(amount * 1e9),
+ Receiver: admin.PublicKey(),
+ RemoteChainSelector: config.EvmChainSelector,
+ }, admin.PublicKey(), poolConfig, v.tokenProgram, mint, poolSigner, poolTokenAccount, p.Chain[config.EvmChainSelector], p.User[admin.PublicKey()]).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{transferI, rmI, rmI}, admin, config.DefaultCommitment, []string{"rate limit reached"})
+
+ // pool should refill automatically, but slowly
+ // small amount should pass
+ time.Sleep(time.Second) // wait for refill
+ rmI, err = token_pool.NewReleaseOrMintTokensInstruction(token_pool.ReleaseOrMintInV1{
+ LocalToken: mint,
+ SourcePoolAddress: remotePool,
+ Amount: utils.ToLittleEndianU256(1e9),
+ Receiver: admin.PublicKey(),
+ RemoteChainSelector: config.EvmChainSelector,
+ }, admin.PublicKey(), poolConfig, v.tokenProgram, mint, poolSigner, poolTokenAccount, p.Chain[config.EvmChainSelector], p.User[admin.PublicKey()]).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{transferI, rmI}, admin, config.DefaultCommitment)
+ })
+ })
+
+ t.Run("closing", func(t *testing.T) {
+ ixDelete, err := token_pool.NewDeleteChainConfigInstruction(config.EvmChainSelector, mint, poolConfig, p.Chain[config.EvmChainSelector], anotherAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+
+ ixRouterChange, err := token_pool.NewSetRampAuthorityInstruction(config.ExternalTokenPoolsSignerPDA, poolConfig, anotherAdmin.PublicKey()).ValidateAndBuild()
+ require.NoError(t, err)
+
+ res := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixDelete, ixRouterChange}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, res)
+
+ eventDelete := EventChainRemoved{}
+ require.NoError(t, utils.ParseEvent(res.Meta.LogMessages, "RemoteChainRemoved", &eventDelete, config.PrintEvents))
+ require.Equal(t, config.EvmChainSelector, eventDelete.ChainSelector)
+
+ eventRouter := EventRouterUpdated{}
+ require.NoError(t, utils.ParseEvent(res.Meta.LogMessages, "RouterUpdated", &eventRouter, config.PrintEvents))
+ require.Equal(t, config.ExternalTokenPoolsSignerPDA, eventRouter.NewAuthority)
+ require.Equal(t, admin.PublicKey(), eventRouter.OldAuthority)
+ })
+ })
+ }
+ })
+ }
+
+ // test functionality with arbitrary wrapped program
+ t.Run("Wrapped", func(t *testing.T) {
+ t.Parallel()
+ p, err := NewTokenPool(solana.TokenProgramID)
+ require.NoError(t, err)
+ mint := p.Mint.PublicKey()
+
+ t.Run("setup:pool", func(t *testing.T) {
+ var err error
+ p.PoolConfig, err = TokenPoolConfigAddress(mint)
+ require.NoError(t, err)
+ p.PoolSigner, err = TokenPoolSignerAddress(mint)
+ require.NoError(t, err)
+
+ // create token
+ instructions, err := utils.CreateToken(ctx, solana.TokenProgramID, mint, admin.PublicKey(), 0, solanaGoClient, config.DefaultCommitment)
+ require.NoError(t, err)
+
+ // create pool
+ poolInitI, err := token_pool.NewInitializeInstruction(token_pool.Wrapped_PoolType, admin.PublicKey(), p.PoolConfig, mint, p.PoolSigner, admin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+
+ // create pool token account
+ var createI solana.Instruction
+ createI, p.PoolTokenAccount, err = utils.CreateAssociatedTokenAccount(solana.TokenProgramID, mint, p.PoolSigner, admin.PublicKey())
+ require.NoError(t, err)
+
+ // set pool config
+ configureI, err := token_pool.NewSetChainRemoteConfigInstruction(config.EvmChainSelector, p.Mint.PublicKey(), token_pool.RemoteConfig{
+ PoolAddress: remotePool,
+ TokenAddress: remoteToken,
+ }, p.PoolConfig, p.Chain[config.EvmChainSelector], admin.PublicKey(), solana.SystemProgramID).ValidateAndBuild()
+ require.NoError(t, err)
+
+ res := utils.SendAndConfirm(ctx, t, solanaGoClient, append(instructions, poolInitI, createI, configureI), admin, config.DefaultCommitment, utils.AddSigners(p.Mint))
+ require.NotNil(t, res)
+ })
+
+ t.Run("burnOrLock", func(t *testing.T) {
+ raw := token_pool.NewLockOrBurnTokensInstruction(token_pool.LockOrBurnInV1{LocalToken: mint, RemoteChainSelector: config.EvmChainSelector}, admin.PublicKey(), p.PoolConfig, solana.TokenProgramID, mint, p.PoolSigner, p.PoolTokenAccount, p.Chain[config.EvmChainSelector])
+ raw.AccountMetaSlice = append(raw.AccountMetaSlice, solana.NewAccountMeta(config.CcipReceiverProgram, false, false))
+ lbI, err := raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ res := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{lbI}, admin, config.DefaultCommitment)
+ require.NotNil(t, res)
+ require.Contains(t, strings.Join(res.Meta.LogMessages, "\n"), "Called `ccip_token_lock_burn`")
+ })
+
+ t.Run("mintOrRelease", func(t *testing.T) {
+ raw := token_pool.NewReleaseOrMintTokensInstruction(token_pool.ReleaseOrMintInV1{
+ LocalToken: mint,
+ SourcePoolAddress: remotePool,
+ Receiver: p.PoolSigner,
+ RemoteChainSelector: config.EvmChainSelector,
+ Amount: utils.ToLittleEndianU256(1),
+ }, admin.PublicKey(), p.PoolConfig, solana.TokenProgramID, mint, p.PoolSigner, p.PoolTokenAccount, p.Chain[config.EvmChainSelector], p.PoolTokenAccount)
+ raw.AccountMetaSlice = append(raw.AccountMetaSlice, solana.NewAccountMeta(config.CcipReceiverProgram, false, false))
+ rmI, err := raw.ValidateAndBuild()
+ require.NoError(t, err)
+
+ res := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{rmI}, admin, config.DefaultCommitment)
+ require.NotNil(t, res)
+ require.Contains(t, strings.Join(res.Meta.LogMessages, "\n"), "Called `ccip_token_release_mint`")
+ })
+ })
+}
diff --git a/chains/solana/contracts/tests/config/access_controller_config.go b/chains/solana/contracts/tests/config/access_controller_config.go
new file mode 100644
index 000000000..0733a946b
--- /dev/null
+++ b/chains/solana/contracts/tests/config/access_controller_config.go
@@ -0,0 +1,10 @@
+package config
+
+import (
+ "github.com/gagliardetto/solana-go"
+)
+
+var (
+ AccessControllerProgram = solana.MustPublicKeyFromBase58("9xi644bRR8birboDGdTiwBq3C7VEeR7VuamRYYXCubUW")
+ AccSpace = uint64(8 + 32 + 32 + ((32 * 64) + 8)) // discriminator + owner + proposed owner + access_list (64 max addresses + length)
+)
diff --git a/chains/solana/contracts/tests/config/ccip_config.go b/chains/solana/contracts/tests/config/ccip_config.go
new file mode 100644
index 000000000..315ca7b2b
--- /dev/null
+++ b/chains/solana/contracts/tests/config/ccip_config.go
@@ -0,0 +1,54 @@
+package config
+
+import (
+ "encoding/binary"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/rpc"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+)
+
+var (
+ PrintEvents = true
+ DefaultCommitment = rpc.CommitmentConfirmed
+
+ CcipRouterProgram = solana.MustPublicKeyFromBase58("C8WSPj3yyus1YN3yNB6YA5zStYtbjQWtpmKadmvyUXq8")
+ CcipReceiverProgram = solana.MustPublicKeyFromBase58("CtEVnHsQzhTNWav8skikiV2oF6Xx7r7uGGa8eCDQtTjH")
+ CcipReceiverAddress = solana.MustPublicKeyFromBase58("DS2tt4BX7YwCw7yrDNwbAdnYrxjeCPeGJbHmZEYC8RTb")
+ CcipInvalidReceiverProgram = solana.MustPublicKeyFromBase58("9Vjda3WU2gsJgE4VdU6QuDw8rfHLyigfFyWs3XDPNUn8")
+ CcipTokenPoolProgram = solana.MustPublicKeyFromBase58("GRvFSLwR7szpjgNEZbGe4HtxfJYXqySXuuRUAJDpu4WH")
+ Token2022Program = solana.MustPublicKeyFromBase58("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
+
+ RouterConfigPDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("config")}, CcipRouterProgram)
+ ExternalExecutionConfigPDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("external_execution_config")}, CcipRouterProgram)
+ ExternalTokenPoolsSignerPDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("external_token_pools_signer")}, CcipRouterProgram)
+ ReceiverTargetAccountPDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("counter")}, CcipReceiverProgram)
+ ReceiverExternalExecutionConfigPDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("external_execution_config")}, CcipReceiverProgram)
+ BillingSignerPDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("fee_billing_signer")}, CcipRouterProgram)
+
+ BillingTokenConfigPrefix = []byte("fee_billing_token_config")
+ DestChainConfigPrefix = []byte("destination_billing_config")
+
+ SolanaChainSelector uint64 = 15
+ EvmChainSelector uint64 = 21
+
+ SolanaChainStatePDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("chain_state"), binary.LittleEndian.AppendUint64([]byte{}, SolanaChainSelector)}, CcipRouterProgram)
+ EvmChainLE = utils.Uint64ToLE(EvmChainSelector)
+ EvmChainStatePDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("chain_state"), binary.LittleEndian.AppendUint64([]byte{}, EvmChainSelector)}, CcipRouterProgram)
+
+ OnRampAddress = []byte{1, 2, 3}
+ EnableExecutionAfter = int64(1800) // 30min
+
+ MaxOracles = 16
+ OcrF uint8 = 5
+ ConfigDigest = utils.MakeRandom32ByteArray()
+ Empty24Byte = [24]byte{}
+ ReportSequence = uint64(8)
+ ReportContext = [3][32]byte{
+ ConfigDigest,
+ [32]byte(binary.BigEndian.AppendUint64(Empty24Byte[:], ReportSequence)),
+ utils.MakeRandom32ByteArray(),
+ }
+ MaxSignersAndTransmitters = 16
+)
diff --git a/chains/solana/contracts/tests/config/config.go b/chains/solana/contracts/tests/config/config.go
new file mode 100644
index 000000000..1b96b1f77
--- /dev/null
+++ b/chains/solana/contracts/tests/config/config.go
@@ -0,0 +1,41 @@
+package config
+
+type Config struct {
+ ChainName string
+ ChainID string
+ RPCUrls []string
+ WSUrls []string
+ ProgramAddresses *ProgramAddresses
+ PrivateKey string
+}
+
+type ProgramAddresses struct {
+ OCR2 string
+ AccessController string
+ Store string
+}
+
+func DevnetConfig() *Config {
+ return &Config{
+ ChainName: "solana",
+ ChainID: "devnet",
+ // Will be overridden if set in toml
+ RPCUrls: []string{"https://api.devnet.solana.com"},
+ WSUrls: []string{"wss://api.devnet.solana.com/"},
+ }
+}
+
+func LocalNetConfig() *Config {
+ return &Config{
+ ChainName: "solana",
+ ChainID: "localnet",
+ // Will be overridden if set in toml
+ RPCUrls: []string{"http://sol:8899"},
+ WSUrls: []string{"ws://sol:8900"},
+ ProgramAddresses: &ProgramAddresses{
+ OCR2: "E3j24rx12SyVsG6quKuZPbQqZPkhAUCh8Uek4XrKYD2x",
+ AccessController: "2ckhep7Mvy1dExenBqpcdevhRu7CLuuctMcx7G9mWEvo",
+ Store: "9kRNTZmoZSiTBuXC62dzK9E7gC7huYgcmRRhYv3i4osC",
+ },
+ }
+}
diff --git a/chains/solana/contracts/tests/config/mcm_config.go b/chains/solana/contracts/tests/config/mcm_config.go
new file mode 100644
index 000000000..05268e4df
--- /dev/null
+++ b/chains/solana/contracts/tests/config/mcm_config.go
@@ -0,0 +1,64 @@
+package config
+
+import (
+ "github.com/gagliardetto/solana-go"
+)
+
+var (
+ McmProgram = solana.MustPublicKeyFromBase58("6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX")
+
+ // For testing CPIs made by other programs (with actual business logic).
+ ExternalCpiStubProgram = solana.MustPublicKeyFromBase58("4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ")
+ StubAccountPDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("u8_value")}, ExternalCpiStubProgram)
+
+ // todo: update chain id following the latest discussion(genesis hash)
+ // Last 8 bytes (uint64) of keccak256("solana:localnet") as big-endian
+ // This is 0x4808e31713a26612 --> in little-endian, it is "1266a21317e30848"
+ TestChainID uint64 = 5190648258797659666
+ TestChainIDPaddedBuffer = [32]byte{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x66, 0xa2, 0x13, 0x17, 0xe3, 0x08, 0x48,
+ }
+
+ // [0,0,0,...'t','e','s','t','-','m','c','m',]
+ TestMsigNamePaddedBuffer = [32]byte{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x6d, 0x63, 0x6d,
+ }
+ MaxNumSigners = 200
+ MaxAppendSignerBatchSize = 45
+ MaxAppendSignatureBatchSize = 13
+
+ // root related configs
+ // the following diagram shows the structure of the signers and groups:
+ // ref: https://github.com/smartcontractkit/ccip-owner-contracts/blob/56f1a8d2cd4ba5ef2b99d2185ffded53957dd410/src/ManyChainMultiSig.sol#L65
+ // ┌──────┐ root
+ // ┌─►│2-of-3│◄───────┐
+ // │ └──────┘ │
+ // │ ▲ │
+ // │ group1 │ group2 │
+ // ┌──┴───┐ ┌──┴───┐ ┌───┴────┐
+ // ┌──►│1-of-2│ │2-of-2│ │signer A│
+ // │ └──────┘ └──────┘ └────────┘
+ // │ ▲ ▲ ▲ group3
+ // │ │ │ │ ┌──────┐
+ // │ │ │ └─────┤1-of-2│◄─┐
+ // │ │ │ └──────┘ │
+ // ┌───────┴┐ ┌────┴───┐ ┌┴───────┐ ▲ │
+ // │signer B│ │signer C│ │signer D│ │ │
+ // └────────┘ └────────┘ └────────┘ │ │
+ // │ │
+ // ┌──────┴─┐ ┌────┴───┐
+ // │signer E│ │signer F│
+ // └────────┘ └────────┘
+ SignerPrivateKeys = []string{
+ "aa4dc5ba14d8921dca4f486a0b5bc573502e33d9093025479fc52f22e8d8a4b7",
+ "82dbcde99a61371aaa0aee75b04fa5663832a1041232ed8de868b4f4b186f00a",
+ "1902e25f992351a3194e16617f879b67c6f7a8a084fea94b77c5b4f1398ff5a6",
+ "f25eee250faea64943df683e48a3c804d4616ad35a4dab5654428916bea7e234",
+ "169aa793d6325a77b3454572da45d421c189bef166ab778b5f35023522046916",
+ "88dfb2c77c655efb5c9d723b4518ea8a7c5416a9951c7f192e254cb446b8db6c",
+ }
+ SignerGroups = []byte{0, 1, 1, 2, 3, 3}
+ GroupQuorums = []uint8{2, 1, 2, 1}
+ GroupParents = []uint8{0, 0, 0, 2}
+ ClearRoot = false
+)
diff --git a/chains/solana/contracts/tests/config/timelock_config.go b/chains/solana/contracts/tests/config/timelock_config.go
new file mode 100644
index 000000000..b304db794
--- /dev/null
+++ b/chains/solana/contracts/tests/config/timelock_config.go
@@ -0,0 +1,25 @@
+package config
+
+import (
+ "github.com/gagliardetto/solana-go"
+)
+
+var (
+ TimelockProgram = solana.MustPublicKeyFromBase58("LoCoNsJFuhTkSQjfdDfn3yuwqhSYoPujmviRHVCzsqn")
+ TimelockConfigPDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("timelock_config")}, TimelockProgram)
+ TimelockSignerPDA, _, _ = solana.FindProgramAddress([][]byte{[]byte("timelock_signer")}, TimelockProgram)
+ TimelockOperationPDA = func(id [32]byte) solana.PublicKey {
+ pda, _, _ := solana.FindProgramAddress([][]byte{
+ []byte("timelock_operation"),
+ id[:],
+ }, TimelockProgram)
+ return pda
+ }
+ NumAccountsPerRole = 63 // max 64 accounts per role(access list) * 4 - 1(to keep test accounts fits single funding)
+ BatchAddAccessChunkSize = 24
+
+ MinDelay = uint64(1)
+
+ TimelockEmptyOpID = [32]byte{}
+ TimelockOpDoneTimestamp = uint64(1)
+)
diff --git a/chains/solana/contracts/tests/lookuptable_test.go b/chains/solana/contracts/tests/lookuptable_test.go
new file mode 100644
index 000000000..f58408a39
--- /dev/null
+++ b/chains/solana/contracts/tests/lookuptable_test.go
@@ -0,0 +1,124 @@
+package contracts
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/gagliardetto/solana-go"
+ addresslookuptable "github.com/gagliardetto/solana-go/programs/address-lookup-table"
+ "github.com/gagliardetto/solana-go/programs/system"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+)
+
+func TestSolanaLookupTables(t *testing.T) {
+ t.Parallel()
+
+ ctx := tests.Context(t)
+ url := utils.SetupLocalSolNode(t)
+ c := rpc.New(url)
+
+ sender, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+ utils.FundAccounts(ctx, []solana.PrivateKey{sender}, c, t)
+
+ // transfer instructions
+ pubkeys := solana.PublicKeySlice{}
+ instructions := []solana.Instruction{}
+ for i := 0; i < 32; i++ {
+ k, kerr := solana.NewRandomPrivateKey()
+ require.NoError(t, kerr)
+ pubkeys = append(pubkeys, k.PublicKey())
+ instructions = append(instructions, system.NewTransferInstruction(1_000_000_000, sender.PublicKey(), k.PublicKey()).Build())
+ }
+
+ loadLookupTable := func(k []solana.PublicKey) solana.PublicKey {
+ // create lookup table
+ slot, serr := c.GetSlot(ctx, rpc.CommitmentFinalized)
+ require.NoError(t, serr)
+ table, instruction, ierr := utils.NewCreateLookupTableInstruction(
+ sender.PublicKey(),
+ sender.PublicKey(),
+ slot,
+ )
+ require.NoError(t, ierr)
+ utils.SendAndConfirm(ctx, t, c, []solana.Instruction{instruction}, sender, rpc.CommitmentConfirmed)
+
+ // add entries to lookup table
+ utils.SendAndConfirm(ctx, t, c, []solana.Instruction{
+ utils.NewExtendLookupTableInstruction(
+ table, sender.PublicKey(), sender.PublicKey(),
+ k,
+ ),
+ }, sender, rpc.CommitmentConfirmed)
+
+ return table
+ }
+
+ // use two separate tables + leave additional addresses that are not in tables
+ table0 := loadLookupTable(pubkeys[0:15])
+ table1 := loadLookupTable(append(pubkeys[15:30], solana.SystemProgramID)) // the program that is called is not used from a lookup table
+
+ // fetch lookup table
+ t0data, err := addresslookuptable.GetAddressLookupTableStateWithOpts(ctx, c, table0, &rpc.GetAccountInfoOpts{
+ Commitment: rpc.CommitmentConfirmed,
+ })
+ require.NoError(t, err)
+ t1data, err := addresslookuptable.GetAddressLookupTableStateWithOpts(ctx, c, table1, &rpc.GetAccountInfoOpts{
+ Commitment: rpc.CommitmentConfirmed,
+ })
+ require.NoError(t, err)
+
+ // tx should fail without lookup tables (too large)
+ blockhash, err := c.GetLatestBlockhash(ctx, rpc.CommitmentFinalized)
+ require.NoError(t, err)
+ tx, err := solana.NewTransaction(
+ instructions,
+ blockhash.Value.Blockhash,
+ solana.TransactionPayer(sender.PublicKey()),
+ )
+ require.NoError(t, err)
+ _, err = tx.Sign(func(_ solana.PublicKey) *solana.PrivateKey {
+ return &sender
+ })
+ require.NoError(t, err)
+ _, err = c.SendTransactionWithOpts(ctx, tx, rpc.TransactionOpts{
+ PreflightCommitment: rpc.CommitmentProcessed,
+ })
+ require.ErrorContains(t, err, "VersionedTransaction too large: 2312 bytes")
+
+ // tx should not fail with lookup tables (874 bytes) - takes a few moments for the tables to load on state?
+ err = fmt.Errorf("waiting for successful run")
+ count := 0
+ for err != nil && count < 5 {
+ count++
+ time.Sleep(time.Second)
+ blockhash, err = c.GetLatestBlockhash(ctx, rpc.CommitmentFinalized)
+ require.NoError(t, err)
+ tx, err = solana.NewTransaction(
+ instructions,
+ blockhash.Value.Blockhash,
+ solana.TransactionAddressTables(map[solana.PublicKey]solana.PublicKeySlice{
+ table0: t0data.Addresses,
+ table1: t1data.Addresses,
+ }),
+ solana.TransactionPayer(sender.PublicKey()),
+ )
+ require.NoError(t, err)
+ _, err = tx.Sign(func(_ solana.PublicKey) *solana.PrivateKey {
+ return &sender
+ })
+ require.NoError(t, err)
+ if _, err = c.SendTransactionWithOpts(ctx, tx, rpc.TransactionOpts{
+ PreflightCommitment: rpc.CommitmentProcessed,
+ }); err != nil {
+ t.Log("failed: retrying in 1s")
+ }
+ }
+ require.NoError(t, err)
+}
diff --git a/chains/solana/contracts/tests/mcms/mcm.go b/chains/solana/contracts/tests/mcms/mcm.go
new file mode 100644
index 000000000..87729e18f
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/mcm.go
@@ -0,0 +1,265 @@
+package contracts
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+
+ "github.com/gagliardetto/solana-go"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/mcm"
+)
+
+// mcm signer dataless pda
+func McmSignerAddress(msigName [32]byte) solana.PublicKey {
+ pda, _, _ := solana.FindProgramAddress([][]byte{
+ []byte("multisig_signer"),
+ msigName[:],
+ }, config.McmProgram)
+ return pda
+}
+
+func McmConfigAddress(msigName [32]byte) solana.PublicKey {
+ pda, _, _ := solana.FindProgramAddress([][]byte{
+ []byte("multisig_config"),
+ msigName[:],
+ }, config.McmProgram)
+ return pda
+}
+
+func McmConfigSignersAddress(msigName [32]byte) solana.PublicKey {
+ pda, _, _ := solana.FindProgramAddress([][]byte{
+ []byte("multisig_config_signers"),
+ msigName[:],
+ }, config.McmProgram)
+ return pda
+}
+
+func RootMetadataAddress(msigName [32]byte) solana.PublicKey {
+ pda, _, _ := solana.FindProgramAddress([][]byte{
+ []byte("root_metadata"),
+ msigName[:],
+ }, config.McmProgram)
+ return pda
+}
+
+func ExpiringRootAndOpCountAddress(msigName [32]byte) solana.PublicKey {
+ pda, _, _ := solana.FindProgramAddress([][]byte{
+ []byte("expiring_root_and_op_count"),
+ msigName[:],
+ }, config.McmProgram)
+ return pda
+}
+
+// get address of the root_signatures pda
+func RootSignaturesAddress(msigName [32]byte, root [32]byte, validUntil uint32) solana.PublicKey {
+ validUntilBytes := make([]byte, 4)
+ binary.LittleEndian.PutUint32(validUntilBytes, validUntil)
+
+ pda, _, _ := solana.FindProgramAddress([][]byte{
+ []byte("root_signatures"),
+ msigName[:],
+ root[:],
+ validUntilBytes,
+ }, config.McmProgram)
+ return pda
+}
+
+// get address of the seen_signed_hashes pda
+func SeenSignedHashesAddress(msigName [32]byte, root [32]byte, validUntil uint32) solana.PublicKey {
+ validUntilBytes := make([]byte, 4)
+ binary.LittleEndian.PutUint32(validUntilBytes, validUntil)
+ pda, _, _ := solana.FindProgramAddress([][]byte{
+ []byte("seen_signed_hashes"),
+ msigName[:],
+ root[:],
+ validUntilBytes,
+ }, config.McmProgram)
+ return pda
+}
+
+func NewMcmMultisig(name [32]byte) mcmsUtils.Multisig {
+ return mcmsUtils.Multisig{
+ PaddedName: name,
+ SignerPDA: McmSignerAddress(name),
+ ConfigPDA: McmConfigAddress(name),
+ RootMetadataPDA: RootMetadataAddress(name),
+ ExpiringRootAndOpCountPDA: ExpiringRootAndOpCountAddress(name),
+ ConfigSignersPDA: McmConfigSignersAddress(name),
+ RootSignaturesPDA: func(root [32]byte, validUntil uint32) solana.PublicKey {
+ return RootSignaturesAddress(name, root, validUntil)
+ },
+ SeenSignedHashesPDA: func(root [32]byte, validUntil uint32) solana.PublicKey {
+ return SeenSignedHashesAddress(name, root, validUntil)
+ },
+ }
+}
+
+// get chunked append instructions to preload signers to pda, required before set_config
+func AppendSignersIxs(signerAddresses [][20]uint8, msigName [32]byte, multisigCfgPDA solana.PublicKey, cfgSignersPDA solana.PublicKey, authority solana.PublicKey, chunkSize int) ([]solana.Instruction, error) {
+ if chunkSize > config.MaxAppendSignerBatchSize {
+ return nil, errors.New("chunkSize exceeds max signers chunk size")
+ }
+ ixs := make([]solana.Instruction, 0)
+ for i := 0; i < len(signerAddresses); i += chunkSize {
+ end := i + chunkSize
+ if end > len(signerAddresses) {
+ end = len(signerAddresses)
+ }
+ appendIx, appendErr := mcm.NewAppendSignersInstruction(
+ msigName,
+ signerAddresses[i:end],
+ multisigCfgPDA,
+ cfgSignersPDA,
+ authority,
+ ).ValidateAndBuild()
+ if appendErr != nil {
+ return nil, appendErr
+ }
+ ixs = append(ixs, appendIx)
+ }
+ return ixs, nil
+}
+
+// get chunked append instructions to preload signatures to pda, required before set_root
+func AppendSignaturesIxs(signatures []mcm.Signature, msigName [32]byte, root [32]uint8, validUntil uint32, signaturesPDA solana.PublicKey, authority solana.PublicKey, chunkSize int) ([]solana.Instruction, error) {
+ if chunkSize > config.MaxAppendSignatureBatchSize {
+ return nil, errors.New("chunkSize exceeds max signatures chunk size")
+ }
+ ixs := make([]solana.Instruction, 0)
+ for i := 0; i < len(signatures); i += chunkSize {
+ end := i + chunkSize
+ if end > len(signatures) {
+ end = len(signatures)
+ }
+ appendIx, appendErr := mcm.NewAppendSignaturesInstruction(
+ msigName,
+ root,
+ validUntil,
+ signatures[i:end],
+ signaturesPDA,
+ authority,
+ ).ValidateAndBuild()
+ if appendErr != nil {
+ return nil, appendErr
+ }
+ ixs = append(ixs, appendIx)
+ }
+ return ixs, nil
+}
+
+type McmRootInput struct {
+ Multisig solana.PublicKey
+ Operations []mcmsUtils.McmOpNode
+ PreOpCount uint64
+ PostOpCount uint64
+ ValidUntil uint32
+ OverridePreviousRoot bool
+}
+
+type McmRootData struct {
+ EthMsgHash []byte
+ Root [32]byte
+ Metadata mcm.RootMetadataInput
+ MetadataProof [][32]uint8
+}
+
+func CreateMcmRootData(input McmRootInput) (McmRootData, error) {
+ numOps := len(input.Operations)
+
+ // add 1 for the root metadata node
+ nodes := make([]mcmsUtils.MerkleNode, numOps+1)
+ for i := range input.Operations {
+ nodes[i] = &input.Operations[i]
+ }
+
+ rootMetadata := mcmsUtils.RootMetadataNode{
+ Multisig: input.Multisig,
+ PreOpCount: input.PreOpCount,
+ PostOpCount: input.PostOpCount,
+ OverridePreviousRoot: input.OverridePreviousRoot,
+ }
+ nodes[numOps] = &rootMetadata
+
+ // construct the tree
+ tree, err := mcmsUtils.NewOpMerkleTree(nodes)
+ if err != nil {
+ return McmRootData{}, fmt.Errorf("failed to create tree: %w", err)
+ }
+
+ metadata := mcm.RootMetadataInput{
+ ChainId: config.TestChainID,
+ Multisig: rootMetadata.Multisig,
+ PreOpCount: rootMetadata.PreOpCount,
+ PostOpCount: rootMetadata.PostOpCount,
+ OverridePreviousRoot: rootMetadata.OverridePreviousRoot,
+ }
+
+ // convert root to 32 byte array
+ root := tree.Hash()
+
+ metadataProof, err := rootMetadata.Proofs()
+ if err != nil {
+ return McmRootData{}, fmt.Errorf("failed to get metadata proof: %w", err)
+ }
+
+ opTree, ok := tree.(*mcmsUtils.OpMerkleTree)
+ if !ok {
+ return McmRootData{}, fmt.Errorf("tree is not of type *OpMerkleTree")
+ }
+ ethMsgHash := opTree.EthMsgHash(input.ValidUntil)
+
+ return McmRootData{
+ Root: root,
+ EthMsgHash: ethMsgHash,
+ Metadata: metadata,
+ MetadataProof: metadataProof,
+ }, nil
+}
+
+func BulkSignOnMsgHash(signers []eth.Signer, ethMsgHash []byte) ([]mcm.Signature, error) {
+ signatures := make([]mcm.Signature, len(signers))
+ for i, signer := range signers {
+ signature, err := signer.Sign(ethMsgHash)
+ if err != nil {
+ return nil, err
+ }
+ signatures[i] = signature
+ }
+ return signatures, nil
+}
+
+func IxToMcmTestOpNode(multisig solana.PublicKey, msigSigner solana.PublicKey, ix solana.Instruction, nonce uint64) (mcmsUtils.McmOpNode, error) {
+ ixData, err := ix.Data()
+ if err != nil {
+ return mcmsUtils.McmOpNode{}, err
+ }
+ // Create the accounts slice with the correct size
+ accounts := make([]*solana.AccountMeta, 0, len(ix.Accounts()))
+
+ for _, acc := range ix.Accounts() {
+ accCopy := *acc
+ // NOTE: this bypasses utils.sendTransaction signing part since it's PDA we don't have private key
+ if accCopy.PublicKey == msigSigner {
+ accCopy.IsSigner = false
+ }
+ accounts = append(accounts, &solana.AccountMeta{
+ PublicKey: accCopy.PublicKey,
+ IsSigner: accCopy.IsSigner,
+ IsWritable: accCopy.IsWritable,
+ })
+ }
+
+ node := mcmsUtils.McmOpNode{
+ Multisig: multisig,
+ Nonce: nonce,
+ To: ix.ProgramID(),
+ Data: ixData,
+ RemainingAccounts: accounts,
+ }
+
+ return node, nil
+}
diff --git a/chains/solana/contracts/tests/mcms/mcm_errors.go b/chains/solana/contracts/tests/mcms/mcm_errors.go
new file mode 100644
index 000000000..d0211033e
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/mcm_errors.go
@@ -0,0 +1,99 @@
+package contracts
+
+import (
+ agbinary "github.com/gagliardetto/binary"
+)
+
+// This Errors should be automatically generated by Anchor-Go but they only support one error per program
+type McmError agbinary.BorshEnum
+
+const (
+ WrongMultiSigMcmError McmError = iota
+ WrongChainIDMcmError
+ UnauthorizedMcmError
+ InvalidInputsMcmError
+ OverflowMcmError
+ InvalidSignatureMcmError
+ FailedEcdsaRecoverMcmError
+ InvalidRootLenMcmError
+ MismatchedInputSignerVectorsLengthMcmError
+ OutOfBoundsNumOfSignersMcmError
+ MismatchedInputGroupArraysLengthMcmError
+ GroupTreeNotWellFormedMcmError
+ SignerInDisabledGroupMcmError
+ OutOfBoundsGroupQuorumMcmError
+ SignersAddressesMustBeStrictlyIncreasingMcmError
+ SignedHashAlreadySeenMcmError
+ InvalidSignerMcmError
+ MissingConfigMcmError
+ InsufficientSignersMcmError
+ ValidUntilHasAlreadyPassedMcmError
+ ProofCannotBeVerifiedMcmError
+ PendingOpsMcmError
+ WrongPreOpCountMcmError
+ WrongPostOpCountMcmError
+ PostOpCountReachedMcmError
+ RootExpiredMcmError
+ WrongNonceMcmError
+)
+
+func (value McmError) String() string {
+ switch value {
+ case WrongMultiSigMcmError:
+ return "WrongMultiSig"
+ case WrongChainIDMcmError:
+ return "WrongChainID"
+ case UnauthorizedMcmError:
+ return "Unauthorized"
+ case InvalidInputsMcmError:
+ return "InvalidInputs"
+ case OverflowMcmError:
+ return "Overflow"
+ case InvalidSignatureMcmError:
+ return "InvalidSignature"
+ case FailedEcdsaRecoverMcmError:
+ return "FailedEcdsaRecover"
+ case InvalidRootLenMcmError:
+ return "InvalidRootLen"
+ case MismatchedInputSignerVectorsLengthMcmError:
+ return "MismatchedInputSignerVectorsLength"
+ case OutOfBoundsNumOfSignersMcmError:
+ return "OutOfBoundsNumOfSigners"
+ case MismatchedInputGroupArraysLengthMcmError:
+ return "MismatchedInputGroupArraysLength"
+ case GroupTreeNotWellFormedMcmError:
+ return "GroupTreeNotWellFormed"
+ case SignerInDisabledGroupMcmError:
+ return "SignerInDisabledGroup"
+ case OutOfBoundsGroupQuorumMcmError:
+ return "OutOfBoundsGroupQuorum"
+ case SignersAddressesMustBeStrictlyIncreasingMcmError:
+ return "SignersAddressesMustBeStrictlyIncreasing"
+ case SignedHashAlreadySeenMcmError:
+ return "SignedHashAlreadySeen"
+ case InvalidSignerMcmError:
+ return "InvalidSigner"
+ case MissingConfigMcmError:
+ return "MissingConfig"
+ case InsufficientSignersMcmError:
+ return "InsufficientSigners"
+ case ValidUntilHasAlreadyPassedMcmError:
+ return "ValidUntilHasAlreadyPassed"
+ case ProofCannotBeVerifiedMcmError:
+ return "ProofCannotBeVerified"
+ case PendingOpsMcmError:
+ return "PendingOps"
+ case WrongPreOpCountMcmError:
+ return "WrongPreOpCount"
+ case WrongPostOpCountMcmError:
+ return "WrongPostOpCount"
+ case PostOpCountReachedMcmError:
+ return "PostOpCountReached"
+ case RootExpiredMcmError:
+ return "RootExpired"
+ case WrongNonceMcmError:
+ return "WrongNonce"
+ default:
+ return ""
+ }
+}
diff --git a/chains/solana/contracts/tests/mcms/mcm_multiple_multisigs_test.go b/chains/solana/contracts/tests/mcms/mcm_multiple_multisigs_test.go
new file mode 100644
index 000000000..b0555304e
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/mcm_multiple_multisigs_test.go
@@ -0,0 +1,369 @@
+package contracts
+
+import (
+ "reflect"
+ "testing"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/mcm"
+)
+
+func TestMcmMultipleMultisigs(t *testing.T) {
+ t.Parallel()
+ mcm.SetProgramID(config.McmProgram)
+
+ ctx := tests.Context(t)
+
+ admin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ solanaGoClient := utils.DeployAllPrograms(t, utils.PathToAnchorConfig, admin)
+
+ // mcm multisig 1
+ TestMsigName1, err := mcmsUtils.PadString32("test_mcm_instance_1")
+ require.NoError(t, err)
+ MultisigConfigPDA1 := McmConfigAddress(TestMsigName1)
+ RootMetadataPDA1 := RootMetadataAddress(TestMsigName1)
+ ExpiringRootAndOpCountPDA1 := ExpiringRootAndOpCountAddress(TestMsigName1)
+ ConfigSignersPDA1 := McmConfigSignersAddress(TestMsigName1)
+
+ // mcm multisig 2
+ TestMsigName2, err := mcmsUtils.PadString32("test_mcm_instance_2")
+ require.NoError(t, err)
+ MultisigConfigPDA2 := McmConfigAddress(TestMsigName2)
+ RootMetadataPDA2 := RootMetadataAddress(TestMsigName2)
+ ExpiringRootAndOpCountPDA2 := ExpiringRootAndOpCountAddress(TestMsigName2)
+ ConfigSignersPDA2 := McmConfigSignersAddress(TestMsigName2)
+
+ t.Run("setup:funding", func(t *testing.T) {
+ utils.FundAccounts(ctx, []solana.PrivateKey{admin}, solanaGoClient, t)
+ })
+
+ t.Run("setup:test_mcm_instance_1", func(t *testing.T) {
+ t.Run("setup:test_mcm_instance_1 init", func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.McmProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ ix, err := mcm.NewInitializeInstruction(
+ config.TestChainID,
+ TestMsigName1,
+ MultisigConfigPDA1,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.McmProgram,
+ programData.Address,
+ RootMetadataPDA1,
+ ExpiringRootAndOpCountPDA1,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA1, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, admin.PublicKey(), configAccount.Owner)
+ })
+
+ t.Run("mcm:set_config", func(t *testing.T) {
+ numSigners := config.MaxNumSigners
+ signerPrivateKeys, err := eth.GenerateEthPrivateKeys(numSigners)
+ require.NoError(t, err)
+
+ signerGroups := make([]byte, numSigners)
+ for i := 0; i < len(signerGroups); i++ {
+ signerGroups[i] = byte(i % 10)
+ }
+
+ // just use simple config for now
+ groupQuorums := []uint8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
+ groupParents := []uint8{0, 0, 0, 2, 0, 0, 0, 0, 0, 0}
+
+ mcmConfig, err := mcmsUtils.NewValidMcmConfig(
+ TestMsigName1,
+ signerPrivateKeys,
+ signerGroups,
+ groupQuorums,
+ groupParents,
+ config.ClearRoot,
+ )
+ require.NoError(t, err)
+
+ signerAddresses := mcmConfig.SignerAddresses
+
+ t.Run("mcm:set_config: preload signers on PDA", func(t *testing.T) {
+ ixs := make([]solana.Instruction, 0)
+
+ parsedTotalSigners, err := mcmsUtils.SafeToUint8(len(signerAddresses))
+ require.NoError(t, err)
+
+ initSignersIx, err := mcm.NewInitSignersInstruction(
+ TestMsigName1,
+ parsedTotalSigners,
+ MultisigConfigPDA1,
+ ConfigSignersPDA1,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+ ixs = append(ixs, initSignersIx)
+
+ appendSignersIxs, err := AppendSignersIxs(signerAddresses, TestMsigName1, MultisigConfigPDA1, ConfigSignersPDA1, admin.PublicKey(), config.MaxAppendSignerBatchSize)
+ require.NoError(t, err)
+ ixs = append(ixs, appendSignersIxs...)
+
+ finalizeSignersIx, err := mcm.NewFinalizeSignersInstruction(
+ TestMsigName1,
+ MultisigConfigPDA1,
+ ConfigSignersPDA1,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ ixs = append(ixs, finalizeSignersIx)
+
+ for _, ix := range ixs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var cfgSignersAccount mcm.ConfigSigners
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, ConfigSignersPDA1, config.DefaultCommitment, &cfgSignersAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, true, cfgSignersAccount.IsFinalized)
+
+ // check if the addresses are registered correctly
+ for i, signer := range cfgSignersAccount.SignerAddresses {
+ require.Equal(t, signerAddresses[i], signer)
+ }
+ })
+
+ t.Run("success:set_config", func(t *testing.T) {
+ ix, err := mcm.NewSetConfigInstruction(
+ TestMsigName1,
+ mcmConfig.SignerGroups,
+ mcmConfig.GroupQuorums,
+ mcmConfig.GroupParents,
+ mcmConfig.ClearRoot,
+ MultisigConfigPDA1,
+ ConfigSignersPDA1,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA1, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupParents, mcmConfig.GroupParents), true)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupQuorums, mcmConfig.GroupQuorums), true)
+
+ // check if the McmSigner struct is correct
+ for i, signer := range configAccount.Signers {
+ require.Equal(t, signer.EvmAddress, mcmConfig.SignerAddresses[i])
+ require.Equal(t, signer.Index, uint8(i))
+ require.Equal(t, signer.Group, (mcmConfig.SignerGroups)[i])
+ }
+
+ // pda closed after set_config
+ utils.AssertClosedAccount(ctx, t, solanaGoClient, ConfigSignersPDA1, config.DefaultCommitment)
+ })
+ })
+ })
+ t.Run("setup:test_mcm_instance_2", func(t *testing.T) {
+ t.Run("setup:test_mcm_instance_2 init", func(t *testing.T) {
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.McmProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ ix, err := mcm.NewInitializeInstruction(
+ config.TestChainID,
+ TestMsigName2,
+ MultisigConfigPDA2,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.McmProgram,
+ programData.Address,
+ RootMetadataPDA2,
+ ExpiringRootAndOpCountPDA2,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA2, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, admin.PublicKey(), configAccount.Owner)
+ })
+
+ t.Run("mcm:set_config", func(t *testing.T) {
+ numSigners := config.MaxNumSigners
+ signerPrivateKeys, err := eth.GenerateEthPrivateKeys(numSigners)
+ require.NoError(t, err)
+
+ signerGroups := make([]byte, numSigners)
+ for i := 0; i < len(signerGroups); i++ {
+ signerGroups[i] = byte(i % 10)
+ }
+
+ groupQuorums := []uint8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
+ groupParents := []uint8{0, 0, 0, 2, 0, 0, 0, 0, 0, 0}
+
+ mcmConfig, err := mcmsUtils.NewValidMcmConfig(
+ TestMsigName2,
+ signerPrivateKeys,
+ signerGroups,
+ groupQuorums,
+ groupParents,
+ config.ClearRoot,
+ )
+ require.NoError(t, err)
+
+ signerAddresses := mcmConfig.SignerAddresses
+
+ t.Run("mcm:set_config: preload signers on PDA", func(t *testing.T) {
+ ixs := make([]solana.Instruction, 0)
+
+ parsedTotalSigners, err := mcmsUtils.SafeToUint8(len(signerAddresses))
+ require.NoError(t, err)
+
+ initSignersIx, err := mcm.NewInitSignersInstruction(
+ TestMsigName2,
+ parsedTotalSigners,
+ MultisigConfigPDA2,
+ ConfigSignersPDA2,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+ ixs = append(ixs, initSignersIx)
+
+ appendSignersIxs, err := AppendSignersIxs(signerAddresses, TestMsigName2, MultisigConfigPDA2, ConfigSignersPDA2, admin.PublicKey(), config.MaxAppendSignerBatchSize)
+ require.NoError(t, err)
+ ixs = append(ixs, appendSignersIxs...)
+
+ finalizeSignersIx, err := mcm.NewFinalizeSignersInstruction(
+ TestMsigName2,
+ MultisigConfigPDA2,
+ ConfigSignersPDA2,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ ixs = append(ixs, finalizeSignersIx)
+
+ for _, ix := range ixs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var cfgSignersAccount mcm.ConfigSigners
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, ConfigSignersPDA2, config.DefaultCommitment, &cfgSignersAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, true, cfgSignersAccount.IsFinalized)
+
+ // check if the addresses are registered correctly
+ for i, signer := range cfgSignersAccount.SignerAddresses {
+ require.Equal(t, signerAddresses[i], signer)
+ }
+ })
+
+ t.Run("fail:set_config with invalid seeds", func(t *testing.T) {
+ ix, err := mcm.NewSetConfigInstruction(
+ TestMsigName1,
+ mcmConfig.SignerGroups,
+ mcmConfig.GroupQuorums,
+ mcmConfig.GroupParents,
+ mcmConfig.ClearRoot,
+ MultisigConfigPDA2,
+ ConfigSignersPDA2,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment, []string{"Error Code: " + "ConstraintSeeds"})
+ require.NotNil(t, result)
+ })
+
+ t.Run("success:set_config", func(t *testing.T) {
+ ix, err := mcm.NewSetConfigInstruction(
+ TestMsigName2,
+ mcmConfig.SignerGroups,
+ mcmConfig.GroupQuorums,
+ mcmConfig.GroupParents,
+ mcmConfig.ClearRoot,
+ MultisigConfigPDA2,
+ ConfigSignersPDA2,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA2, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupParents, mcmConfig.GroupParents), true)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupQuorums, mcmConfig.GroupQuorums), true)
+
+ // check if the McmSigner struct is correct
+ for i, signer := range configAccount.Signers {
+ require.Equal(t, signer.EvmAddress, mcmConfig.SignerAddresses[i])
+ require.Equal(t, signer.Index, uint8(i))
+ require.Equal(t, signer.Group, (mcmConfig.SignerGroups)[i])
+ }
+
+ // pda closed after set_config
+ utils.AssertClosedAccount(ctx, t, solanaGoClient, ConfigSignersPDA2, config.DefaultCommitment)
+ })
+ })
+ })
+}
diff --git a/chains/solana/contracts/tests/mcms/mcm_set_config_test.go b/chains/solana/contracts/tests/mcms/mcm_set_config_test.go
new file mode 100644
index 000000000..d3706e578
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/mcm_set_config_test.go
@@ -0,0 +1,444 @@
+package contracts
+
+import (
+ "reflect"
+ "testing"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/mcm"
+)
+
+func TestMcmSetConfig(t *testing.T) {
+ t.Parallel()
+ mcm.SetProgramID(config.McmProgram)
+
+ ctx := tests.Context(t)
+
+ admin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ anotherAdmin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ user, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ solanaGoClient := utils.DeployAllPrograms(t, utils.PathToAnchorConfig, admin)
+
+ // mcm name
+ TestMsigName := config.TestMsigNamePaddedBuffer
+
+ // test mcm pdas
+ MultisigConfigPDA := McmConfigAddress(TestMsigName)
+ RootMetadataPDA := RootMetadataAddress(TestMsigName)
+ ExpiringRootAndOpCountPDA := ExpiringRootAndOpCountAddress(TestMsigName)
+ ConfigSignersPDA := McmConfigSignersAddress(TestMsigName)
+
+ t.Run("setup:funding", func(t *testing.T) {
+ utils.FundAccounts(ctx, []solana.PrivateKey{admin, anotherAdmin, user}, solanaGoClient, t)
+ })
+
+ t.Run("setup:mcm", func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.McmProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ ix, err := mcm.NewInitializeInstruction(
+ config.TestChainID,
+ TestMsigName,
+ MultisigConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.McmProgram,
+ programData.Address,
+ RootMetadataPDA,
+ ExpiringRootAndOpCountPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, admin.PublicKey(), configAccount.Owner)
+ })
+
+ t.Run("mcm:ownership", func(t *testing.T) {
+ // Fail to transfer ownership when not owner
+ instruction, err := mcm.NewTransferOwnershipInstruction(
+ TestMsigName,
+ anotherAdmin.PublicKey(),
+ MultisigConfigPDA,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + UnauthorizedMcmError.String()})
+ require.NotNil(t, result)
+
+ // successfully transfer ownership
+ instruction, err = mcm.NewTransferOwnershipInstruction(
+ TestMsigName,
+ anotherAdmin.PublicKey(),
+ MultisigConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // Fail to accept ownership when not proposed_owner
+ instruction, err = mcm.NewAcceptOwnershipInstruction(
+ TestMsigName,
+ MultisigConfigPDA,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + UnauthorizedMcmError.String()})
+ require.NotNil(t, result)
+
+ // Successfully accept ownership
+ // anotherAdmin becomes owner for remaining tests
+ instruction, err = mcm.NewAcceptOwnershipInstruction(
+ TestMsigName,
+ MultisigConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // Current owner cannot propose self
+ instruction, err = mcm.NewTransferOwnershipInstruction(
+ TestMsigName,
+ anotherAdmin.PublicKey(),
+ MultisigConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + InvalidInputsMcmError.String()})
+ require.NotNil(t, result)
+
+ // Validate proposed set to 0-address after accepting ownership
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ require.Equal(t, anotherAdmin.PublicKey(), configAccount.Owner)
+ require.Equal(t, solana.PublicKey{}, configAccount.ProposedOwner)
+
+ // get it back
+ instruction, err = mcm.NewTransferOwnershipInstruction(
+ TestMsigName,
+ admin.PublicKey(),
+ MultisigConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ instruction, err = mcm.NewAcceptOwnershipInstruction(
+ TestMsigName,
+ MultisigConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t, admin.PublicKey(), configAccount.Owner)
+ require.Equal(t, solana.PublicKey{}, configAccount.ProposedOwner)
+ })
+
+ t.Run("mcm:set_config", func(t *testing.T) {
+ numSigners := config.MaxNumSigners
+ signerPrivateKeys, err := eth.GenerateEthPrivateKeys(numSigners)
+ require.NoError(t, err)
+
+ signerGroups := make([]byte, numSigners)
+ for i := 0; i < len(signerGroups); i++ {
+ signerGroups[i] = byte(i % 10)
+ }
+
+ // just use simple config for now
+ groupQuorums := []uint8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
+ groupParents := []uint8{0, 0, 0, 2, 0, 0, 0, 0, 0, 0}
+
+ mcmConfig, err := mcmsUtils.NewValidMcmConfig(
+ TestMsigName,
+ signerPrivateKeys,
+ signerGroups,
+ groupQuorums,
+ groupParents,
+ config.ClearRoot,
+ )
+ require.NoError(t, err)
+
+ signerAddresses := mcmConfig.SignerAddresses
+
+ t.Run("mcm:set_config: preload signers on PDA", func(t *testing.T) {
+ ixs := make([]solana.Instruction, 0)
+ parsedTotalSigners, err := mcmsUtils.SafeToUint8(len(signerAddresses))
+ require.NoError(t, err)
+
+ initSignersIx, err := mcm.NewInitSignersInstruction(
+ TestMsigName,
+ parsedTotalSigners,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+ ixs = append(ixs, initSignersIx)
+
+ appendSignersIxs, err := AppendSignersIxs(signerAddresses, TestMsigName, MultisigConfigPDA, ConfigSignersPDA, admin.PublicKey(), config.MaxAppendSignerBatchSize)
+ require.NoError(t, err)
+ ixs = append(ixs, appendSignersIxs...)
+
+ finalizeSignersIx, err := mcm.NewFinalizeSignersInstruction(
+ TestMsigName,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ ixs = append(ixs, finalizeSignersIx)
+
+ for _, ix := range ixs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var cfgSignersAccount mcm.ConfigSigners
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, ConfigSignersPDA, config.DefaultCommitment, &cfgSignersAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, true, cfgSignersAccount.IsFinalized)
+
+ // check if the addresses are registered correctly
+ for i, signer := range cfgSignersAccount.SignerAddresses {
+ require.Equal(t, signerAddresses[i], signer)
+ }
+ })
+
+ t.Run("mcm:set_config:admin authorization", func(t *testing.T) {
+ t.Run("fail:set_config from unauthorized user", func(t *testing.T) {
+ ix, err := mcm.NewSetConfigInstruction(
+ mcmConfig.MultisigName,
+ mcmConfig.SignerGroups,
+ mcmConfig.GroupQuorums,
+ mcmConfig.GroupParents,
+ mcmConfig.ClearRoot,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ user.PublicKey(), // unauthorized user
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, user, config.DefaultCommitment, []string{"Error Code: " + UnauthorizedMcmError.String()})
+ require.NotNil(t, result)
+ })
+
+ t.Run("success:set_config from admin", func(t *testing.T) {
+ // set config
+ ix, err := mcm.NewSetConfigInstruction(
+ mcmConfig.MultisigName,
+ mcmConfig.SignerGroups,
+ mcmConfig.GroupQuorums,
+ mcmConfig.GroupParents,
+ mcmConfig.ClearRoot,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupParents, mcmConfig.GroupParents), true)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupQuorums, mcmConfig.GroupQuorums), true)
+
+ // check if the McmSigner struct is correct
+ for i, signer := range configAccount.Signers {
+ require.Equal(t, signer.EvmAddress, mcmConfig.SignerAddresses[i])
+ require.Equal(t, signer.Index, uint8(i))
+ require.Equal(t, signer.Group, (mcmConfig.SignerGroups)[i])
+ }
+
+ // pda closed after set_config
+ utils.AssertClosedAccount(ctx, t, solanaGoClient, ConfigSignersPDA, config.DefaultCommitment)
+ })
+ })
+ })
+
+ t.Run("mcm:set_config with reinitializing signers pda(closed)", func(t *testing.T) {
+ numSigners := config.MaxNumSigners
+ signerPrivateKeys, err := eth.GenerateEthPrivateKeys(numSigners)
+ require.NoError(t, err)
+
+ signerGroups := make([]byte, numSigners)
+ for i := 0; i < len(signerGroups); i++ {
+ signerGroups[i] = byte(i % 10)
+ }
+
+ // just use simple config for now
+ groupQuorums := []uint8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
+ groupParents := []uint8{0, 0, 0, 2, 0, 0, 0, 0, 0, 0}
+
+ mcmConfig, err := mcmsUtils.NewValidMcmConfig(
+ TestMsigName,
+ signerPrivateKeys,
+ signerGroups,
+ groupQuorums,
+ groupParents,
+ config.ClearRoot,
+ )
+ require.NoError(t, err)
+
+ signerAddresses := mcmConfig.SignerAddresses
+
+ t.Run("mcm:set_config: preload signers on PDA", func(t *testing.T) {
+ // ConfigSignersPDA should be closed before reinitializing
+ utils.AssertClosedAccount(ctx, t, solanaGoClient, ConfigSignersPDA, config.DefaultCommitment)
+
+ parsedTotalSigners, err := mcmsUtils.SafeToUint8(len(signerAddresses))
+ require.NoError(t, err)
+
+ initSignersIx, err := mcm.NewInitSignersInstruction(
+ TestMsigName,
+ parsedTotalSigners,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initSignersIx}, admin, config.DefaultCommitment)
+
+ appendSignersIxs, err := AppendSignersIxs(signerAddresses, TestMsigName, MultisigConfigPDA, ConfigSignersPDA, admin.PublicKey(), config.MaxAppendSignerBatchSize)
+ require.NoError(t, err)
+
+ // partially register signers
+ for _, ix := range appendSignersIxs[:3] {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ // clear signers
+ clearIx, err := mcm.NewClearSignersInstruction(
+ TestMsigName,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{clearIx}, admin, config.DefaultCommitment)
+
+ // register all signers
+ for _, ix := range appendSignersIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ // finalize registration
+ finalizeSignersIx, err := mcm.NewFinalizeSignersInstruction(
+ TestMsigName,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{finalizeSignersIx}, admin, config.DefaultCommitment)
+
+ var cfgSignersAccount mcm.ConfigSigners
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, ConfigSignersPDA, config.DefaultCommitment, &cfgSignersAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, true, cfgSignersAccount.IsFinalized)
+
+ // check if the addresses are registered correctly
+ for i, signer := range cfgSignersAccount.SignerAddresses {
+ require.Equal(t, signerAddresses[i], signer)
+ }
+ })
+
+ t.Run("success:set_config", func(t *testing.T) {
+ ix, err := mcm.NewSetConfigInstruction(
+ TestMsigName,
+ mcmConfig.SignerGroups,
+ mcmConfig.GroupQuorums,
+ mcmConfig.GroupParents,
+ mcmConfig.ClearRoot,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupParents, mcmConfig.GroupParents), true)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupQuorums, mcmConfig.GroupQuorums), true)
+
+ // check if the McmSigner struct is correct
+ for i, signer := range configAccount.Signers {
+ require.Equal(t, signer.EvmAddress, mcmConfig.SignerAddresses[i])
+ require.Equal(t, signer.Index, uint8(i))
+ require.Equal(t, signer.Group, (mcmConfig.SignerGroups)[i])
+ }
+
+ // pda closed after set_config
+ utils.AssertClosedAccount(ctx, t, solanaGoClient, ConfigSignersPDA, config.DefaultCommitment)
+ })
+ })
+ // todo: negative tests
+}
diff --git a/chains/solana/contracts/tests/mcms/mcm_set_root_execute_test.go b/chains/solana/contracts/tests/mcms/mcm_set_root_execute_test.go
new file mode 100644
index 000000000..aa0839622
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/mcm_set_root_execute_test.go
@@ -0,0 +1,542 @@
+package contracts
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ computebudget "github.com/gagliardetto/solana-go/programs/compute-budget"
+ "github.com/gagliardetto/solana-go/programs/system"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/external_program_cpi_stub"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/mcm"
+)
+
+type TestMcmOperation struct {
+ Data []byte
+ ExpectedMethod string
+ ExpectedLogSubstr string
+ RemainingAccounts []*solana.AccountMeta
+ CheckExpectations func(instruction *utils.AnchorInstruction) error
+}
+
+func TestMcmSetRootAndExecute(t *testing.T) {
+ t.Parallel()
+ mcm.SetProgramID(config.McmProgram)
+ external_program_cpi_stub.SetProgramID(config.ExternalCpiStubProgram) // testing program
+
+ ctx := tests.Context(t)
+
+ admin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ user, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ solanaGoClient := utils.DeployAllPrograms(t, utils.PathToAnchorConfig, admin)
+
+ // mcm name
+ TestMsigName := config.TestMsigNamePaddedBuffer
+
+ // test mcm pdas
+ MultisigConfigPDA := McmConfigAddress(TestMsigName)
+ RootMetadataPDA := RootMetadataAddress(TestMsigName)
+ ExpiringRootAndOpCountPDA := ExpiringRootAndOpCountAddress(TestMsigName)
+ ConfigSignersPDA := McmConfigSignersAddress(TestMsigName)
+ MsigSignerPDA := McmSignerAddress(TestMsigName)
+
+ // helper to inject anchor discriminator into instruction data
+ // NOTE: if ix is built with anchor-go we don't need it
+ getAnchorInstructionData := func(method string, data []byte) []byte {
+ discriminator := sha256.Sum256([]byte("global:" + method))
+ return append(discriminator[:8], data...)
+ }
+
+ // NOTE: this list of operations is methods for testing program,
+ // contracts/programs/external_program_cpi_stub
+ // the other way to construct mcmTestOp is using IxToMcmTestOpNode
+ var stupProgramTestMcmOps = []TestMcmOperation{
+ {
+ ExpectedMethod: "Initialize",
+ Data: getAnchorInstructionData("initialize", nil),
+ ExpectedLogSubstr: "Called `initialize`",
+ RemainingAccounts: []*solana.AccountMeta{
+ {
+ PublicKey: config.StubAccountPDA,
+ IsSigner: false,
+ IsWritable: true,
+ },
+ {
+ PublicKey: McmSignerAddress(config.TestMsigNamePaddedBuffer),
+ IsSigner: false,
+ IsWritable: true,
+ },
+ {
+ PublicKey: solana.SystemProgramID,
+ IsSigner: false,
+ IsWritable: false,
+ },
+ },
+ },
+ {
+ ExpectedMethod: "Empty",
+ Data: getAnchorInstructionData("empty", nil),
+ ExpectedLogSubstr: "Called `empty`",
+ },
+ {
+ ExpectedMethod: "U8InstructionData",
+ Data: getAnchorInstructionData("u8_instruction_data", []byte{123}),
+ ExpectedLogSubstr: "Called `u8_instruction_data` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: Empty, remaining_accounts: [], bumps: EmptyBumps } and data 123",
+ },
+ {
+ ExpectedMethod: "StructInstructionData",
+ Data: getAnchorInstructionData("struct_instruction_data", []byte{234}),
+ ExpectedLogSubstr: "Called `struct_instruction_data` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: Empty, remaining_accounts: [], bumps: EmptyBumps } and data Value { value: 234 }",
+ },
+ {
+ ExpectedMethod: "AccountRead",
+ Data: getAnchorInstructionData("account_read", nil),
+ RemainingAccounts: []*solana.AccountMeta{
+ {
+ PublicKey: config.StubAccountPDA,
+ IsSigner: false,
+ IsWritable: false,
+ },
+ },
+ ExpectedLogSubstr: "Called `account_read`",
+ CheckExpectations: func(instruction *utils.AnchorInstruction) error {
+ if !strings.Contains(instruction.Logs[0], "value: 1") {
+ return fmt.Errorf("expected log to contain 'value: 1', got: %s", instruction.Logs[0])
+ }
+ return nil
+ },
+ },
+ {
+ ExpectedMethod: "AccountMut",
+ Data: getAnchorInstructionData("account_mut", nil),
+ RemainingAccounts: []*solana.AccountMeta{
+ {
+ PublicKey: config.StubAccountPDA,
+ IsSigner: false,
+ IsWritable: true,
+ },
+ {
+ PublicKey: McmSignerAddress(config.TestMsigNamePaddedBuffer),
+ IsSigner: false,
+ IsWritable: true,
+ },
+ {
+ PublicKey: solana.SystemProgramID,
+ IsSigner: false,
+ IsWritable: false,
+ },
+ },
+ ExpectedLogSubstr: "Called `account_mut`",
+ CheckExpectations: func(instruction *utils.AnchorInstruction) error {
+ if !strings.Contains(instruction.Logs[0], "is_writable: true") {
+ return fmt.Errorf("expected log to contain 'is_writable: true', got: %s", instruction.Logs[0])
+ }
+ return nil
+ },
+ },
+ }
+
+ t.Run("setup:funding", func(t *testing.T) {
+ utils.FundAccounts(ctx, []solana.PrivateKey{admin, user}, solanaGoClient, t)
+ // fund the signer pda
+ fundPDAIx := system.NewTransferInstruction(1*solana.LAMPORTS_PER_SOL, admin.PublicKey(), MsigSignerPDA).Build()
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient,
+ []solana.Instruction{fundPDAIx},
+ admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+ })
+
+ t.Run("fail: NOT able to init program from non-deployer user", func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.McmProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ ix, initErr := mcm.NewInitializeInstruction(
+ config.TestChainID,
+ TestMsigName,
+ MultisigConfigPDA,
+ user.PublicKey(),
+ solana.SystemProgramID,
+ config.McmProgram,
+ programData.Address,
+ RootMetadataPDA,
+ ExpiringRootAndOpCountPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, initErr)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, user, config.DefaultCommitment, []string{"Error Code: " + UnauthorizedMcmError.String()})
+ require.NotNil(t, result)
+ })
+
+ t.Run("setup:mcm", func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.McmProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ ix, initErr := mcm.NewInitializeInstruction(
+ config.TestChainID,
+ TestMsigName,
+ MultisigConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.McmProgram,
+ programData.Address,
+ RootMetadataPDA,
+ ExpiringRootAndOpCountPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, initErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, admin.PublicKey(), configAccount.Owner)
+ })
+
+ numSigners := 50
+ signerPrivateKeys, err := eth.GenerateEthPrivateKeys(numSigners)
+
+ t.Run("mcm:set_config:success", func(t *testing.T) {
+ require.NoError(t, err)
+
+ signerGroups := make([]byte, numSigners)
+ for i := 0; i < len(signerGroups); i++ {
+ signerGroups[i] = byte(i % 5)
+ }
+
+ // just use simple config for now
+ groupQuorums := []uint8{1, 1, 1, 1, 1}
+ groupParents := []uint8{0, 0, 0, 2, 0}
+
+ mcmConfig, configErr := mcmsUtils.NewValidMcmConfig(
+ TestMsigName,
+ signerPrivateKeys,
+ signerGroups,
+ groupQuorums,
+ groupParents,
+ config.ClearRoot,
+ )
+ require.NoError(t, configErr)
+
+ signerAddresses := mcmConfig.SignerAddresses
+
+ t.Run("mcm:preload signers", func(t *testing.T) {
+ ixs := make([]solana.Instruction, 0)
+
+ parsedTotalSigners, pErr := mcmsUtils.SafeToUint8(len(signerAddresses))
+ require.NoError(t, pErr)
+ initSignersIx, isErr := mcm.NewInitSignersInstruction(
+ TestMsigName,
+ parsedTotalSigners,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, isErr)
+ ixs = append(ixs, initSignersIx)
+
+ appendSignersIxs, asErr := AppendSignersIxs(signerAddresses, TestMsigName, MultisigConfigPDA, ConfigSignersPDA, admin.PublicKey(), config.MaxAppendSignerBatchSize)
+ require.NoError(t, asErr)
+ ixs = append(ixs, appendSignersIxs...)
+
+ finalizeSignersIx, fsErr := mcm.NewFinalizeSignersInstruction(
+ TestMsigName,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, fsErr)
+ ixs = append(ixs, finalizeSignersIx)
+
+ for _, ix := range ixs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var cfgSignersAccount mcm.ConfigSigners
+ queryErr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, ConfigSignersPDA, config.DefaultCommitment, &cfgSignersAccount)
+ require.NoError(t, queryErr, "failed to get account info")
+
+ require.Equal(t, true, cfgSignersAccount.IsFinalized)
+
+ // check if the addresses are registered correctly
+ for i, signer := range cfgSignersAccount.SignerAddresses {
+ require.Equal(t, signerAddresses[i], signer)
+ }
+ })
+
+ // set config
+ ix, configErr := mcm.NewSetConfigInstruction(
+ mcmConfig.MultisigName,
+ mcmConfig.SignerGroups,
+ mcmConfig.GroupQuorums,
+ mcmConfig.GroupParents,
+ mcmConfig.ClearRoot,
+ MultisigConfigPDA,
+ ConfigSignersPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, configErr)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ configErr = utils.GetAccountDataBorshInto(ctx, solanaGoClient, MultisigConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, configErr, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupParents, mcmConfig.GroupParents), true)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupQuorums, mcmConfig.GroupQuorums), true)
+
+ // check if the McmSigner struct is correct
+ for i, signer := range configAccount.Signers {
+ require.Equal(t, signer.EvmAddress, mcmConfig.SignerAddresses[i])
+ require.Equal(t, signer.Index, uint8(i))
+ require.Equal(t, signer.Group, mcmConfig.SignerGroups[i])
+ }
+ })
+
+ var opNodes []mcmsUtils.McmOpNode
+
+ t.Run("mcm:set_root:success", func(t *testing.T) {
+ for i, op := range stupProgramTestMcmOps {
+ node := mcmsUtils.McmOpNode{
+ Nonce: uint64(i),
+ Multisig: MultisigConfigPDA,
+ To: config.ExternalCpiStubProgram,
+ Data: op.Data,
+ RemainingAccounts: op.RemainingAccounts,
+ }
+ opNodes = append(opNodes, node)
+ }
+ validUntil := uint32(0xffffffff)
+
+ rootValidationData, rvErr := CreateMcmRootData(
+ McmRootInput{
+ Multisig: MultisigConfigPDA,
+ Operations: opNodes,
+ PreOpCount: 0,
+ PostOpCount: uint64(len(opNodes)),
+ ValidUntil: validUntil,
+ OverridePreviousRoot: false,
+ },
+ )
+ require.NoError(t, rvErr)
+ signaturesPDA := RootSignaturesAddress(TestMsigName, rootValidationData.Root, validUntil)
+
+ t.Run("mcm:preload signatures", func(t *testing.T) {
+ signers, getSignerErr := eth.GetEvmSigners(signerPrivateKeys)
+ require.NoError(t, getSignerErr, "Failed to get signers")
+
+ signatures, sigsErr := BulkSignOnMsgHash(signers, rootValidationData.EthMsgHash)
+ require.NoError(t, sigsErr)
+
+ parsedTotalSigs, pErr := mcmsUtils.SafeToUint8(len(signatures))
+ require.NoError(t, pErr)
+
+ initSigsIx, isErr := mcm.NewInitSignaturesInstruction(
+ TestMsigName,
+ rootValidationData.Root,
+ validUntil,
+ parsedTotalSigs,
+ signaturesPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, isErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initSigsIx}, admin, config.DefaultCommitment)
+
+ appendSigsIxs, asErr := AppendSignaturesIxs(signatures, TestMsigName, rootValidationData.Root, validUntil, signaturesPDA, admin.PublicKey(), config.MaxAppendSignatureBatchSize)
+ require.NoError(t, asErr)
+
+ // partially register signatures
+ for _, ix := range appendSigsIxs[:3] {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ // clear uploaded signatures
+ clearIx, cErr := mcm.NewClearSignaturesInstruction(
+ TestMsigName,
+ rootValidationData.Root,
+ validUntil,
+ signaturesPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, cErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{clearIx}, admin, config.DefaultCommitment)
+
+ // register all signatures again
+ for _, ix := range appendSigsIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ finalizeSigsIx, fsErr := mcm.NewFinalizeSignaturesInstruction(
+ TestMsigName,
+ rootValidationData.Root,
+ validUntil,
+ signaturesPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+
+ require.NoError(t, fsErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{finalizeSigsIx}, admin, config.DefaultCommitment)
+
+ var sigAccount mcm.RootSignatures
+ queryErr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, signaturesPDA, config.DefaultCommitment, &sigAccount)
+ require.NoError(t, queryErr, "failed to get account info")
+
+ require.Equal(t, true, sigAccount.IsFinalized)
+ require.Equal(t, true, sigAccount.TotalSignatures == uint8(len(signatures)))
+
+ // check if the sigs are registered correctly
+ for i, sig := range sigAccount.Signatures {
+ require.Equal(t, signatures[i], sig)
+ }
+ })
+
+ newIx, setRootIxErr := mcm.NewSetRootInstruction(
+ TestMsigName,
+ rootValidationData.Root,
+ validUntil,
+ rootValidationData.Metadata,
+ rootValidationData.MetadataProof,
+ signaturesPDA,
+ RootMetadataPDA,
+ SeenSignedHashesAddress(TestMsigName, rootValidationData.Root, validUntil),
+ ExpiringRootAndOpCountPDA,
+ MultisigConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, setRootIxErr)
+ cu := uint32(numSigners * 28_000) //estimated cu per signer
+ require.True(t, cu <= 1_400_000, "maximum CU limit exceeded")
+ cuIx, cuErr := computebudget.NewSetComputeUnitLimitInstruction(cu).ValidateAndBuild()
+ require.NoError(t, cuErr)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{cuIx, newIx}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var newRootAndOpCount mcm.ExpiringRootAndOpCount
+
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, ExpiringRootAndOpCountPDA, config.DefaultCommitment, &newRootAndOpCount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, rootValidationData.Root, newRootAndOpCount.Root)
+ require.Equal(t, validUntil, newRootAndOpCount.ValidUntil)
+ require.Equal(t, rootValidationData.Metadata.PreOpCount, newRootAndOpCount.OpCount)
+
+ // get config and validate
+ var newRootMetadata mcm.RootMetadata
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, RootMetadataPDA, config.DefaultCommitment, &newRootMetadata)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, rootValidationData.Metadata.ChainId, newRootMetadata.ChainId)
+ require.Equal(t, rootValidationData.Metadata.Multisig, newRootMetadata.Multisig)
+ require.Equal(t, rootValidationData.Metadata.PreOpCount, newRootMetadata.PreOpCount)
+ require.Equal(t, rootValidationData.Metadata.PostOpCount, newRootMetadata.PostOpCount)
+ require.Equal(t, rootValidationData.Metadata.OverridePreviousRoot, newRootMetadata.OverridePreviousRoot)
+ })
+
+ t.Run("mcm:execute:success", func(t *testing.T) {
+ for i, op := range opNodes {
+ proofs, proofsErr := op.Proofs()
+ require.NoError(t, proofsErr, "Failed to getting op proof")
+
+ ix := mcm.NewExecuteInstruction(
+ TestMsigName,
+ config.TestChainID,
+ op.Nonce,
+ op.Data,
+ proofs,
+
+ MultisigConfigPDA,
+ RootMetadataPDA,
+ ExpiringRootAndOpCountPDA,
+ config.ExternalCpiStubProgram,
+ McmSignerAddress(TestMsigName),
+ admin.PublicKey(),
+ )
+ // append remaining accounts
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op.RemainingAccounts...)
+
+ vIx, vIxErr := ix.ValidateAndBuild()
+ require.NoError(t, vIxErr)
+
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, admin, config.DefaultCommitment)
+ require.NotNil(t, tx.Meta)
+ require.Nil(t, tx.Meta.Err, fmt.Sprintf("tx failed with: %+v", tx.Meta))
+
+ parsedInstructions := utils.ParseLogMessages(tx.Meta.LogMessages)
+
+ require.Len(t, parsedInstructions, 1, "Expected 1 top-level instruction")
+
+ topLevelInstruction := parsedInstructions[0]
+ require.Equal(t, "Execute", topLevelInstruction.Name, "Top-level instruction should be Execute")
+ require.Equal(t, config.McmProgram.String(), topLevelInstruction.ProgramID, "Top-level instruction should be executed by MCM program")
+
+ require.Len(t, topLevelInstruction.InnerCalls, 1, "Expected 1 inner call")
+ innerCall := topLevelInstruction.InnerCalls[0]
+
+ require.Equal(t, stupProgramTestMcmOps[i].ExpectedMethod, innerCall.Name, "Inner call name should match the expected method")
+ require.Equal(t, config.ExternalCpiStubProgram.String(), innerCall.ProgramID, "Inner call should be executed by external CPI stub program")
+
+ require.NotEmpty(t, innerCall.Logs, "Inner call should have logs")
+ require.Contains(t, innerCall.Logs[0], stupProgramTestMcmOps[i].ExpectedLogSubstr, "Inner call log should contain expected substring")
+
+ if stupProgramTestMcmOps[i].CheckExpectations != nil {
+ vIxErr = stupProgramTestMcmOps[i].CheckExpectations(innerCall)
+ require.NoError(t, vIxErr, "Custom expectations check failed")
+ }
+ }
+
+ var stubAccountValue external_program_cpi_stub.Value
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.StubAccountPDA, config.DefaultCommitment, &stubAccountValue)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, uint8(2), stubAccountValue.Value)
+ })
+}
diff --git a/chains/solana/contracts/tests/mcms/mcm_timelock_test.go b/chains/solana/contracts/tests/mcms/mcm_timelock_test.go
new file mode 100644
index 000000000..4c8686ff1
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/mcm_timelock_test.go
@@ -0,0 +1,1619 @@
+package contracts
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+ "time"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ computebudget "github.com/gagliardetto/solana-go/programs/compute-budget"
+ "github.com/gagliardetto/solana-go/programs/system"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/access_controller"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/mcm"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
+)
+
+func TestMcmWithTimelock(t *testing.T) {
+ t.Parallel()
+ ctx := tests.Context(t)
+
+ mcm.SetProgramID(config.McmProgram)
+ timelock.SetProgramID(config.TimelockProgram)
+ access_controller.SetProgramID(config.AccessControllerProgram)
+
+ // initial admin
+ admin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ anyone, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ solanaGoClient := utils.DeployAllPrograms(t, utils.PathToAnchorConfig, admin)
+
+ msigs := map[timelock.Role]RoleMultisigs{
+ timelock.Proposer_Role: CreateRoleMultisigs(timelock.Proposer_Role, 1),
+ timelock.Canceller_Role: CreateRoleMultisigs(timelock.Canceller_Role, 1),
+ timelock.Executor_Role: CreateRoleMultisigs(timelock.Executor_Role, 1),
+ timelock.Bypasser_Role: CreateRoleMultisigs(timelock.Bypasser_Role, 1),
+ }
+
+ require.NoError(t, err)
+
+ t.Run("setup:funding", func(t *testing.T) {
+ utils.FundAccounts(ctx, []solana.PrivateKey{admin, anyone}, solanaGoClient, t)
+
+ // fund msig PDA signers
+ for _, roleMsigs := range msigs {
+ ixs := make([]solana.Instruction, 0)
+ for _, msig := range roleMsigs.Multisigs {
+ fundPDAIx := system.NewTransferInstruction(1*solana.LAMPORTS_PER_SOL, admin.PublicKey(), msig.SignerPDA).Build()
+ ixs = append(ixs, fundPDAIx)
+ }
+ utils.SendAndConfirm(ctx, t, solanaGoClient,
+ ixs,
+ admin, config.DefaultCommitment)
+ }
+ // fund timelock signer
+ fundPDAIx := system.NewTransferInstruction(1*solana.LAMPORTS_PER_SOL, admin.PublicKey(), config.TimelockSignerPDA).Build()
+ utils.SendAndConfirm(ctx, t, solanaGoClient,
+ []solana.Instruction{fundPDAIx},
+ admin, config.DefaultCommitment)
+ })
+
+ t.Run("setup: initialize mcm multisigs", func(t *testing.T) {
+ for role, roleMsigs := range msigs {
+ for _, msig := range roleMsigs.Multisigs {
+ t.Run(fmt.Sprintf("init mcm for role %s with multisig %s", role.String(), mcmsUtils.UnpadString32(msig.PaddedName)), func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.McmProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ ix, initIxErr := mcm.NewInitializeInstruction(
+ config.TestChainID,
+ msig.PaddedName,
+ msig.ConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.McmProgram,
+ programData.Address,
+ msig.RootMetadataPDA,
+ msig.ExpiringRootAndOpCountPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, initIxErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, msig.ConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, admin.PublicKey(), configAccount.Owner)
+ require.Equal(t, msig.PaddedName, configAccount.MultisigName)
+ })
+ }
+ }
+ })
+
+ t.Run("setup: set_config for each mcm multisigs", func(t *testing.T) {
+ for role, roleMsigs := range msigs {
+ for _, msig := range roleMsigs.Multisigs {
+ t.Run(fmt.Sprintf("set_config of role %s with multisig %s", role.String(), mcmsUtils.UnpadString32(msig.PaddedName)), func(t *testing.T) {
+ signerAddresses := msig.RawConfig.SignerAddresses
+
+ t.Run("preload signers on PDA", func(t *testing.T) {
+ ixs := make([]solana.Instruction, 0)
+
+ parsedTotalSigners, parseErr := mcmsUtils.SafeToUint8(len(signerAddresses))
+ require.NoError(t, parseErr)
+
+ initSignersIx, initSignersIxErr := mcm.NewInitSignersInstruction(
+ msig.PaddedName,
+ parsedTotalSigners,
+ msig.ConfigPDA,
+ msig.ConfigSignersPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, initSignersIxErr)
+ ixs = append(ixs, initSignersIx)
+
+ appendSignersIxs, appendSignersIxsErr := AppendSignersIxs(signerAddresses, msig.PaddedName, msig.ConfigPDA, msig.ConfigSignersPDA, admin.PublicKey(), config.MaxAppendSignerBatchSize)
+ require.NoError(t, appendSignersIxsErr)
+ ixs = append(ixs, appendSignersIxs...)
+
+ finalizeSignersIx, finSignersIxErr := mcm.NewFinalizeSignersInstruction(
+ msig.PaddedName,
+ msig.ConfigPDA,
+ msig.ConfigSignersPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, finSignersIxErr)
+ ixs = append(ixs, finalizeSignersIx)
+
+ for _, ix := range ixs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var cfgSignersAccount mcm.ConfigSigners
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, msig.ConfigSignersPDA, config.DefaultCommitment, &cfgSignersAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, true, cfgSignersAccount.IsFinalized)
+
+ // check if the addresses are registered correctly
+ for i, signer := range cfgSignersAccount.SignerAddresses {
+ require.Equal(t, signerAddresses[i], signer)
+ }
+ })
+
+ t.Run("success:set_config", func(t *testing.T) {
+ // set config
+ ix, setConfigErr := mcm.NewSetConfigInstruction(
+ msig.PaddedName,
+ msig.RawConfig.SignerGroups,
+ msig.RawConfig.GroupQuorums,
+ msig.RawConfig.GroupParents,
+ msig.RawConfig.ClearRoot,
+ msig.ConfigPDA,
+ msig.ConfigSignersPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, setConfigErr)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // get config and validate
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, msig.ConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, config.TestChainID, configAccount.ChainId)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupParents, msig.RawConfig.GroupParents), true)
+ require.Equal(t, reflect.DeepEqual(configAccount.GroupQuorums, msig.RawConfig.GroupQuorums), true)
+
+ // check if the McmSigner struct is correct
+ for i, signer := range configAccount.Signers {
+ require.Equal(t, signer.EvmAddress, msig.RawConfig.SignerAddresses[i])
+ require.Equal(t, signer.Index, uint8(i))
+ require.Equal(t, signer.Group, msig.RawConfig.SignerGroups[i])
+ }
+
+ // pda closed after set_config
+ utils.AssertClosedAccount(ctx, t, solanaGoClient, msig.ConfigSignersPDA, config.DefaultCommitment)
+ })
+ })
+ }
+ }
+ })
+
+ t.Run("setup: timelock", func(t *testing.T) {
+ for role, roleMsigs := range msigs {
+ t.Run(fmt.Sprintf("init access controller for role %s", role.String()), func(t *testing.T) {
+ initAccIxs, initAccErr := InitAccessControllersIxs(ctx, roleMsigs.AccessController.PublicKey(), admin, solanaGoClient)
+ require.NoError(t, initAccErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, initAccIxs, admin, config.DefaultCommitment, utils.AddSigners(roleMsigs.AccessController))
+
+ var ac access_controller.AccessController
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, roleMsigs.AccessController.PublicKey(), config.DefaultCommitment, &ac)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ })
+ }
+
+ t.Run("initialize timelock with access controllers", func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ initTimelockIx, initTimelockErr := timelock.NewInitializeInstruction(
+ config.MinDelay,
+ config.TimelockConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.TimelockProgram,
+ programData.Address,
+ config.AccessControllerProgram,
+ msigs[timelock.Proposer_Role].AccessController.PublicKey(),
+ msigs[timelock.Executor_Role].AccessController.PublicKey(),
+ msigs[timelock.Canceller_Role].AccessController.PublicKey(),
+ msigs[timelock.Bypasser_Role].AccessController.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, initTimelockErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initTimelockIx}, admin, config.DefaultCommitment)
+
+ var configAccount timelock.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.TimelockConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t, admin.PublicKey(), configAccount.Owner, "Owner doesn't match")
+ require.Equal(t, config.MinDelay, configAccount.MinDelay, "MinDelay doesn't match")
+ require.Equal(t, msigs[timelock.Proposer_Role].AccessController.PublicKey(), configAccount.ProposerRoleAccessController, "ProposerRoleAccessController doesn't match")
+ require.Equal(t, msigs[timelock.Executor_Role].AccessController.PublicKey(), configAccount.ExecutorRoleAccessController, "ExecutorRoleAccessController doesn't match")
+ require.Equal(t, msigs[timelock.Canceller_Role].AccessController.PublicKey(), configAccount.CancellerRoleAccessController, "CancellerRoleAccessController doesn't match")
+ require.Equal(t, msigs[timelock.Bypasser_Role].AccessController.PublicKey(), configAccount.BypasserRoleAccessController, "BypasserRoleAccessController doesn't match")
+ })
+
+ t.Run("register msig signers to each role", func(t *testing.T) {
+ for role, roleMsigs := range msigs {
+ t.Run(fmt.Sprintf("registering role %s", role.String()), func(t *testing.T) {
+ t.Parallel()
+ addresses := []solana.PublicKey{}
+ for _, msig := range roleMsigs.Multisigs {
+ addresses = append(addresses, msig.SignerPDA)
+ }
+ batchAddAccessIxs, batchAddAccessErr := TimelockBatchAddAccessIxs(ctx, roleMsigs.AccessController.PublicKey(), role, addresses, admin, config.BatchAddAccessChunkSize, solanaGoClient)
+ require.NoError(t, batchAddAccessErr)
+
+ for _, ix := range batchAddAccessIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var ac access_controller.AccessController
+ err = utils.GetAccountDataBorshInto(
+ ctx,
+ solanaGoClient,
+ roleMsigs.AccessController.PublicKey(),
+ config.DefaultCommitment,
+ &ac,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, uint64(len(roleMsigs.Multisigs)), ac.AccessList.Len,
+ "AccessList length mismatch for %s", role)
+
+ for _, msig := range roleMsigs.Multisigs {
+ targetPubKey := msig.SignerPDA
+ _, found := mcmsUtils.FindInSortedList(ac.AccessList.Xs[:ac.AccessList.Len], targetPubKey)
+ require.True(t, found, "Account %s not found in %s AccessList",
+ targetPubKey, role)
+ }
+ })
+ }
+ })
+ })
+
+ t.Run("setup: transfer ownership multisigs to timelock signer", func(t *testing.T) {
+ for role, roleMsigs := range msigs {
+ for _, msig := range roleMsigs.Multisigs {
+ t.Run(fmt.Sprintf("transfer ownership of role %s multisig %s to timelock signer", role.String(), mcmsUtils.UnpadString32(msig.PaddedName)), func(t *testing.T) {
+ t.Parallel()
+ ix, transferOwnershipErr := mcm.NewTransferOwnershipInstruction(
+ msig.PaddedName,
+ config.TimelockSignerPDA, // new proposed owner
+ msig.ConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, transferOwnershipErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+
+ var configAccount mcm.MultisigConfig
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, msig.ConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ require.Equal(t, admin.PublicKey(), configAccount.Owner)
+ require.Equal(t, config.TimelockSignerPDA, configAccount.ProposedOwner)
+
+ acceptOwnershipIx, acceptOwnershipixErr := mcm.NewAcceptOwnershipInstruction(
+ msig.PaddedName,
+ msig.ConfigPDA,
+ config.TimelockSignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, acceptOwnershipixErr)
+
+ salt, sErr := mcmsUtils.SimpleSalt()
+ require.NoError(t, sErr)
+ acceptOwnershipOp := TimelockOperation{
+ Predecessor: config.TimelockEmptyOpID,
+ Salt: salt,
+ Delay: uint64(1),
+ }
+
+ acceptOwnershipOp.AddInstruction(acceptOwnershipIx, []solana.PublicKey{config.McmProgram})
+
+ id := acceptOwnershipOp.OperationID()
+ operationPDA := acceptOwnershipOp.OperationPDA()
+
+ ixs := make([]solana.Instruction, 0)
+ initOpIx, initOpErr := timelock.NewInitializeOperationInstruction(
+ acceptOwnershipOp.OperationID(),
+ acceptOwnershipOp.Predecessor,
+ acceptOwnershipOp.Salt,
+ acceptOwnershipOp.IxsCountU32(),
+ config.TimelockConfigPDA,
+ operationPDA,
+ admin.PublicKey(),
+ admin.PublicKey(), // proposer - direct schedule batch here
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, initOpErr)
+ ixs = append(ixs, initOpIx)
+
+ appendIxIx, apErr := timelock.NewAppendInstructionsInstruction(
+ acceptOwnershipOp.OperationID(),
+ acceptOwnershipOp.ToInstructionData(),
+ operationPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, apErr)
+
+ ixs = append(ixs, appendIxIx)
+
+ finIxIx, finErr := timelock.NewFinalizeOperationInstruction(
+ acceptOwnershipOp.OperationID(),
+ operationPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, finErr)
+ ixs = append(ixs, finIxIx)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, ixs, admin, config.DefaultCommitment)
+
+ scheduleBatchIx, scErr := timelock.NewScheduleBatchInstruction(
+ acceptOwnershipOp.OperationID(),
+ acceptOwnershipOp.Delay,
+ config.TimelockConfigPDA,
+ operationPDA,
+ roleMsigs.AccessController.PublicKey(),
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, scErr)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{scheduleBatchIx}, admin, config.DefaultCommitment)
+
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, operationPDA, config.DefaultCommitment, &opAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t,
+ result.BlockTime.Time().Add(time.Duration(acceptOwnershipOp.Delay)*time.Second).Unix(),
+ int64(opAccount.Timestamp),
+ "Scheduled Times don't match",
+ )
+
+ require.Equal(t,
+ id,
+ opAccount.Id,
+ "Ids don't match",
+ )
+
+ bypassExeIx := timelock.NewBypasserExecuteBatchInstruction(
+ acceptOwnershipOp.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ acceptOwnershipOp.OperationPDA(),
+ roleMsigs.AccessController.PublicKey(),
+ admin.PublicKey(), // bypass execute with admin previledges
+ )
+ bypassExeIx.AccountMetaSlice = append(bypassExeIx.AccountMetaSlice, acceptOwnershipOp.RemainingAccounts()...)
+
+ vIx, vIxErr := bypassExeIx.ValidateAndBuild()
+ require.NoError(t, vIxErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, admin, config.DefaultCommitment)
+
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, msig.ConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ require.Equal(t, config.TimelockSignerPDA, configAccount.Owner)
+ require.Equal(t, solana.PublicKey{}, configAccount.ProposedOwner)
+ })
+ }
+ }
+ })
+
+ // shared proposer msig for testing OpCount in metadata
+ proposerMsig := msigs[timelock.Proposer_Role].GetAnyMultisig()
+ // keep track of operation count, this will be updated after each operation
+ currentOpCount := 0
+
+ // NOTE: These tests are not designed to run in parallel, we're testing opCount also(using currentOpCount).
+ t.Run("mcm + timelock mint operation", func(t *testing.T) {
+ for _, v := range []struct {
+ tokenName string
+ tokenProgram solana.PublicKey
+ }{
+ {tokenName: "spl-token", tokenProgram: solana.TokenProgramID},
+ {tokenName: "spl-token-2022", tokenProgram: config.Token2022Program},
+ } {
+ t.Run(v.tokenName, func(t *testing.T) {
+ mintKeypair, mintKeypairErr := solana.NewRandomPrivateKey()
+ require.NoError(t, mintKeypairErr)
+ mint := mintKeypair.PublicKey()
+
+ // Use CreateToken utility to get initialization instructions
+ // NOTE: can't create token with cpi(mint signature required)
+ createTokenIxs, createTokenErr := utils.CreateToken(
+ ctx,
+ v.tokenProgram, // token program
+ mint, // mint account
+ admin.PublicKey(), // initial mint owner(admin)
+ 9, // decimals
+ solanaGoClient,
+ config.DefaultCommitment,
+ )
+ require.NoError(t, createTokenErr)
+
+ for _, ix := range createTokenIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment, utils.AddSigners(mintKeypair))
+ }
+
+ t.Run("mint ixs", func(t *testing.T) {
+ // 1. mcm set_root and execute through multisigs
+ // 1-1. mcm:: set_root { root of { timelock::schedule_batch { spl::mint }}}
+ // 1-2. pre-create operation PDA & upload instructions with timelock::initialize_operation, append_instructions, finalize_operation
+ // 1-3. mcm:: execute { timelock::schedule_batch { spl::mint }}
+ // 2. execute scheduled transaction
+ // 2-1. timelock worker -> timelock::execute_batch { spl::mint } by op id
+
+ recipient, kerr := solana.NewRandomPrivateKey()
+ require.NoError(t, kerr)
+
+ rIxATA, rAta, rAtaIxErr := utils.CreateAssociatedTokenAccount(v.tokenProgram, mint, recipient.PublicKey(), admin.PublicKey())
+ require.NoError(t, rAtaIxErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{rIxATA}, admin, config.DefaultCommitment)
+
+ _, rInitBal, bErr := utils.TokenBalance(ctx, solanaGoClient, rAta, config.DefaultCommitment)
+ require.NoError(t, bErr)
+ require.Equal(t, 0, rInitBal)
+
+ // mint authority to timelock
+ authIx, aErr := utils.SetTokenMintAuthority(v.tokenProgram, config.TimelockSignerPDA, mint, admin.PublicKey())
+ require.NoError(t, aErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{authIx}, admin, config.DefaultCommitment)
+
+ numMintIxs := 18
+
+ salt, sErr := mcmsUtils.SimpleSalt()
+ require.NoError(t, sErr)
+ opToSchedule := TimelockOperation{
+ Predecessor: config.TimelockEmptyOpID, // no predecessor
+ Salt: salt,
+ Delay: uint64(1),
+ }
+
+ for i := 0; i < numMintIxs; i++ {
+ // timelock signer can mint token (transferred authority)
+ ix, mIxErr := utils.MintTo(1*solana.LAMPORTS_PER_SOL, v.tokenProgram, mint, rAta, config.TimelockSignerPDA)
+ require.NoError(t, mIxErr)
+
+ // add instruction to timelock operation
+ opToSchedule.AddInstruction(ix, []solana.PublicKey{v.tokenProgram})
+ }
+
+ initOpIx, ioErr := timelock.NewInitializeOperationInstruction(
+ opToSchedule.OperationID(),
+ opToSchedule.Predecessor,
+ opToSchedule.Salt,
+ uint32(len(opToSchedule.instructions)),
+ config.TimelockConfigPDA,
+ opToSchedule.OperationPDA(),
+ admin.PublicKey(),
+ proposerMsig.SignerPDA,
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, ioErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initOpIx}, admin, config.DefaultCommitment)
+
+ for _, instruction := range opToSchedule.ToInstructionData() {
+ appendIxsIx, apErr := timelock.NewAppendInstructionsInstruction(
+ opToSchedule.OperationID(),
+ []timelock.InstructionData{instruction}, // this should be a slice of instruction within 1232 bytes
+ opToSchedule.OperationPDA(),
+ admin.PublicKey(),
+ solana.SystemProgramID, // for reallocation
+ ).ValidateAndBuild()
+ require.NoError(t, apErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{appendIxsIx}, admin, config.DefaultCommitment)
+ }
+
+ finOpIx, foErr := timelock.NewFinalizeOperationInstruction(
+ opToSchedule.OperationID(),
+ opToSchedule.OperationPDA(),
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, foErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{finOpIx}, admin, config.DefaultCommitment)
+
+ // Schedule the operation
+ scheduleIx, scErr := timelock.NewScheduleBatchInstruction(
+ opToSchedule.OperationID(),
+ opToSchedule.Delay,
+ config.TimelockConfigPDA,
+ opToSchedule.OperationPDA(),
+ msigs[timelock.Proposer_Role].AccessController.PublicKey(),
+ proposerMsig.SignerPDA, // msig signer since we're going to run this ix with mcm::execute
+ ).ValidateAndBuild()
+ require.NoError(t, scErr)
+
+ node, cErr := IxToMcmTestOpNode(proposerMsig.ConfigPDA, proposerMsig.SignerPDA, scheduleIx, uint64(currentOpCount)) // operation nonce
+ require.NoError(t, cErr)
+ mcmOpNodes := []mcmsUtils.McmOpNode{node} // only one mcm op node
+ validUntil := uint32(0xffffffff)
+
+ rootValidationData, rErr := CreateMcmRootData(McmRootInput{
+ Multisig: proposerMsig.ConfigPDA,
+ Operations: mcmOpNodes,
+ PreOpCount: uint64(currentOpCount),
+ PostOpCount: uint64(currentOpCount + len(mcmOpNodes)),
+ ValidUntil: validUntil,
+ OverridePreviousRoot: false,
+ })
+ require.NoError(t, rErr)
+
+ currentOpCount += len(mcmOpNodes)
+
+ signatures, bulkSignErr := BulkSignOnMsgHash(proposerMsig.Signers, rootValidationData.EthMsgHash)
+ require.NoError(t, bulkSignErr)
+ signaturesPDA := proposerMsig.RootSignaturesPDA(rootValidationData.Root, validUntil)
+
+ t.Run("mcm:preload signatures", func(t *testing.T) {
+ parsedTotalSigs, pErr := mcmsUtils.SafeToUint8(len(signatures))
+ require.NoError(t, pErr)
+
+ ixs := make([]solana.Instruction, 0)
+
+ initSigsIx, isErr := mcm.NewInitSignaturesInstruction(
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ parsedTotalSigs,
+ signaturesPDA,
+ admin.PublicKey(), // auth from someone who call set_root
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, isErr)
+ ixs = append(ixs, initSigsIx)
+
+ appendSigsIxs, asErr := AppendSignaturesIxs(signatures, proposerMsig.PaddedName, rootValidationData.Root, validUntil, signaturesPDA, admin.PublicKey(), config.MaxAppendSignatureBatchSize)
+ require.NoError(t, asErr)
+ ixs = append(ixs, appendSigsIxs...)
+
+ finalizeSigsIx, fsErr := mcm.NewFinalizeSignaturesInstruction(
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ signaturesPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, fsErr)
+ ixs = append(ixs, finalizeSigsIx)
+
+ for _, ix := range ixs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var sigAccount mcm.RootSignatures
+ queryErr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, signaturesPDA, config.DefaultCommitment, &sigAccount)
+ require.NoError(t, queryErr, "failed to get account info")
+
+ require.Equal(t, true, sigAccount.IsFinalized)
+ require.Equal(t, true, sigAccount.TotalSignatures == uint8(len(signatures)))
+
+ // check if the sigs are registered correctly
+ for i, sig := range sigAccount.Signatures {
+ require.Equal(t, signatures[i], sig)
+ }
+ })
+
+ t.Run("mcm:set_root", func(t *testing.T) {
+ newIx, setRootIxErr := mcm.NewSetRootInstruction(
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ rootValidationData.Metadata,
+ rootValidationData.MetadataProof,
+ signaturesPDA,
+ proposerMsig.RootMetadataPDA,
+ SeenSignedHashesAddress(proposerMsig.PaddedName, rootValidationData.Root, validUntil),
+ proposerMsig.ExpiringRootAndOpCountPDA,
+ proposerMsig.ConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, setRootIxErr)
+
+ cuIx, cuErr := computebudget.NewSetComputeUnitLimitInstruction(1_400_000).ValidateAndBuild()
+ require.NoError(t, cuErr)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{cuIx, newIx}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var newRootAndOpCount mcm.ExpiringRootAndOpCount
+
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, proposerMsig.ExpiringRootAndOpCountPDA, config.DefaultCommitment, &newRootAndOpCount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, rootValidationData.Root, newRootAndOpCount.Root)
+ require.Equal(t, validUntil, newRootAndOpCount.ValidUntil)
+ require.Equal(t, rootValidationData.Metadata.PreOpCount, newRootAndOpCount.OpCount)
+
+ // get config and validate
+ var newRootMetadata mcm.RootMetadata
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, proposerMsig.RootMetadataPDA, config.DefaultCommitment, &newRootMetadata)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, rootValidationData.Metadata.ChainId, newRootMetadata.ChainId)
+ require.Equal(t, rootValidationData.Metadata.Multisig, newRootMetadata.Multisig)
+ require.Equal(t, rootValidationData.Metadata.PreOpCount, newRootMetadata.PreOpCount)
+ require.Equal(t, rootValidationData.Metadata.PostOpCount, newRootMetadata.PostOpCount)
+ require.Equal(t, rootValidationData.Metadata.OverridePreviousRoot, newRootMetadata.OverridePreviousRoot)
+ })
+
+ t.Run("mcm:execute -> timelock::schedule_batch", func(t *testing.T) {
+ t.Run("check if timelock config is correct", func(t *testing.T) {
+ info, infoErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockConfigPDA, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, infoErr)
+ require.Equal(t, info.Value.Owner, config.TimelockProgram, "Timelock config owner doesn't match")
+ })
+
+ for _, op := range mcmOpNodes {
+ proofs, proofsErr := op.Proofs()
+ require.NoError(t, proofsErr, "Failed to getting op proof")
+
+ ix := mcm.NewExecuteInstruction(
+ proposerMsig.PaddedName,
+ config.TestChainID,
+ op.Nonce,
+ op.Data,
+ proofs,
+
+ proposerMsig.ConfigPDA,
+ proposerMsig.RootMetadataPDA,
+ proposerMsig.ExpiringRootAndOpCountPDA,
+ op.To,
+ proposerMsig.SignerPDA,
+ anyone.PublicKey(),
+ )
+
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op.RemainingAccounts...)
+
+ vIx, vIxErr := ix.ValidateAndBuild()
+ require.NoError(t, vIxErr)
+
+ tx := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, anyone, config.DefaultCommitment)
+ require.NotNil(t, tx.Meta)
+ require.Nil(t, tx.Meta.Err, fmt.Sprintf("tx failed with: %+v", tx.Meta))
+
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, opToSchedule.OperationPDA(), config.DefaultCommitment, &opAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t,
+ tx.BlockTime.Time().Add(time.Duration(opToSchedule.Delay)*time.Second).Unix(),
+ int64(opAccount.Timestamp),
+ "Scheduled Times don't match",
+ )
+
+ require.Equal(t,
+ opToSchedule.OperationID(),
+ opAccount.Id,
+ "Ids don't match",
+ )
+ }
+ })
+
+ t.Run("success: wait for operations to be ready", func(t *testing.T) {
+ wErr := WaitForOperationToBeReady(ctx, solanaGoClient, opToSchedule.OperationPDA(), config.DefaultCommitment)
+ require.NoError(t, wErr)
+ })
+
+ t.Run("timelock worker -> timelock::execute_batch", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ opToSchedule.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ opToSchedule.OperationPDA(),
+ config.TimelockEmptyOpID,
+ msigs[timelock.Executor_Role].AccessController.PublicKey(),
+ admin.PublicKey(), // timelock worker authority
+ )
+
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, opToSchedule.RemainingAccounts()...)
+
+ vIx, vErr := ix.ValidateAndBuild()
+ require.NoError(t, vErr)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, admin, config.DefaultCommitment, utils.AddComputeUnitLimit(1_400_000))
+ require.NotNil(t, result)
+
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, opToSchedule.OperationPDA(), config.DefaultCommitment, &opAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t,
+ config.TimelockOpDoneTimestamp,
+ opAccount.Timestamp,
+ "Executed operation's time should be 1(DONE_TIMESTAMP)",
+ )
+
+ _, rInitBal, bErr := utils.TokenBalance(ctx, solanaGoClient, rAta, config.DefaultCommitment)
+ require.NoError(t, bErr)
+ require.Equal(t, numMintIxs*int(solana.LAMPORTS_PER_SOL), rInitBal)
+ })
+ })
+ })
+ }
+
+ var rootAccount mcm.ExpiringRootAndOpCount
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, proposerMsig.ExpiringRootAndOpCountPDA, config.DefaultCommitment, &rootAccount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, uint64(currentOpCount), rootAccount.OpCount)
+ })
+
+ t.Run("scheduled token vesting scenario", func(t *testing.T) {
+ ///////////////////////////////////////////
+ // Setup - Create Token & Pass Authority //
+ ///////////////////////////////////////////
+ mintKeypair, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+ mint := mintKeypair.PublicKey()
+
+ tokenProgram := config.Token2022Program
+
+ // Use CreateToken utility to get initialization instructions
+ // NOTE: can't create token with cpi(mint signature required)
+ createTokenIxs, err := utils.CreateToken(
+ ctx,
+ tokenProgram, // token program
+ mint, // mint account
+ admin.PublicKey(), // initial mint owner(admin)
+ 9, // decimals
+ solanaGoClient,
+ config.DefaultCommitment,
+ )
+ require.NoError(t, err)
+
+ authIx, err := utils.SetTokenMintAuthority(tokenProgram, config.TimelockSignerPDA, mint, admin.PublicKey())
+ require.NoError(t, err)
+
+ setupIxs := append(createTokenIxs, authIx)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, setupIxs, admin, config.DefaultCommitment, utils.AddSigners(mintKeypair))
+
+ /////////////////////////////////////////
+ // Timelock Operation 1 - Initial Mint //
+ /////////////////////////////////////////
+ treasury, kerr := solana.NewRandomPrivateKey()
+ require.NoError(t, kerr)
+
+ ix1, treasuryATA, err := utils.CreateAssociatedTokenAccount(tokenProgram, mint, treasury.PublicKey(), config.TimelockSignerPDA)
+ require.NoError(t, err)
+
+ ix2, err := utils.MintTo(1000*solana.LAMPORTS_PER_SOL, tokenProgram, mint, treasuryATA, config.TimelockSignerPDA)
+ require.NoError(t, err)
+
+ salt1, err := mcmsUtils.SimpleSalt()
+ require.NoError(t, err)
+ op1 := TimelockOperation{
+ Predecessor: config.TimelockEmptyOpID, // no predecessor
+ Salt: salt1,
+ Delay: uint64(1),
+ }
+ op1.AddInstruction(ix1, []solana.PublicKey{tokenProgram, solana.SPLAssociatedTokenAccountProgramID})
+ op1.AddInstruction(ix2, []solana.PublicKey{tokenProgram})
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Timelock Operation 2 - Schedule team associated token account creation //
+ ////////////////////////////////////////////////////////////////////////////
+ team1, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+ team2, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+ team3, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ ix3, team1ATA, err := utils.CreateAssociatedTokenAccount(
+ tokenProgram, mint, team1.PublicKey(),
+ config.TimelockSignerPDA,
+ )
+ require.NoError(t, err)
+
+ ix4, team2ATA, err := utils.CreateAssociatedTokenAccount(
+ tokenProgram, mint, team2.PublicKey(),
+ config.TimelockSignerPDA,
+ )
+ require.NoError(t, err)
+
+ ix5, team3ATA, err := utils.CreateAssociatedTokenAccount(
+ tokenProgram, mint, team3.PublicKey(),
+ config.TimelockSignerPDA,
+ )
+ require.NoError(t, err)
+
+ salt2, err := mcmsUtils.SimpleSalt()
+ require.NoError(t, err)
+ op2 := TimelockOperation{
+ Predecessor: op1.OperationID(), // must happen after initial mint
+ Salt: salt2,
+ Delay: uint64(1),
+ }
+ op2.AddInstruction(ix3, []solana.PublicKey{tokenProgram, solana.SPLAssociatedTokenAccountProgramID})
+ op2.AddInstruction(ix4, []solana.PublicKey{tokenProgram, solana.SPLAssociatedTokenAccountProgramID})
+ op2.AddInstruction(ix5, []solana.PublicKey{tokenProgram, solana.SPLAssociatedTokenAccountProgramID})
+
+ //////////////////////////////////////////////////////////////
+ // Timelock Operation 3 - Schedule team token distribution //
+ //////////////////////////////////////////////////////////////
+ ix6, err := utils.TokenTransferChecked(100*solana.LAMPORTS_PER_SOL, 9, tokenProgram, treasuryATA, mint, team1ATA, config.TimelockSignerPDA, []solana.PublicKey{})
+ require.NoError(t, err)
+ ix7, err := utils.TokenTransferChecked(200*solana.LAMPORTS_PER_SOL, 9, tokenProgram, treasuryATA, mint, team2ATA, config.TimelockSignerPDA, []solana.PublicKey{})
+ require.NoError(t, err)
+ ix8, err := utils.TokenTransferChecked(300*solana.LAMPORTS_PER_SOL, 9, tokenProgram, treasuryATA, mint, team3ATA, config.TimelockSignerPDA, []solana.PublicKey{})
+ require.NoError(t, err)
+
+ // add all team distribution instructions
+ salt3, err := mcmsUtils.SimpleSalt()
+ require.NoError(t, err)
+ op3 := TimelockOperation{
+ Predecessor: op2.OperationID(), // must happen after ata creation
+ Salt: salt3,
+ Delay: uint64(1),
+ }
+ op3.AddInstruction(ix6, []solana.PublicKey{tokenProgram})
+ op3.AddInstruction(ix7, []solana.PublicKey{tokenProgram})
+ op3.AddInstruction(ix8, []solana.PublicKey{tokenProgram})
+
+ require.NotEqual(t, op1.OperationID(), op2.OperationID(), "Operation IDs should be different")
+ require.NotEqual(t, op1.OperationID(), op3.OperationID(), "Operation IDs should be different")
+ require.NotEqual(t, op2.OperationID(), op3.OperationID(), "Operation IDs should be different")
+
+ ////////////////////////////////////////
+ // Pre-create Timelock Operation PDAs //
+ ////////////////////////////////////////
+ opNodes := []mcmsUtils.McmOpNode{}
+
+ for i, op := range []TimelockOperation{op1, op2, op3} {
+ t.Run(fmt.Sprintf("prepare mcm op node %d with timelock::schedule_batch ix", i), func(t *testing.T) {
+ prepareOpIxs := make([]solana.Instruction, 0)
+
+ initOpIx, initOpIxErr := timelock.NewInitializeOperationInstruction(
+ op.OperationID(),
+ op.Predecessor,
+ op.Salt,
+ op.IxsCountU32(),
+ config.TimelockConfigPDA,
+ op.OperationPDA(),
+ admin.PublicKey(),
+ proposerMsig.SignerPDA,
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, initOpIxErr)
+ prepareOpIxs = append(prepareOpIxs, initOpIx)
+
+ for _, ixData := range op.ToInstructionData() {
+ appendIxsIx, apErr := timelock.NewAppendInstructionsInstruction(
+ op.OperationID(),
+ []timelock.InstructionData{ixData}, // this should be a slice of instruction within 1232 bytes
+ op.OperationPDA(),
+ admin.PublicKey(),
+ solana.SystemProgramID, // for reallocation
+ ).ValidateAndBuild()
+ require.NoError(t, apErr)
+ prepareOpIxs = append(prepareOpIxs, appendIxsIx)
+ }
+
+ finOpIx, finOpErr := timelock.NewFinalizeOperationInstruction(
+ op.OperationID(),
+ op.OperationPDA(),
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, finOpErr)
+ prepareOpIxs = append(prepareOpIxs, finOpIx)
+
+ for _, ix := range prepareOpIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ ///////////////////////////////////////////
+ // Construct schedule_batch instruction //
+ ///////////////////////////////////////////
+ scheduleOpIx, scErr := timelock.NewScheduleBatchInstruction(
+ op.OperationID(),
+ op.Delay,
+ config.TimelockConfigPDA,
+ op.OperationPDA(),
+ msigs[timelock.Proposer_Role].AccessController.PublicKey(),
+ proposerMsig.SignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, scErr)
+
+ opNode, cErr := IxToMcmTestOpNode(proposerMsig.ConfigPDA, proposerMsig.SignerPDA, scheduleOpIx, uint64(currentOpCount+i))
+ require.NoError(t, cErr)
+ // fmt.Println("opNode", opNode)
+ opNodes = append(opNodes, opNode)
+ })
+ }
+
+ //////////////////////////////////
+ // mcm - Prepare root & root metadata //
+ //////////////////////////////////
+ validUntil := uint32(0xffffffff)
+
+ rootValidationData, err := CreateMcmRootData(McmRootInput{
+ Multisig: proposerMsig.ConfigPDA,
+ Operations: opNodes,
+ PreOpCount: uint64(currentOpCount),
+ PostOpCount: uint64(currentOpCount + len(opNodes)),
+ ValidUntil: validUntil,
+ OverridePreviousRoot: false,
+ })
+ require.NoError(t, err)
+
+ // update currentOpCount
+ currentOpCount += len(opNodes)
+
+ t.Run("offchain: bulk sign on root and upload signatures", func(t *testing.T) {
+ // sign the root
+ signatures, signErr := BulkSignOnMsgHash(proposerMsig.Signers, rootValidationData.EthMsgHash)
+ require.NoError(t, signErr)
+
+ ////////////////////////////////////////////////
+ // mcm::set_root - with preloading signatures //
+ ////////////////////////////////////////////////
+ parsedTotalSigs, pErr := mcmsUtils.SafeToUint8(len(signatures))
+ require.NoError(t, pErr)
+
+ ixs := make([]solana.Instruction, 0)
+
+ initSigsIx, isErr := mcm.NewInitSignaturesInstruction(
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ parsedTotalSigs,
+ proposerMsig.RootSignaturesPDA(rootValidationData.Root, validUntil),
+ admin.PublicKey(), // auth from someone who call set_root
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+
+ require.NoError(t, isErr)
+ ixs = append(ixs, initSigsIx)
+
+ appendSigsIxs, asErr := AppendSignaturesIxs(signatures, proposerMsig.PaddedName, rootValidationData.Root, validUntil, proposerMsig.RootSignaturesPDA(rootValidationData.Root, validUntil), admin.PublicKey(), config.MaxAppendSignatureBatchSize)
+ require.NoError(t, asErr)
+ ixs = append(ixs, appendSigsIxs...)
+
+ finalizeSigsIx, fsErr := mcm.NewFinalizeSignaturesInstruction(
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ proposerMsig.RootSignaturesPDA(rootValidationData.Root, validUntil),
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, fsErr)
+ ixs = append(ixs, finalizeSigsIx)
+
+ for _, ix := range ixs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var sigAccount mcm.RootSignatures
+ queryErr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, proposerMsig.RootSignaturesPDA(rootValidationData.Root, validUntil), config.DefaultCommitment, &sigAccount)
+ require.NoError(t, queryErr, "failed to get account info")
+
+ require.Equal(t, true, sigAccount.IsFinalized)
+ require.Equal(t, true, sigAccount.TotalSignatures == uint8(len(signatures)))
+
+ // check if the sigs are registered correctly
+ for i, sig := range sigAccount.Signatures {
+ require.Equal(t, signatures[i], sig)
+ }
+ })
+
+ t.Run("mcm::set_root", func(t *testing.T) {
+ newIx, setRootIxErr := mcm.NewSetRootInstruction(
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ rootValidationData.Metadata,
+ rootValidationData.MetadataProof,
+ proposerMsig.RootSignaturesPDA(rootValidationData.Root, validUntil),
+ proposerMsig.RootMetadataPDA,
+ SeenSignedHashesAddress(proposerMsig.PaddedName, rootValidationData.Root, validUntil),
+ proposerMsig.ExpiringRootAndOpCountPDA,
+ proposerMsig.ConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, setRootIxErr)
+
+ cuIx, cuErr := computebudget.NewSetComputeUnitLimitInstruction(1_400_000).ValidateAndBuild()
+ require.NoError(t, cuErr)
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{cuIx, newIx}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var newRootAndOpCount mcm.ExpiringRootAndOpCount
+
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, proposerMsig.ExpiringRootAndOpCountPDA, config.DefaultCommitment, &newRootAndOpCount)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, rootValidationData.Root, newRootAndOpCount.Root)
+ require.Equal(t, validUntil, newRootAndOpCount.ValidUntil)
+ require.Equal(t, rootValidationData.Metadata.PreOpCount, newRootAndOpCount.OpCount)
+
+ // get config and validate
+ var newRootMetadata mcm.RootMetadata
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, proposerMsig.RootMetadataPDA, config.DefaultCommitment, &newRootMetadata)
+ require.NoError(t, err, "failed to get account info")
+
+ require.Equal(t, rootValidationData.Metadata.ChainId, newRootMetadata.ChainId)
+ require.Equal(t, rootValidationData.Metadata.Multisig, newRootMetadata.Multisig)
+ require.Equal(t, rootValidationData.Metadata.PreOpCount, newRootMetadata.PreOpCount)
+ require.Equal(t, rootValidationData.Metadata.PostOpCount, newRootMetadata.PostOpCount)
+ require.Equal(t, rootValidationData.Metadata.OverridePreviousRoot, newRootMetadata.OverridePreviousRoot)
+ })
+
+ t.Run("mcm::execute to schedule timelock operations", func(t *testing.T) {
+ for _, op := range opNodes {
+ proofs, proofsErr := op.Proofs()
+ require.NoError(t, proofsErr)
+
+ ix := mcm.NewExecuteInstruction(
+ proposerMsig.PaddedName,
+ config.TestChainID,
+ op.Nonce,
+ op.Data, // this is timelock::schedule_batch ix
+ proofs,
+ proposerMsig.ConfigPDA,
+ proposerMsig.RootMetadataPDA,
+ proposerMsig.ExpiringRootAndOpCountPDA,
+ op.To,
+ proposerMsig.SignerPDA,
+ anyone.PublicKey(),
+ )
+ // append remaining accounts
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op.RemainingAccounts...)
+
+ vIx, vIxErr := ix.ValidateAndBuild()
+ require.NoError(t, vIxErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, anyone, config.DefaultCommitment)
+ }
+ })
+
+ var newOp3 TimelockOperation
+
+ t.Run("cancel and reschedule token distribution with corrected amounts", func(t *testing.T) {
+ t.Run("cancel existing distribution operation through multisig", func(t *testing.T) {
+ canceller := msigs[timelock.Canceller_Role].GetAnyMultisig()
+
+ cancelIx, err := timelock.NewCancelInstruction(
+ op3.OperationID(),
+ config.TimelockConfigPDA,
+ op3.OperationPDA(),
+ msigs[timelock.Canceller_Role].AccessController.PublicKey(),
+ canceller.SignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ // create MCM operation node for the cancel instruction
+ // NOTE: nonce is 0 since it's the first operation
+ node, err := IxToMcmTestOpNode(canceller.ConfigPDA, canceller.SignerPDA, cancelIx, uint64(0))
+ require.NoError(t, err)
+
+ cancleOpNodes := []mcmsUtils.McmOpNode{node}
+
+ // create and validate root data for the cancel operation
+ validUntil := uint32(0xffffffff)
+ rootValidationData, err := CreateMcmRootData(McmRootInput{
+ Multisig: canceller.ConfigPDA,
+ Operations: cancleOpNodes,
+ PreOpCount: uint64(0),
+ PostOpCount: uint64(1),
+ ValidUntil: validUntil,
+ OverridePreviousRoot: false,
+ })
+ require.NoError(t, err)
+
+ signatures, err := BulkSignOnMsgHash(canceller.Signers, rootValidationData.EthMsgHash)
+ require.NoError(t, err)
+
+ signaturesPDA := canceller.RootSignaturesPDA(rootValidationData.Root, validUntil)
+
+ parsedTotalSigs, err := mcmsUtils.SafeToUint8(len(signatures))
+ require.NoError(t, err)
+
+ initSigsIx, err := mcm.NewInitSignaturesInstruction(
+ canceller.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ parsedTotalSigs,
+ signaturesPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initSigsIx}, admin, config.DefaultCommitment)
+
+ appendSigsIxs, err := AppendSignaturesIxs(
+ signatures,
+ canceller.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ signaturesPDA,
+ admin.PublicKey(),
+ config.MaxAppendSignatureBatchSize,
+ )
+ require.NoError(t, err)
+ for _, ix := range appendSigsIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ finalizeSigsIx, err := mcm.NewFinalizeSignaturesInstruction(
+ canceller.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ signaturesPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{finalizeSigsIx}, admin, config.DefaultCommitment)
+
+ setRootIx, err := mcm.NewSetRootInstruction(
+ canceller.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ rootValidationData.Metadata,
+ rootValidationData.MetadataProof,
+ signaturesPDA,
+ canceller.RootMetadataPDA,
+ SeenSignedHashesAddress(canceller.PaddedName, rootValidationData.Root, validUntil),
+ canceller.ExpiringRootAndOpCountPDA,
+ canceller.ConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{setRootIx}, admin, config.DefaultCommitment)
+
+ // execute mcm operation to cancel the timelock operation
+ proofs, err := cancleOpNodes[0].Proofs()
+ require.NoError(t, err)
+
+ executeIx := mcm.NewExecuteInstruction(
+ canceller.PaddedName,
+ config.TestChainID,
+ node.Nonce,
+ node.Data,
+ proofs,
+ canceller.ConfigPDA,
+ canceller.RootMetadataPDA,
+ canceller.ExpiringRootAndOpCountPDA,
+ node.To,
+ canceller.SignerPDA,
+ anyone.PublicKey(),
+ )
+ executeIx.AccountMetaSlice = append(executeIx.AccountMetaSlice, node.RemainingAccounts...)
+
+ vIx, err := executeIx.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, anyone, config.DefaultCommitment)
+
+ utils.AssertClosedAccount(ctx, t, solanaGoClient, op3.OperationPDA(), config.DefaultCommitment)
+ })
+
+ t.Run("create new operation with corrected amounts", func(t *testing.T) {
+ // Create corrected transfer instructions with new amounts
+ ix1, err := utils.TokenTransferChecked(150*solana.LAMPORTS_PER_SOL, 9, tokenProgram, treasuryATA, mint, team1ATA, config.TimelockSignerPDA, []solana.PublicKey{})
+ require.NoError(t, err)
+ ix2, err := utils.TokenTransferChecked(150*solana.LAMPORTS_PER_SOL, 9, tokenProgram, treasuryATA, mint, team2ATA, config.TimelockSignerPDA, []solana.PublicKey{})
+ require.NoError(t, err)
+ ix3, err := utils.TokenTransferChecked(100*solana.LAMPORTS_PER_SOL, 9, tokenProgram, treasuryATA, mint, team3ATA, config.TimelockSignerPDA, []solana.PublicKey{})
+ require.NoError(t, err)
+
+ // Create new operation
+ salt, err := mcmsUtils.SimpleSalt()
+ require.NoError(t, err)
+ newOp3 = TimelockOperation{
+ Predecessor: op2.OperationID(),
+ Salt: salt,
+ Delay: uint64(1),
+ }
+
+ newOp3.AddInstruction(ix1, []solana.PublicKey{tokenProgram})
+ newOp3.AddInstruction(ix2, []solana.PublicKey{tokenProgram})
+ newOp3.AddInstruction(ix3, []solana.PublicKey{tokenProgram})
+
+ // Initialize operation account
+ initOpIx, err := timelock.NewInitializeOperationInstruction(
+ newOp3.OperationID(),
+ newOp3.Predecessor,
+ newOp3.Salt,
+ newOp3.IxsCountU32(),
+ config.TimelockConfigPDA,
+ newOp3.OperationPDA(),
+ admin.PublicKey(),
+ proposerMsig.SignerPDA,
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initOpIx}, admin, config.DefaultCommitment)
+
+ // Append instructions
+ for _, ixData := range newOp3.ToInstructionData() {
+ appendIx, appendIxErr := timelock.NewAppendInstructionsInstruction(
+ newOp3.OperationID(),
+ []timelock.InstructionData{ixData},
+ newOp3.OperationPDA(),
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, appendIxErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{appendIx}, admin, config.DefaultCommitment)
+ }
+
+ // Finalize operation
+ finalizeIx, err := timelock.NewFinalizeOperationInstruction(
+ newOp3.OperationID(),
+ newOp3.OperationPDA(),
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{finalizeIx}, admin, config.DefaultCommitment)
+
+ // Create mcm operation node for scheduling
+ scheduleIx, err := timelock.NewScheduleBatchInstruction(
+ newOp3.OperationID(),
+ newOp3.Delay,
+ config.TimelockConfigPDA,
+ newOp3.OperationPDA(),
+ msigs[timelock.Proposer_Role].AccessController.PublicKey(),
+ proposerMsig.SignerPDA,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ node, err := IxToMcmTestOpNode(proposerMsig.ConfigPDA, proposerMsig.SignerPDA, scheduleIx, uint64(currentOpCount))
+ require.NoError(t, err)
+
+ newOpNodes := []mcmsUtils.McmOpNode{node}
+
+ // Create and validate root data
+ validUntil := uint32(0xffffffff)
+ rootValidationData, err := CreateMcmRootData(McmRootInput{
+ Multisig: proposerMsig.ConfigPDA,
+ Operations: newOpNodes,
+ PreOpCount: uint64(currentOpCount),
+ PostOpCount: uint64(currentOpCount + 1),
+ ValidUntil: validUntil,
+ OverridePreviousRoot: false,
+ })
+ require.NoError(t, err)
+
+ currentOpCount++
+
+ // Sign and set root
+ signatures, err := BulkSignOnMsgHash(proposerMsig.Signers, rootValidationData.EthMsgHash)
+ require.NoError(t, err)
+
+ signaturesPDA := proposerMsig.RootSignaturesPDA(rootValidationData.Root, validUntil)
+
+ // Initialize signatures
+ parsedTotalSigs, err := mcmsUtils.SafeToUint8(len(signatures))
+ require.NoError(t, err)
+
+ initSigsIx, err := mcm.NewInitSignaturesInstruction(
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ parsedTotalSigs,
+ signaturesPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initSigsIx}, admin, config.DefaultCommitment)
+
+ // Append signatures
+ appendSigsIxs, err := AppendSignaturesIxs(
+ signatures,
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ signaturesPDA,
+ admin.PublicKey(),
+ config.MaxAppendSignatureBatchSize,
+ )
+ require.NoError(t, err)
+ for _, ix := range appendSigsIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ // Finalize signatures
+ finalizeSigsIx, err := mcm.NewFinalizeSignaturesInstruction(
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ signaturesPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{finalizeSigsIx}, admin, config.DefaultCommitment)
+
+ // Set root
+ setRootIx, err := mcm.NewSetRootInstruction(
+ proposerMsig.PaddedName,
+ rootValidationData.Root,
+ validUntil,
+ rootValidationData.Metadata,
+ rootValidationData.MetadataProof,
+ signaturesPDA,
+ proposerMsig.RootMetadataPDA,
+ SeenSignedHashesAddress(proposerMsig.PaddedName, rootValidationData.Root, validUntil),
+ proposerMsig.ExpiringRootAndOpCountPDA,
+ proposerMsig.ConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{setRootIx}, admin, config.DefaultCommitment)
+
+ // Execute mcm operation to schedule the timelock operation
+ proofs, err := newOpNodes[0].Proofs()
+ require.NoError(t, err)
+
+ executeIx := mcm.NewExecuteInstruction(
+ proposerMsig.PaddedName,
+ config.TestChainID,
+ node.Nonce,
+ node.Data,
+ proofs,
+ proposerMsig.ConfigPDA,
+ proposerMsig.RootMetadataPDA,
+ proposerMsig.ExpiringRootAndOpCountPDA,
+ node.To,
+ proposerMsig.SignerPDA,
+ anyone.PublicKey(),
+ )
+ executeIx.AccountMetaSlice = append(executeIx.AccountMetaSlice, node.RemainingAccounts...)
+
+ vIx, err := executeIx.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, anyone, config.DefaultCommitment)
+ })
+ })
+
+ t.Run("execute timelock operations", func(t *testing.T) {
+ // Wait for operations to be ready
+ err := WaitForOperationToBeReady(ctx, solanaGoClient, op1.OperationPDA(), config.DefaultCommitment)
+ require.NoError(t, err)
+
+ rErr := WaitForOperationToBeReady(ctx, solanaGoClient, op2.OperationPDA(), config.DefaultCommitment)
+ require.NoError(t, rErr)
+
+ t.Run("op2: cannot be executed before op1", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ op2.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ op2.OperationPDA(),
+ op1.OperationPDA(), // provide op1 PDA as predecessor
+ msigs[timelock.Executor_Role].AccessController.PublicKey(),
+ admin.PublicKey(),
+ )
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op2.RemainingAccounts()...)
+
+ vIx, err := ix.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient,
+ []solana.Instruction{vIx},
+ admin,
+ config.DefaultCommitment,
+ []string{"Error Code: " + timelock.MissingDependency_TimelockError.String()},
+ )
+ })
+
+ t.Run("op1: initial mint to treasury", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ op1.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ op1.OperationPDA(),
+ config.TimelockEmptyOpID,
+ msigs[timelock.Executor_Role].AccessController.PublicKey(),
+ admin.PublicKey(),
+ )
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op1.RemainingAccounts()...)
+
+ vIx, err := ix.ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient,
+ []solana.Instruction{vIx},
+ admin,
+ config.DefaultCommitment,
+ utils.AddComputeUnitLimit(1_400_000),
+ )
+ require.NotNil(t, result)
+
+ // Verify operation status
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, op1.OperationPDA(), config.DefaultCommitment, &opAccount)
+ require.NoError(t, err)
+ require.Equal(t, config.TimelockOpDoneTimestamp, opAccount.Timestamp, "Op1 should be marked as executed")
+
+ // Verify treasury balance
+ _, treasuryBalance, err := utils.TokenBalance(ctx, solanaGoClient, treasuryATA, config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 1000*int(solana.LAMPORTS_PER_SOL), treasuryBalance,
+ "Treasury should have received 1000 tokens")
+ })
+
+ t.Run("token approval to timelock signer", func(t *testing.T) {
+ // fund treasury account first
+ fundIx, err := system.NewTransferInstruction(
+ 1*solana.LAMPORTS_PER_SOL, // 1 SOL should be enough
+ admin.PublicKey(),
+ treasury.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{fundIx}, admin, config.DefaultCommitment)
+
+ // approve can't be deligated to timelock authority(security - CPI Guard)
+ approveIx, err := utils.TokenApproveChecked(
+ 600*solana.LAMPORTS_PER_SOL,
+ 9,
+ tokenProgram,
+ treasuryATA,
+ mint,
+ config.TimelockSignerPDA,
+ treasury.PublicKey(),
+ nil,
+ )
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{approveIx}, treasury, config.DefaultCommitment)
+ })
+
+ t.Run("op2: should provide the correct predecessor pda address", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ op2.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ op2.OperationPDA(),
+ op1.OperationID(), // provide op1 ID as predecessor
+ msigs[timelock.Executor_Role].AccessController.PublicKey(),
+ admin.PublicKey(),
+ )
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op2.RemainingAccounts()...)
+
+ vIx, err := ix.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient,
+ []solana.Instruction{vIx},
+ admin,
+ config.DefaultCommitment,
+ []string{"Error Code: " + timelock.InvalidInput_TimelockError.String()},
+ )
+ })
+
+ t.Run("op2: team ata creation", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ op2.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ op2.OperationPDA(),
+ op1.OperationPDA(), // provide op1 PDA as predecessor
+ msigs[timelock.Executor_Role].AccessController.PublicKey(),
+ admin.PublicKey(),
+ )
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op2.RemainingAccounts()...)
+
+ vIx, err := ix.ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient,
+ []solana.Instruction{vIx},
+ admin,
+ config.DefaultCommitment,
+ utils.AddComputeUnitLimit(1_400_000),
+ )
+ require.NotNil(t, result)
+
+ // verify operation status
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, op2.OperationPDA(), config.DefaultCommitment, &opAccount)
+ require.NoError(t, err)
+ require.Equal(t, config.TimelockOpDoneTimestamp, opAccount.Timestamp, "Op2 should be marked as executed")
+ })
+ })
+
+ t.Run("op3: team token distribution", func(t *testing.T) {
+ // Wait for delay and execute the timelock operation
+ time.Sleep(time.Duration(newOp3.Delay) * time.Second)
+
+ executeTimelockIx := timelock.NewExecuteBatchInstruction(
+ newOp3.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ newOp3.OperationPDA(),
+ op2.OperationPDA(),
+ msigs[timelock.Executor_Role].AccessController.PublicKey(),
+ admin.PublicKey(),
+ )
+ executeTimelockIx.AccountMetaSlice = append(executeTimelockIx.AccountMetaSlice, newOp3.RemainingAccounts()...)
+
+ vTimelockIx, err := executeTimelockIx.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vTimelockIx}, admin, config.DefaultCommitment)
+
+ // Verify final balances
+ _, treasuryBalance, err := utils.TokenBalance(ctx, solanaGoClient, treasuryATA, config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 600*int(solana.LAMPORTS_PER_SOL), treasuryBalance,
+ "Treasury should have 600 tokens remaining after distributions")
+
+ _, team1Balance, err := utils.TokenBalance(ctx, solanaGoClient, team1ATA, config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 150*int(solana.LAMPORTS_PER_SOL), team1Balance,
+ "Team1 should have received 150 tokens")
+
+ _, team2Balance, err := utils.TokenBalance(ctx, solanaGoClient, team2ATA, config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 150*int(solana.LAMPORTS_PER_SOL), team2Balance,
+ "Team2 should have received 150 tokens")
+
+ _, team3Balance, err := utils.TokenBalance(ctx, solanaGoClient, team3ATA, config.DefaultCommitment)
+ require.NoError(t, err)
+ require.Equal(t, 100*int(solana.LAMPORTS_PER_SOL), team3Balance,
+ "Team3 should have received 100 tokens")
+ })
+ })
+}
diff --git a/chains/solana/contracts/tests/mcms/timelock.go b/chains/solana/contracts/tests/mcms/timelock.go
new file mode 100644
index 000000000..cb9cfb219
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/timelock.go
@@ -0,0 +1,197 @@
+package contracts
+
+import (
+ "context"
+ crypto_rand "crypto/rand"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/programs/system"
+ "github.com/gagliardetto/solana-go/rpc"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/access_controller"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
+)
+
+// instruction builder for initializing access controllers
+func InitAccessControllersIxs(ctx context.Context, roleAcAccount solana.PublicKey, authority solana.PrivateKey, client *rpc.Client) ([]solana.Instruction, error) {
+ ixs := []solana.Instruction{}
+
+ rentExemption, err := client.GetMinimumBalanceForRentExemption(
+ ctx,
+ config.AccSpace,
+ config.DefaultCommitment,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to get rent exemption: %w", err)
+ }
+
+ createAccIx, err := system.NewCreateAccountInstruction(
+ rentExemption,
+ config.AccSpace,
+ config.AccessControllerProgram,
+ authority.PublicKey(),
+ roleAcAccount,
+ ).ValidateAndBuild()
+ if err != nil {
+ return nil, fmt.Errorf("failed to create account instruction: %w", err)
+ }
+ ixs = append(ixs, createAccIx)
+
+ initIx, err := access_controller.NewInitializeInstruction(
+ roleAcAccount,
+ authority.PublicKey(),
+ ).ValidateAndBuild()
+ if err != nil {
+ return nil, fmt.Errorf("failed to create initialize instruction: %w", err)
+ }
+ ixs = append(ixs, initIx)
+
+ return ixs, nil
+}
+
+// instructions builder for adding access to a role
+func TimelockBatchAddAccessIxs(ctx context.Context, roleAcAccount solana.PublicKey, role timelock.Role, addresses []solana.PublicKey, authority solana.PrivateKey, chunkSize int, client *rpc.Client) ([]solana.Instruction, error) {
+ var ac access_controller.AccessController
+ err := utils.GetAccountDataBorshInto(ctx, client, roleAcAccount, config.DefaultCommitment, &ac)
+ if err != nil {
+ return nil, fmt.Errorf("access controller for role %s is not initialized: %w", role, err)
+ }
+ ixs := []solana.Instruction{}
+ for i := 0; i < len(addresses); i += chunkSize {
+ end := i + chunkSize
+ if end > len(addresses) {
+ end = len(addresses)
+ }
+ chunk := addresses[i:end]
+ ix := timelock.NewBatchAddAccessInstruction(
+ role,
+ config.TimelockConfigPDA,
+ config.AccessControllerProgram,
+ roleAcAccount,
+ authority.PublicKey(),
+ )
+ for _, address := range chunk {
+ ix.Append(&solana.AccountMeta{
+ PublicKey: address,
+ IsSigner: false,
+ IsWritable: false,
+ })
+ }
+ vIx, err := ix.ValidateAndBuild()
+ if err != nil {
+ return nil, fmt.Errorf("failed to build instruction for role %v: %w", role, err)
+ }
+ ixs = append(ixs, vIx)
+ }
+ return ixs, nil
+}
+
+// mcm + timelock test helpers
+type RoleMultisigs struct {
+ Multisigs []mcmsUtils.Multisig
+ AccessController solana.PrivateKey
+}
+
+func (r RoleMultisigs) GetAnyMultisig() mcmsUtils.Multisig {
+ if len(r.Multisigs) == 0 {
+ panic("no multisigs to pick from")
+ }
+ maxN := big.NewInt(int64(len(r.Multisigs)))
+ n, err := crypto_rand.Int(crypto_rand.Reader, maxN)
+ if err != nil {
+ panic(err)
+ }
+ return r.Multisigs[n.Int64()]
+}
+
+func CreateRoleMultisigs(role timelock.Role, numMsigs int) RoleMultisigs {
+ msigs := make([]mcmsUtils.Multisig, numMsigs)
+ for i := 0; i < numMsigs; i++ {
+ name, _ := mcmsUtils.PadString32(fmt.Sprintf("%s_%d", role.String(), i))
+ msig := NewMcmMultisig(name)
+ // Create and set the config for each msig
+ // ┌──────┐
+ // │2-of-2│ root
+ // └──────┘
+ // ▲ ▲
+ // group1 │ │ group2
+ // ┌──────┐│ │┌──────┐
+ // │2-of-3│┘ └│1-of-2│
+ // └──────┘ └──────┘
+ // ▲ ▲ ▲ ▲ ▲
+ // │ │ │ │ │
+ // ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
+ // │A│ │B│ │C│ │D│ │E│ signers
+ // └─┘ └─┘ └─┘ └─┘ └─┘
+ signerPrivateKeys, _ := eth.GenerateEthPrivateKeys(5)
+ signerGroups := []byte{1, 1, 1, 2, 2} // A,B,C in group1; D,E in group2
+ groupQuorums := []uint8{2, 2, 1} // root: 2-of-2, group1: 2-of-3, group2: 1-of-2
+ groupParents := []uint8{0, 0, 0} // both groups under root
+
+ mcmConfig, _ := mcmsUtils.NewValidMcmConfig(
+ name,
+ signerPrivateKeys,
+ signerGroups,
+ groupQuorums,
+ groupParents,
+ false,
+ )
+
+ msig.RawConfig = *mcmConfig
+ signers, _ := eth.GetEvmSigners(signerPrivateKeys)
+ msig.Signers = signers
+
+ msigs[i] = msig
+ }
+
+ acKey, _ := solana.NewRandomPrivateKey()
+ return RoleMultisigs{
+ Multisigs: msigs,
+ AccessController: acKey,
+ }
+}
+
+func WaitForOperationToBeReady(ctx context.Context, client *rpc.Client, opPDA solana.PublicKey, commitment rpc.CommitmentType) error {
+ const maxAttempts = 20
+ const pollInterval = 500 * time.Millisecond
+ const timeBuffer = 2 * time.Second
+
+ var opAccount timelock.Operation
+ err := utils.GetAccountDataBorshInto(ctx, client, opPDA, commitment, &opAccount)
+ if err != nil {
+ return fmt.Errorf("failed to get account info: %w", err)
+ }
+
+ if opAccount.Timestamp == config.TimelockOpDoneTimestamp {
+ return nil
+ }
+
+ //nolint:gosec
+ scheduledTime := time.Unix(int64(opAccount.Timestamp), 0)
+
+ // add buffer to scheduled time to ensure blockchain has advanced enough
+ scheduledTimeWithBuffer := scheduledTime.Add(timeBuffer)
+
+ for attempts := 0; attempts < maxAttempts; attempts++ {
+ currentTime, err := utils.GetBlockTime(ctx, client, commitment)
+ if err != nil {
+ return fmt.Errorf("failed to get current block time: %w", err)
+ }
+
+ if currentTime.Time().After(scheduledTimeWithBuffer) || currentTime.Time().Equal(scheduledTimeWithBuffer) {
+ return nil
+ }
+
+ time.Sleep(pollInterval)
+ }
+
+ return fmt.Errorf("operation not ready after %d attempts (scheduled for: %v, with buffer: %v)",
+ maxAttempts, scheduledTime.UTC(), scheduledTimeWithBuffer.UTC())
+}
diff --git a/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go b/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go
new file mode 100644
index 000000000..550ffb1ca
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go
@@ -0,0 +1,345 @@
+package contracts
+
+import (
+ "strconv"
+ "testing"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/programs/system"
+ "github.com/gagliardetto/solana-go/rpc"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/access_controller"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
+)
+
+func TestTimelockBypasserExecute(t *testing.T) {
+ t.Parallel()
+ ctx := tests.Context(t)
+
+ timelock.SetProgramID(config.TimelockProgram)
+ access_controller.SetProgramID(config.AccessControllerProgram)
+
+ admin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ roles, roleMap := mcmsUtils.TestRoleAccounts(t, config.NumAccountsPerRole)
+ solanaGoClient := utils.DeployAllPrograms(t, utils.PathToAnchorConfig, admin)
+
+ allowance := struct {
+ timelockAuthority uint64
+ recipient uint64
+ }{
+ timelockAuthority: 5 * solana.LAMPORTS_PER_SOL,
+ recipient: 1 * solana.LAMPORTS_PER_SOL,
+ }
+
+ tokenProgram := solana.TokenProgramID
+ wsol := solana.WrappedSol
+ wsolDecimal := uint8(9)
+
+ adminATA, _, err := solana.FindAssociatedTokenAddress(admin.PublicKey(), wsol)
+ require.NoError(t, err)
+
+ recipient, kerr := solana.NewRandomPrivateKey()
+ require.NoError(t, kerr)
+
+ recipientATA, _, err := solana.FindAssociatedTokenAddress(recipient.PublicKey(), wsol)
+ require.NoError(t, err)
+
+ t.Run("setup:funding", func(t *testing.T) {
+ all := []solana.PrivateKey{}
+ all = append(all, admin)
+ // utils.FundAccounts(ctx, []solana.PrivateKey{admin}, client, t)
+ for _, role := range roles {
+ all = append(all, role.Accounts...)
+ }
+ utils.FundAccounts(ctx, all, solanaGoClient, t)
+ })
+
+ t.Run("setup:init access controllers", func(t *testing.T) {
+ for _, data := range roleMap {
+ initAccIxs, initAccIxsErr := InitAccessControllersIxs(ctx, data.AccessController.PublicKey(), admin, solanaGoClient)
+ require.NoError(t, initAccIxsErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, initAccIxs, admin, config.DefaultCommitment, utils.AddSigners(data.AccessController))
+
+ var ac access_controller.AccessController
+ acAccErr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, data.AccessController.PublicKey(), config.DefaultCommitment, &ac)
+ if acAccErr != nil {
+ require.NoError(t, acAccErr, "failed to get account info")
+ }
+ }
+ })
+
+ t.Run("setup:init timelock program", func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ initTimelockIx, initErr := timelock.NewInitializeInstruction(
+ config.MinDelay,
+ config.TimelockConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.TimelockProgram,
+ programData.Address,
+ config.AccessControllerProgram,
+ roleMap[timelock.Proposer_Role].AccessController.PublicKey(),
+ roleMap[timelock.Executor_Role].AccessController.PublicKey(),
+ roleMap[timelock.Canceller_Role].AccessController.PublicKey(),
+ roleMap[timelock.Bypasser_Role].AccessController.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, initErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initTimelockIx}, admin, config.DefaultCommitment)
+
+ var configAccount timelock.Config
+ cfgErr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.TimelockConfigPDA, config.DefaultCommitment, &configAccount)
+ if cfgErr != nil {
+ require.NoError(t, cfgErr, "failed to get account info")
+ }
+
+ require.Equal(t, admin.PublicKey(), configAccount.Owner, "Owner doesn't match")
+ require.Equal(t, config.MinDelay, configAccount.MinDelay, "MinDelay doesn't match")
+ require.Equal(t, roleMap[timelock.Proposer_Role].AccessController.PublicKey(), configAccount.ProposerRoleAccessController, "ProposerRoleAccessController doesn't match")
+ require.Equal(t, roleMap[timelock.Executor_Role].AccessController.PublicKey(), configAccount.ExecutorRoleAccessController, "ExecutorRoleAccessController doesn't match")
+ require.Equal(t, roleMap[timelock.Canceller_Role].AccessController.PublicKey(), configAccount.CancellerRoleAccessController, "CancellerRoleAccessController doesn't match")
+ require.Equal(t, roleMap[timelock.Bypasser_Role].AccessController.PublicKey(), configAccount.BypasserRoleAccessController, "BypasserRoleAccessController doesn't match")
+ })
+
+ t.Run("setup:register access list & verify", func(t *testing.T) {
+ for role, data := range roleMap {
+ addresses := []solana.PublicKey{}
+ for _, account := range data.Accounts {
+ addresses = append(addresses, account.PublicKey())
+ }
+ batchAddAccessIxs, batchAddAccessIxsErr := TimelockBatchAddAccessIxs(ctx, data.AccessController.PublicKey(), role, addresses, admin, config.BatchAddAccessChunkSize, solanaGoClient)
+ require.NoError(t, batchAddAccessIxsErr)
+
+ for _, ix := range batchAddAccessIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var ac access_controller.AccessController
+ acAccountErr := utils.GetAccountDataBorshInto(
+ ctx,
+ solanaGoClient,
+ data.AccessController.PublicKey(),
+ config.DefaultCommitment,
+ &ac,
+ )
+ require.NoError(t, acAccountErr)
+
+ require.Equal(t, uint64(len(data.Accounts)), ac.AccessList.Len,
+ "AccessList length mismatch for %s", data.Role)
+
+ for _, account := range data.Accounts {
+ targetPubKey := account.PublicKey()
+ _, found := mcmsUtils.FindInSortedList(ac.AccessList.Xs[:ac.AccessList.Len], targetPubKey)
+ require.True(t, found, "Account %s not found in %s AccessList",
+ targetPubKey, data.Role)
+ }
+ }
+ })
+
+ t.Run("schedule_execute: multiple wsol transfer operation", func(t *testing.T) {
+ t.Run("setup: wsol transfer operation", func(t *testing.T) {
+ requiredAmount := allowance.recipient
+
+ fundPDAIx := system.NewTransferInstruction(allowance.timelockAuthority, admin.PublicKey(), config.TimelockSignerPDA).Build()
+
+ createAdminATAIx, _, caErr := utils.CreateAssociatedTokenAccount(tokenProgram, wsol, admin.PublicKey(), admin.PublicKey())
+ require.NoError(t, caErr)
+
+ wrapSolIx, wsErr := system.NewTransferInstruction(
+ requiredAmount,
+ admin.PublicKey(),
+ adminATA,
+ ).ValidateAndBuild()
+ require.NoError(t, wsErr)
+
+ syncNativeIx, snErr := utils.SyncNative(
+ tokenProgram,
+ adminATA, // token account
+ )
+ require.NoError(t, snErr)
+
+ // approve can't be deligated to timelock authority(CPI Guard)
+ approveIx, aiErr := utils.TokenApproveChecked(
+ requiredAmount,
+ wsolDecimal,
+ tokenProgram,
+ adminATA,
+ wsol,
+ config.TimelockSignerPDA,
+ admin.PublicKey(),
+ nil,
+ )
+ require.NoError(t, aiErr)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient,
+ []solana.Instruction{createAdminATAIx, wrapSolIx, syncNativeIx, fundPDAIx, approveIx},
+ admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // check results
+ timelockAuthorityBalance, tlBalanceErr := solanaGoClient.GetBalance(
+ ctx,
+ config.TimelockSignerPDA,
+ config.DefaultCommitment,
+ )
+ require.NoError(t, tlBalanceErr)
+ require.Equal(t, allowance.timelockAuthority, timelockAuthorityBalance.Value)
+
+ adminWsolBalance, adminATABalanceErr := solanaGoClient.GetTokenAccountBalance(
+ ctx,
+ adminATA,
+ config.DefaultCommitment,
+ )
+ require.NoError(t, adminATABalanceErr)
+ require.Equal(t, strconv.Itoa(int(requiredAmount)), adminWsolBalance.Value.Amount)
+ })
+
+ t.Run("success: schedule and execute batch instructions", func(t *testing.T) {
+ salt, err := mcmsUtils.SimpleSalt()
+ require.NoError(t, err)
+ op := TimelockOperation{
+ Predecessor: config.TimelockEmptyOpID,
+ Salt: salt,
+ Delay: uint64(1000),
+ }
+
+ cIx, _, ciErr := utils.CreateAssociatedTokenAccount(
+ tokenProgram,
+ wsol,
+ recipient.PublicKey(),
+ config.TimelockSignerPDA,
+ )
+ require.NoError(t, ciErr)
+ op.AddInstruction(
+ cIx,
+ []solana.PublicKey{tokenProgram, solana.SPLAssociatedTokenAccountProgramID},
+ )
+
+ tIx, tiErr := utils.TokenTransferChecked(
+ allowance.recipient,
+ wsolDecimal,
+ tokenProgram,
+ adminATA,
+ wsol,
+ recipientATA,
+ config.TimelockSignerPDA,
+ nil,
+ )
+ require.NoError(t, tiErr)
+ op.AddInstruction(tIx, []solana.PublicKey{tokenProgram})
+
+ id := op.OperationID()
+ operationPDA := op.OperationPDA()
+
+ signer := roleMap[timelock.Proposer_Role].RandomPick()
+
+ initOpIx, err := timelock.NewInitializeOperationInstruction(
+ op.OperationID(),
+ op.Predecessor,
+ op.Salt,
+ op.IxsCountU32(),
+ config.TimelockConfigPDA,
+ op.OperationPDA(),
+ signer.PublicKey(),
+ signer.PublicKey(), // proposer - who calls the schedule_batch
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initOpIx}, signer, config.DefaultCommitment)
+
+ for _, ixData := range op.ToInstructionData() {
+ appendIxIx, aErr := timelock.NewAppendInstructionsInstruction(
+ op.OperationID(),
+ []timelock.InstructionData{ixData},
+ op.OperationPDA(),
+ signer.PublicKey(),
+ solana.SystemProgramID, // for reallocation
+ ).ValidateAndBuild()
+ require.NoError(t, aErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{appendIxIx}, signer, config.DefaultCommitment)
+ }
+
+ finIxIx, err := timelock.NewFinalizeOperationInstruction(
+ op.OperationID(),
+ op.OperationPDA(),
+ signer.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{finIxIx}, signer, config.DefaultCommitment)
+
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, operationPDA, config.DefaultCommitment, &opAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t,
+ true,
+ opAccount.IsFinalized,
+ "operation is not finalized",
+ )
+
+ t.Run("success: bypass_execute_batch", func(t *testing.T) {
+ signer := roleMap[timelock.Bypasser_Role].RandomPick()
+ ac := roleMap[timelock.Bypasser_Role].AccessController
+
+ ix := timelock.NewBypasserExecuteBatchInstruction(
+ id,
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ operationPDA,
+ ac.PublicKey(),
+ signer.PublicKey(),
+ )
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op.RemainingAccounts()...)
+
+ vIx, err := ix.ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, signer, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, operationPDA, config.DefaultCommitment, &opAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ recipientWsolBalance, err := solanaGoClient.GetTokenAccountBalance(
+ ctx,
+ recipientATA,
+ config.DefaultCommitment,
+ )
+ require.NoError(t, err)
+ require.Equal(t,
+ strconv.Itoa(int(allowance.recipient)),
+ recipientWsolBalance.Value.Amount,
+ "Recipient balance mismatch",
+ )
+ })
+ })
+ })
+}
diff --git a/chains/solana/contracts/tests/mcms/timelock_operations.go b/chains/solana/contracts/tests/mcms/timelock_operations.go
new file mode 100644
index 000000000..6a8e61119
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/timelock_operations.go
@@ -0,0 +1,160 @@
+package contracts
+
+import (
+ "bytes"
+
+ "github.com/gagliardetto/solana-go"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+)
+
+// represents a single instruction with its required accounts
+type TimelockInstruction struct {
+ Ix solana.Instruction // instruction to be scheduled / executed
+ Accounts []solana.AccountMeta // required accounts for the instruction(should be provided in execute stage)
+}
+
+// represents a batch of instructions that having atomicy to be scheduled and executed via timelock
+type TimelockOperation struct {
+ Predecessor [32]byte // hashed id of the previous operation
+ Salt [32]byte // random salt for the operation
+ Delay uint64 // delay in seconds
+ instructions []TimelockInstruction // instruction data slice, use Add method to add instructions and accounts
+}
+
+// add instruction and required accounts to operation
+func (op *TimelockOperation) AddInstruction(ix solana.Instruction, additionalPrograms []solana.PublicKey) {
+ accounts := make([]solana.AccountMeta, len(ix.Accounts()))
+ // anchor ix builder doesn't include program
+ for _, program := range additionalPrograms {
+ accounts = append(accounts, solana.AccountMeta{PublicKey: program, IsSigner: false, IsWritable: false})
+ }
+
+ for i, acc := range ix.Accounts() {
+ accounts[i] = *acc
+ // for debugging
+ // fmt.Printf("Account %d: %s (signer: %v, writable: %v)\n",
+ // i, acc.PublicKey, acc.IsSigner, acc.IsWritable)
+ }
+
+ op.instructions = append(op.instructions, TimelockInstruction{
+ Ix: ix,
+ Accounts: accounts,
+ })
+}
+
+func (op *TimelockOperation) IxsCountU32() uint32 {
+ ixsCount, err := mcmsUtils.SafeToUint32(len(op.instructions))
+ if err != nil {
+ panic(err)
+ }
+ return ixsCount
+}
+
+// convert operation to timelock instruction data slice
+func (op *TimelockOperation) ToInstructionData() []timelock.InstructionData {
+ ixs := make([]timelock.InstructionData, len(op.instructions))
+ for i, instr := range op.instructions {
+ ixData, err := convertToInstructionData(instr.Ix)
+ if err != nil {
+ panic(err)
+ }
+ ixs[i] = ixData
+ }
+ return ixs
+}
+
+// get required accounts for the operation
+// it merges the required accounts of all instructions and removes duplicates
+func (op *TimelockOperation) RemainingAccounts() []*solana.AccountMeta {
+ accountMap := make(map[string]*solana.AccountMeta)
+ for _, instr := range op.instructions {
+ for _, acc := range instr.Accounts {
+ key := acc.PublicKey.String()
+ if existing, exists := accountMap[key]; exists {
+ existing.IsWritable = existing.IsWritable || acc.IsWritable
+ } else {
+ accCopy := acc
+ // todo: maybe keep it as it is and override on chain(in case if we gonna calc root from this)
+ accCopy.IsSigner = false // force false for CPI
+ accountMap[key] = &accCopy
+ }
+ }
+ }
+
+ accounts := make([]*solana.AccountMeta, 0, len(accountMap))
+ for _, acc := range accountMap {
+ accounts = append(accounts, acc)
+ }
+ return accounts
+}
+
+// hash the operation and return operation id
+func (op *TimelockOperation) OperationID() [32]byte {
+ return hashOperation(op.ToInstructionData(), op.Predecessor, op.Salt)
+}
+
+func (op *TimelockOperation) OperationPDA() solana.PublicKey {
+ id := op.OperationID()
+ return config.TimelockOperationPDA(id)
+}
+
+// type conversion from solana instruction to timelock instruction data
+func convertToInstructionData(ix solana.Instruction) (timelock.InstructionData, error) {
+ accounts := make([]timelock.InstructionAccount, len(ix.Accounts()))
+ for i, acc := range ix.Accounts() {
+ accounts[i] = timelock.InstructionAccount{
+ Pubkey: acc.PublicKey,
+ IsSigner: acc.IsSigner,
+ IsWritable: acc.IsWritable,
+ }
+ }
+
+ data, err := ix.Data()
+ if err != nil {
+ return timelock.InstructionData{}, err
+ }
+
+ return timelock.InstructionData{
+ ProgramId: ix.ProgramID(),
+ Accounts: accounts,
+ Data: data,
+ }, nil
+}
+
+func hashOperation(instructions []timelock.InstructionData, predecessor [32]byte, salt [32]byte) [32]byte {
+ var encodedData bytes.Buffer
+
+ for _, ix := range instructions {
+ encodedData.Write(ix.ProgramId[:])
+
+ for _, acc := range ix.Accounts {
+ encodedData.Write(acc.Pubkey[:])
+ if acc.IsSigner {
+ encodedData.WriteByte(1)
+ } else {
+ encodedData.WriteByte(0)
+ }
+ if acc.IsWritable {
+ encodedData.WriteByte(1)
+ } else {
+ encodedData.WriteByte(0)
+ }
+ }
+ encodedData.Write(ix.Data)
+ }
+
+ encodedData.Write(predecessor[:])
+ encodedData.Write(salt[:])
+
+ result := eth.Keccak256(encodedData.Bytes())
+
+ var hash [32]byte
+ copy(hash[:], result)
+
+ return hash
+}
diff --git a/chains/solana/contracts/tests/mcms/timelock_rbac_test.go b/chains/solana/contracts/tests/mcms/timelock_rbac_test.go
new file mode 100644
index 000000000..146a18998
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/timelock_rbac_test.go
@@ -0,0 +1,448 @@
+package contracts
+
+import (
+ "testing"
+ "time"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/programs/system"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/access_controller"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
+)
+
+func TestTimelockRBAC(t *testing.T) {
+ t.Parallel()
+ ctx := tests.Context(t)
+
+ timelock.SetProgramID(config.TimelockProgram)
+ access_controller.SetProgramID(config.AccessControllerProgram)
+
+ admin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ anotherAdmin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ user, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ roles, roleMap := mcmsUtils.TestRoleAccounts(t, config.NumAccountsPerRole)
+ solanaGoClient := utils.DeployAllPrograms(t, utils.PathToAnchorConfig, admin)
+
+ t.Run("setup:funding", func(t *testing.T) {
+ all := []solana.PrivateKey{}
+ all = append(all, admin, anotherAdmin, user)
+ // utils.FundAccounts(ctx, []solana.PrivateKey{admin}, client, t)
+ for _, role := range roles {
+ all = append(all, role.Accounts...)
+ }
+ utils.FundAccounts(ctx, all, solanaGoClient, t)
+ })
+
+ t.Run("setup:init access controllers", func(t *testing.T) {
+ for _, data := range roleMap {
+ initAccIxs, err := InitAccessControllersIxs(ctx, data.AccessController.PublicKey(), admin, solanaGoClient)
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, initAccIxs, admin, config.DefaultCommitment, utils.AddSigners(data.AccessController))
+
+ var ac access_controller.AccessController
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, data.AccessController.PublicKey(), config.DefaultCommitment, &ac)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ }
+ })
+
+ t.Run("fail: NOT able to init program from non-deployer user", func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ initTimelockIx, err := timelock.NewInitializeInstruction(
+ config.MinDelay,
+ config.TimelockConfigPDA,
+ anotherAdmin.PublicKey(),
+ solana.SystemProgramID,
+ config.TimelockProgram,
+ programData.Address,
+ config.AccessControllerProgram,
+ roleMap[timelock.Proposer_Role].AccessController.PublicKey(),
+ roleMap[timelock.Executor_Role].AccessController.PublicKey(),
+ roleMap[timelock.Canceller_Role].AccessController.PublicKey(),
+ roleMap[timelock.Bypasser_Role].AccessController.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{initTimelockIx}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + timelock.Unauthorized_TimelockError.String()})
+ require.NotNil(t, result)
+ })
+
+ t.Run("setup:init timelock program", func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ initTimelockIx, err := timelock.NewInitializeInstruction(
+ config.MinDelay,
+ config.TimelockConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.TimelockProgram,
+ programData.Address,
+ config.AccessControllerProgram,
+ roleMap[timelock.Proposer_Role].AccessController.PublicKey(),
+ roleMap[timelock.Executor_Role].AccessController.PublicKey(),
+ roleMap[timelock.Canceller_Role].AccessController.PublicKey(),
+ roleMap[timelock.Bypasser_Role].AccessController.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initTimelockIx}, admin, config.DefaultCommitment)
+
+ var configAccount timelock.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.TimelockConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t, admin.PublicKey(), configAccount.Owner, "Owner doesn't match")
+ require.Equal(t, config.MinDelay, configAccount.MinDelay, "MinDelay doesn't match")
+ require.Equal(t, roleMap[timelock.Proposer_Role].AccessController.PublicKey(), configAccount.ProposerRoleAccessController, "ProposerRoleAccessController doesn't match")
+ require.Equal(t, roleMap[timelock.Executor_Role].AccessController.PublicKey(), configAccount.ExecutorRoleAccessController, "ExecutorRoleAccessController doesn't match")
+ require.Equal(t, roleMap[timelock.Canceller_Role].AccessController.PublicKey(), configAccount.CancellerRoleAccessController, "CancellerRoleAccessController doesn't match")
+ require.Equal(t, roleMap[timelock.Bypasser_Role].AccessController.PublicKey(), configAccount.BypasserRoleAccessController, "BypasserRoleAccessController doesn't match")
+ })
+
+ t.Run("timelock:ownership", func(t *testing.T) {
+ // Fail to transfer ownership when not owner
+ instruction, err := timelock.NewTransferOwnershipInstruction(
+ anotherAdmin.PublicKey(),
+ config.TimelockConfigPDA,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + timelock.Unauthorized_TimelockError.String()})
+ require.NotNil(t, result)
+
+ // successfully transfer ownership
+ instruction, err = timelock.NewTransferOwnershipInstruction(
+ anotherAdmin.PublicKey(),
+ config.TimelockConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // Fail to accept ownership when not proposed_owner
+ instruction, err = timelock.NewAcceptOwnershipInstruction(
+ config.TimelockConfigPDA,
+ user.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, user, config.DefaultCommitment, []string{"Error Code: " + timelock.Unauthorized_TimelockError.String()})
+ require.NotNil(t, result)
+
+ // Successfully accept ownership
+ // anotherAdmin becomes owner for remaining tests
+ instruction, err = timelock.NewAcceptOwnershipInstruction(
+ config.TimelockConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ // Current owner cannot propose self
+ instruction, err = timelock.NewTransferOwnershipInstruction(
+ anotherAdmin.PublicKey(),
+ config.TimelockConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment, []string{"Error Code: " + timelock.InvalidInput_TimelockError.String()})
+ require.NotNil(t, result)
+
+ // Validate proposed set to 0-address after accepting ownership
+ var configAccount timelock.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.TimelockConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ require.Equal(t, anotherAdmin.PublicKey(), configAccount.Owner)
+ require.Equal(t, solana.PublicKey{}, configAccount.ProposedOwner)
+
+ // get it back
+ instruction, err = timelock.NewTransferOwnershipInstruction(
+ admin.PublicKey(),
+ config.TimelockConfigPDA,
+ anotherAdmin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, anotherAdmin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ instruction, err = timelock.NewAcceptOwnershipInstruction(
+ config.TimelockConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ result = utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.TimelockConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t, admin.PublicKey(), configAccount.Owner)
+ require.Equal(t, solana.PublicKey{}, configAccount.ProposedOwner)
+ })
+
+ t.Run("setup:register access list & verify", func(t *testing.T) {
+ for role, data := range roleMap {
+ addresses := []solana.PublicKey{}
+ for _, account := range data.Accounts {
+ addresses = append(addresses, account.PublicKey())
+ }
+ batchAddAccessIxs, err := TimelockBatchAddAccessIxs(ctx, data.AccessController.PublicKey(), role, addresses, admin, config.BatchAddAccessChunkSize, solanaGoClient)
+ require.NoError(t, err)
+
+ for _, ix := range batchAddAccessIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var ac access_controller.AccessController
+ err = utils.GetAccountDataBorshInto(
+ ctx,
+ solanaGoClient,
+ data.AccessController.PublicKey(),
+ config.DefaultCommitment,
+ &ac,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, uint64(len(data.Accounts)), ac.AccessList.Len,
+ "AccessList length mismatch for %s", data.Role)
+
+ for _, account := range data.Accounts {
+ targetPubKey := account.PublicKey()
+ _, found := mcmsUtils.FindInSortedList(ac.AccessList.Xs[:ac.AccessList.Len], targetPubKey)
+ require.True(t, found, "Account %s not found in %s AccessList",
+ targetPubKey, data.Role)
+ }
+ }
+ })
+
+ t.Run("rbac: schedule and cancel a timelock operation", func(t *testing.T) {
+ salt, err := mcmsUtils.SimpleSalt()
+ require.NoError(t, err)
+ nonExecutableOp := TimelockOperation{
+ Predecessor: config.TimelockEmptyOpID,
+ Salt: salt,
+ Delay: uint64(1),
+ }
+
+ ix := system.NewTransferInstruction(1*solana.LAMPORTS_PER_SOL, admin.PublicKey(), config.TimelockSignerPDA).Build()
+ nonExecutableOp.AddInstruction(ix, []solana.PublicKey{})
+
+ id := nonExecutableOp.OperationID()
+ operationPDA := nonExecutableOp.OperationPDA()
+
+ t.Run("rbac: Should able to schedule tx with proposer role", func(t *testing.T) {
+ signer := roleMap[timelock.Proposer_Role].RandomPick()
+ ac := roleMap[timelock.Proposer_Role].AccessController
+
+ ixs := make([]solana.Instruction, 0)
+ initOpIx, err := timelock.NewInitializeOperationInstruction(
+ nonExecutableOp.OperationID(),
+ nonExecutableOp.Predecessor,
+ nonExecutableOp.Salt,
+ uint32(len(nonExecutableOp.instructions)),
+ config.TimelockConfigPDA,
+ operationPDA,
+ signer.PublicKey(),
+ signer.PublicKey(), // proposer - direct schedule batch here
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ ixs = append(ixs, initOpIx)
+
+ appendIxIx, err := timelock.NewAppendInstructionsInstruction(
+ nonExecutableOp.OperationID(),
+ nonExecutableOp.ToInstructionData(),
+ operationPDA,
+ signer.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ ixs = append(ixs, appendIxIx)
+
+ finIxIx, err := timelock.NewFinalizeOperationInstruction(
+ nonExecutableOp.OperationID(),
+ operationPDA,
+ signer.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+ ixs = append(ixs, finIxIx)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, ixs, signer, config.DefaultCommitment)
+
+ ix, err := timelock.NewScheduleBatchInstruction(
+ nonExecutableOp.OperationID(),
+ nonExecutableOp.Delay,
+ config.TimelockConfigPDA,
+ operationPDA,
+ ac.PublicKey(),
+ signer.PublicKey(),
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, signer, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, operationPDA, config.DefaultCommitment, &opAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t,
+ result.BlockTime.Time().Add(time.Duration(nonExecutableOp.Delay)*time.Second).Unix(),
+ int64(opAccount.Timestamp),
+ "Scheduled Times don't match",
+ )
+ require.Equal(t,
+ id,
+ opAccount.Id,
+ "Ids don't match",
+ )
+ })
+
+ t.Run("rbac: cancel scheduled tx", func(t *testing.T) {
+ t.Run("fail: should feed the right role access controller", func(t *testing.T) {
+ signer := roleMap[timelock.Canceller_Role].RandomPick()
+ ac := roleMap[timelock.Proposer_Role].AccessController
+
+ ix, err := timelock.NewCancelInstruction(
+ id,
+ config.TimelockConfigPDA,
+ operationPDA,
+ ac.PublicKey(),
+ signer.PublicKey(),
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, signer, config.DefaultCommitment, []string{"Error Code: " + "InvalidAccessController."})
+ require.NotNil(t, result)
+ })
+
+ t.Run("fail: unauthorized on cancel attempt from non-canceller(proposer)", func(t *testing.T) {
+ signer := roleMap[timelock.Proposer_Role].RandomPick()
+ ac := roleMap[timelock.Canceller_Role].AccessController
+
+ ix, err := timelock.NewCancelInstruction(
+ id,
+ config.TimelockConfigPDA,
+ operationPDA,
+ ac.PublicKey(),
+ signer.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, signer, config.DefaultCommitment, []string{"Error Code: " + timelock.Unauthorized_TimelockError.String()})
+ require.NotNil(t, result)
+ })
+
+ t.Run("success: Should able to cancel scheduled tx: PDA closed", func(t *testing.T) {
+ signer := roleMap[timelock.Canceller_Role].RandomPick()
+ ac := roleMap[timelock.Canceller_Role].AccessController
+
+ ix, err := timelock.NewCancelInstruction(
+ id,
+
+ config.TimelockConfigPDA,
+ operationPDA,
+
+ ac.PublicKey(),
+ signer.PublicKey(),
+ ).ValidateAndBuild()
+
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, signer, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ utils.AssertClosedAccount(ctx, t, solanaGoClient, operationPDA, config.DefaultCommitment)
+ })
+ })
+ })
+
+ t.Run("rbac: only_admin authentication ", func(t *testing.T) {
+ newMinDelay := uint64(14000)
+
+ t.Run("fail: only admin can call functions with only_admin macro", func(t *testing.T) {
+ signer := roleMap[timelock.Proposer_Role].RandomPick()
+
+ ix, err := timelock.NewUpdateDelayInstruction(
+ newMinDelay,
+ config.TimelockConfigPDA,
+ signer.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, signer, config.DefaultCommitment, []string{"Error Code: " + timelock.Unauthorized_TimelockError.String()})
+ require.NotNil(t, result)
+ })
+
+ t.Run("success: only admin can call functions with only_admin macro", func(t *testing.T) {
+ signer := admin
+
+ ix, err := timelock.NewUpdateDelayInstruction(
+ newMinDelay,
+ config.TimelockConfigPDA,
+ signer.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, signer, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var configAccount timelock.Config
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.TimelockConfigPDA, config.DefaultCommitment, &configAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+ require.Equal(t, newMinDelay, configAccount.MinDelay, "MinDelay is not updated")
+ })
+ })
+}
diff --git a/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go b/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go
new file mode 100644
index 000000000..f8b04923c
--- /dev/null
+++ b/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go
@@ -0,0 +1,560 @@
+package contracts
+
+import (
+ "strconv"
+ "testing"
+ "time"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/programs/system"
+ "github.com/gagliardetto/solana-go/rpc"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils"
+ mcmsUtils "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/mcms"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/access_controller"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
+)
+
+func TestTimelockScheduleAndExecute(t *testing.T) {
+ t.Parallel()
+ ctx := tests.Context(t)
+
+ timelock.SetProgramID(config.TimelockProgram)
+ access_controller.SetProgramID(config.AccessControllerProgram)
+
+ admin, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ roles, roleMap := mcmsUtils.TestRoleAccounts(t, config.NumAccountsPerRole)
+ solanaGoClient := utils.DeployAllPrograms(t, utils.PathToAnchorConfig, admin)
+ allowance := struct {
+ timelockAuthority uint64
+ recipient uint64
+ }{
+ timelockAuthority: 5 * solana.LAMPORTS_PER_SOL,
+ recipient: 1 * solana.LAMPORTS_PER_SOL,
+ }
+
+ tokenProgram := solana.TokenProgramID
+ wsol := solana.WrappedSol
+ wsolDecimal := uint8(9)
+
+ adminATA, _, err := solana.FindAssociatedTokenAddress(admin.PublicKey(), wsol)
+ require.NoError(t, err)
+
+ recipient, kerr := solana.NewRandomPrivateKey()
+ require.NoError(t, kerr)
+
+ recipientATA, _, err := solana.FindAssociatedTokenAddress(recipient.PublicKey(), wsol)
+ require.NoError(t, err)
+
+ t.Run("setup:funding", func(t *testing.T) {
+ all := []solana.PrivateKey{}
+ all = append(all, admin)
+ // utils.FundAccounts(ctx, []solana.PrivateKey{admin}, client, t)
+ for _, role := range roles {
+ all = append(all, role.Accounts...)
+ }
+ utils.FundAccounts(ctx, all, solanaGoClient, t)
+ })
+
+ t.Run("setup:init access controllers", func(t *testing.T) {
+ for _, data := range roleMap {
+ initAccIxs, initAccIxsErr := InitAccessControllersIxs(ctx, data.AccessController.PublicKey(), admin, solanaGoClient)
+ require.NoError(t, initAccIxsErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, initAccIxs, admin, config.DefaultCommitment, utils.AddSigners(data.AccessController))
+
+ var ac access_controller.AccessController
+ acAccErr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, data.AccessController.PublicKey(), config.DefaultCommitment, &ac)
+ if acAccErr != nil {
+ require.NoError(t, acAccErr, "failed to get account info")
+ }
+ }
+ })
+ t.Run("setup:init timelock program", func(t *testing.T) {
+ // get program data account
+ data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockProgram, &rpc.GetAccountInfoOpts{
+ Commitment: config.DefaultCommitment,
+ })
+ require.NoError(t, accErr)
+
+ // decode program data
+ var programData struct {
+ DataType uint32
+ Address solana.PublicKey
+ }
+ require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes()))
+
+ initTimelockIx, initErr := timelock.NewInitializeInstruction(
+ config.MinDelay,
+ config.TimelockConfigPDA,
+ admin.PublicKey(),
+ solana.SystemProgramID,
+ config.TimelockProgram,
+ programData.Address,
+ config.AccessControllerProgram,
+ roleMap[timelock.Proposer_Role].AccessController.PublicKey(),
+ roleMap[timelock.Executor_Role].AccessController.PublicKey(),
+ roleMap[timelock.Canceller_Role].AccessController.PublicKey(),
+ roleMap[timelock.Bypasser_Role].AccessController.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, initErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initTimelockIx}, admin, config.DefaultCommitment)
+
+ var configAccount timelock.Config
+ cfgErr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.TimelockConfigPDA, config.DefaultCommitment, &configAccount)
+ if cfgErr != nil {
+ require.NoError(t, cfgErr, "failed to get account info")
+ }
+
+ require.Equal(t, admin.PublicKey(), configAccount.Owner, "Owner doesn't match")
+ require.Equal(t, config.MinDelay, configAccount.MinDelay, "MinDelay doesn't match")
+ require.Equal(t, roleMap[timelock.Proposer_Role].AccessController.PublicKey(), configAccount.ProposerRoleAccessController, "ProposerRoleAccessController doesn't match")
+ require.Equal(t, roleMap[timelock.Executor_Role].AccessController.PublicKey(), configAccount.ExecutorRoleAccessController, "ExecutorRoleAccessController doesn't match")
+ require.Equal(t, roleMap[timelock.Canceller_Role].AccessController.PublicKey(), configAccount.CancellerRoleAccessController, "CancellerRoleAccessController doesn't match")
+ require.Equal(t, roleMap[timelock.Bypasser_Role].AccessController.PublicKey(), configAccount.BypasserRoleAccessController, "BypasserRoleAccessController doesn't match")
+ })
+
+ t.Run("setup:register access list & verify", func(t *testing.T) {
+ for role, data := range roleMap {
+ addresses := []solana.PublicKey{}
+ for _, account := range data.Accounts {
+ addresses = append(addresses, account.PublicKey())
+ }
+ batchAddAccessIxs, batchAddAccessIxsErr := TimelockBatchAddAccessIxs(ctx, data.AccessController.PublicKey(), role, addresses, admin, config.BatchAddAccessChunkSize, solanaGoClient)
+ require.NoError(t, batchAddAccessIxsErr)
+
+ for _, ix := range batchAddAccessIxs {
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ }
+
+ var ac access_controller.AccessController
+ acAccErr := utils.GetAccountDataBorshInto(
+ ctx,
+ solanaGoClient,
+ data.AccessController.PublicKey(),
+ config.DefaultCommitment,
+ &ac,
+ )
+ require.NoError(t, acAccErr)
+
+ require.Equal(t, uint64(len(data.Accounts)), ac.AccessList.Len,
+ "AccessList length mismatch for %s", data.Role)
+
+ for _, account := range data.Accounts {
+ targetPubKey := account.PublicKey()
+ _, found := mcmsUtils.FindInSortedList(ac.AccessList.Xs[:ac.AccessList.Len], targetPubKey)
+ require.True(t, found, "Account %s not found in %s AccessList",
+ targetPubKey, data.Role)
+ }
+ }
+ })
+
+ t.Run("schedule_execute: multiple wsol transfer operation", func(t *testing.T) {
+ t.Run("setup: update delay for testing", func(t *testing.T) {
+ newMinDelay := uint64(1)
+
+ ix, updateDelayIxErr := timelock.NewUpdateDelayInstruction(
+ newMinDelay,
+ config.TimelockConfigPDA,
+ admin.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, updateDelayIxErr)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var configAccount timelock.Config
+ getConfigAccountErr := utils.GetAccountDataBorshInto(ctx, solanaGoClient, config.TimelockConfigPDA, config.DefaultCommitment, &configAccount)
+ require.NoError(t, getConfigAccountErr, "failed to get account info")
+
+ require.Equal(t, newMinDelay, configAccount.MinDelay, "MinDelay is not updated")
+ })
+
+ t.Run("setup: wsol transfer operation", func(t *testing.T) {
+ requiredAmount := allowance.recipient
+
+ fundPDAIx := system.NewTransferInstruction(allowance.timelockAuthority, admin.PublicKey(), config.TimelockSignerPDA).Build()
+
+ createAdminATAIx, _, caErr := utils.CreateAssociatedTokenAccount(tokenProgram, wsol, admin.PublicKey(), admin.PublicKey())
+ require.NoError(t, caErr)
+
+ wrapSolIx := system.NewTransferInstruction(
+ requiredAmount,
+ admin.PublicKey(),
+ adminATA,
+ ).Build()
+
+ syncNativeIx, snErr := utils.SyncNative(
+ tokenProgram,
+ adminATA, // token account
+ )
+ require.NoError(t, snErr)
+
+ // approve can't be deligated to timelock authority(CPI Guard)
+ approveIx, aiErr := utils.TokenApproveChecked(
+ requiredAmount*2, // double the requiredAmount for op2 + op3(op2 will be executed only)
+ wsolDecimal,
+ tokenProgram,
+ adminATA,
+ wsol,
+ config.TimelockSignerPDA,
+ admin.PublicKey(),
+ nil,
+ )
+ require.NoError(t, aiErr)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient,
+ []solana.Instruction{createAdminATAIx, wrapSolIx, syncNativeIx, fundPDAIx, approveIx},
+ admin, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ timelockAuthorityBalance, tlBalanceErr := solanaGoClient.GetBalance(
+ ctx,
+ config.TimelockSignerPDA,
+ config.DefaultCommitment,
+ )
+ require.NoError(t, tlBalanceErr)
+ require.Equal(t, allowance.timelockAuthority, timelockAuthorityBalance.Value)
+
+ adminWsolBalance, adminATABalanceErr := solanaGoClient.GetTokenAccountBalance(
+ ctx,
+ adminATA,
+ config.DefaultCommitment,
+ )
+ require.NoError(t, adminATABalanceErr)
+ require.Equal(t, strconv.Itoa(int(requiredAmount)), adminWsolBalance.Value.Amount)
+ })
+
+ t.Run("success: schedule and execute operations", func(t *testing.T) {
+ salt1, err := mcmsUtils.SimpleSalt()
+ require.NoError(t, err)
+ op1 := TimelockOperation{
+ Predecessor: config.TimelockEmptyOpID,
+ Salt: salt1,
+ Delay: 2,
+ }
+ cIx, _, ciErr := utils.CreateAssociatedTokenAccount(
+ tokenProgram,
+ wsol,
+ recipient.PublicKey(),
+ config.TimelockSignerPDA,
+ )
+ require.NoError(t, ciErr)
+ op1.AddInstruction(cIx, []solana.PublicKey{solana.TokenProgramID, solana.SPLAssociatedTokenAccountProgramID})
+
+ salt2, err := mcmsUtils.SimpleSalt()
+ require.NoError(t, err)
+ op2 := TimelockOperation{
+ Predecessor: op1.OperationID(),
+ Salt: salt2,
+ Delay: 2,
+ }
+
+ tIx, tiErr := utils.TokenTransferChecked(
+ allowance.recipient,
+ wsolDecimal,
+ tokenProgram,
+ adminATA,
+ wsol,
+ recipientATA,
+ config.TimelockSignerPDA,
+ nil,
+ )
+ require.NoError(t, tiErr)
+ op2.AddInstruction(tIx, []solana.PublicKey{tokenProgram})
+
+ salt3, err := mcmsUtils.SimpleSalt()
+ require.NoError(t, err)
+ op3 := TimelockOperation{
+ Predecessor: op1.OperationID(),
+ Salt: salt3,
+ Delay: 60,
+ }
+
+ anotherTransferIx, atErr := utils.TokenTransferChecked(
+ allowance.recipient,
+ wsolDecimal,
+ tokenProgram,
+ adminATA,
+ wsol,
+ recipientATA,
+ config.TimelockSignerPDA,
+ nil,
+ )
+ require.NoError(t, atErr)
+ op3.AddInstruction(anotherTransferIx, []solana.PublicKey{tokenProgram})
+
+ t.Run("schedule", func(t *testing.T) {
+ proposer := roleMap[timelock.Proposer_Role].RandomPick()
+ proposerAccessController := roleMap[timelock.Proposer_Role].AccessController.PublicKey()
+
+ executor := roleMap[timelock.Executor_Role].RandomPick()
+ executorAccessController := roleMap[timelock.Executor_Role].AccessController.PublicKey()
+
+ t.Run("success: schedule all operations", func(t *testing.T) {
+ for _, op := range []TimelockOperation{op1, op2, op3} {
+ id := op.OperationID()
+ operationPDA1 := op.OperationPDA()
+
+ initOpIx, iErr := timelock.NewInitializeOperationInstruction(
+ op.OperationID(),
+ op.Predecessor,
+ op.Salt,
+ op.IxsCountU32(),
+ config.TimelockConfigPDA,
+ op.OperationPDA(),
+ proposer.PublicKey(),
+ proposer.PublicKey(),
+ solana.SystemProgramID,
+ ).ValidateAndBuild()
+ require.NoError(t, iErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initOpIx}, proposer, config.DefaultCommitment)
+
+ for _, ixData := range op.ToInstructionData() {
+ appendIxsIx, aErr := timelock.NewAppendInstructionsInstruction(
+ op.OperationID(),
+ []timelock.InstructionData{ixData},
+ op.OperationPDA(),
+ proposer.PublicKey(),
+ solana.SystemProgramID, // for reallocation
+ ).ValidateAndBuild()
+ require.NoError(t, aErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{appendIxsIx}, proposer, config.DefaultCommitment)
+ }
+
+ t.Run("clear & reappend op instructions", func(t *testing.T) {
+ // clear instructions so that we can reinitialize the operation
+ clearIx, ciErr := timelock.NewClearOperationInstruction(op.OperationID(), op.OperationPDA(), proposer.PublicKey()).ValidateAndBuild()
+ require.NoError(t, ciErr)
+
+ // send clear and check if it's closed
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{clearIx}, proposer, config.DefaultCommitment)
+ utils.AssertClosedAccount(ctx, t, solanaGoClient, op.OperationPDA(), config.DefaultCommitment)
+
+ // reinitialize operation
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initOpIx}, proposer, config.DefaultCommitment)
+
+ // reappend instructions
+ for _, ixData := range op.ToInstructionData() {
+ appendIxsIx, aErr := timelock.NewAppendInstructionsInstruction(
+ op.OperationID(),
+ []timelock.InstructionData{ixData},
+ op.OperationPDA(),
+ proposer.PublicKey(),
+ solana.SystemProgramID, // for reallocation
+ ).ValidateAndBuild()
+ require.NoError(t, aErr)
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{appendIxsIx}, proposer, config.DefaultCommitment)
+ }
+ })
+
+ finIxIx, fErr := timelock.NewFinalizeOperationInstruction(
+ op.OperationID(),
+ op.OperationPDA(),
+ proposer.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, fErr)
+
+ utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{finIxIx}, proposer, config.DefaultCommitment)
+
+ ix, ixVErr := timelock.NewScheduleBatchInstruction(
+ id,
+ op.Delay,
+ config.TimelockConfigPDA,
+ operationPDA1,
+ proposerAccessController,
+ proposer.PublicKey(),
+ ).ValidateAndBuild()
+ require.NoError(t, ixVErr)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, proposer, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, operationPDA1, config.DefaultCommitment, &opAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t,
+ result.BlockTime.Time().Add(time.Duration(op.Delay)*time.Second).Unix(),
+ int64(opAccount.Timestamp),
+ "Scheduled Times don't match",
+ )
+
+ require.Equal(t,
+ id,
+ opAccount.Id,
+ "Ids don't match",
+ )
+ }
+ })
+
+ t.Run("wait for operation 1 to be ready", func(t *testing.T) {
+ // Wait for operations to be ready
+ err := WaitForOperationToBeReady(ctx, solanaGoClient, op1.OperationPDA(), config.DefaultCommitment)
+ require.NoError(t, err)
+ })
+
+ t.Run("fail: OperationAlreadyExists", func(t *testing.T) {
+ ix := timelock.NewScheduleBatchInstruction(
+ op1.OperationID(),
+ op1.Delay,
+ config.TimelockConfigPDA,
+ op1.OperationPDA(),
+ proposerAccessController,
+ proposer.PublicKey(),
+ ).Build()
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{ix}, proposer, config.DefaultCommitment, []string{"Error Code: " + timelock.OperationAlreadyScheduled_TimelockError.String()})
+ })
+
+ t.Run("wait for operation 2 to be ready", func(t *testing.T) {
+ // Wait for operations to be ready
+ err := WaitForOperationToBeReady(ctx, solanaGoClient, op1.OperationPDA(), config.DefaultCommitment)
+ require.NoError(t, err)
+ })
+
+ t.Run("fail: should provide the right dependency pda", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ op2.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ op2.OperationPDA(),
+ op2.OperationPDA(), // wrong dependency
+ executorAccessController,
+ executor.PublicKey(),
+ )
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op2.RemainingAccounts()...)
+
+ vIx, err := ix.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{vIx}, executor, config.DefaultCommitment, []string{"Error Code: " + timelock.InvalidInput_TimelockError.String()})
+ })
+
+ t.Run("fail: not able to execute op2 before dependency(op1) execution", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ op2.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ op2.OperationPDA(),
+ op1.OperationPDA(), // not executed yet
+ executorAccessController,
+ executor.PublicKey(),
+ )
+
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op2.RemainingAccounts()...)
+
+ vIx, err := ix.ValidateAndBuild()
+ require.NoError(t, err)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{vIx}, executor, config.DefaultCommitment, []string{"Error Code: " + timelock.MissingDependency_TimelockError.String()})
+ })
+
+ t.Run("success: op1 executed", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ op1.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ op1.OperationPDA(),
+ config.TimelockEmptyOpID,
+ executorAccessController,
+ executor.PublicKey(),
+ )
+
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op1.RemainingAccounts()...)
+
+ vIx, err := ix.ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, executor, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, op1.OperationPDA(), config.DefaultCommitment, &opAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t,
+ config.TimelockOpDoneTimestamp,
+ opAccount.Timestamp,
+ "Executed operation's time should be 1(DONE_TIMESTAMP)",
+ )
+ })
+
+ t.Run("success: op2 executed", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ op2.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ op2.OperationPDA(),
+ op1.OperationPDA(),
+ executorAccessController,
+ executor.PublicKey(),
+ )
+
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op2.RemainingAccounts()...)
+
+ vIx, err := ix.ValidateAndBuild()
+ require.NoError(t, err)
+
+ result := utils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{vIx}, executor, config.DefaultCommitment)
+ require.NotNil(t, result)
+
+ var opAccount timelock.Operation
+ err = utils.GetAccountDataBorshInto(ctx, solanaGoClient, op1.OperationPDA(), config.DefaultCommitment, &opAccount)
+ if err != nil {
+ require.NoError(t, err, "failed to get account info")
+ }
+
+ require.Equal(t,
+ config.TimelockOpDoneTimestamp,
+ opAccount.Timestamp,
+ "Executed operation's time should be 1(DONE_TIMESTAMP)",
+ )
+
+ recipientWsolBalance, err := solanaGoClient.GetTokenAccountBalance(
+ ctx,
+ recipientATA,
+ config.DefaultCommitment,
+ )
+ require.NoError(t, err)
+ require.Equal(t,
+ strconv.Itoa(int(allowance.recipient)),
+ recipientWsolBalance.Value.Amount,
+ "Recipient balance mismatch",
+ )
+ })
+
+ t.Run("failure on execution try: op3 is not ready", func(t *testing.T) {
+ ix := timelock.NewExecuteBatchInstruction(
+ op3.OperationID(),
+ config.TimelockConfigPDA,
+ config.TimelockSignerPDA,
+ op3.OperationPDA(),
+ config.TimelockEmptyOpID,
+ executorAccessController,
+ executor.PublicKey(),
+ )
+ ix.AccountMetaSlice = append(ix.AccountMetaSlice, op3.RemainingAccounts()...)
+
+ vIx, vIxErr := ix.ValidateAndBuild()
+ require.NoError(t, vIxErr)
+
+ utils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{vIx}, executor, config.DefaultCommitment, []string{"Error Code: " + timelock.OperationNotReady_TimelockError.String()})
+ })
+ })
+ })
+ })
+}
diff --git a/chains/solana/contracts/tests/utils/anchor.go b/chains/solana/contracts/tests/utils/anchor.go
new file mode 100644
index 000000000..30a657a1a
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/anchor.go
@@ -0,0 +1,318 @@
+package utils
+
+import (
+ "bytes"
+ "context"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/binary"
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/rpc"
+
+ "github.com/pelletier/go-toml/v2"
+
+ "github.com/stretchr/testify/require"
+)
+
+var PathToAnchorConfig = filepath.Join(ProjectRoot, "Anchor.toml")
+
+var ZeroAddress = [32]byte{}
+
+func MakeRandom32ByteArray() [32]byte {
+ a := make([]byte, 32)
+ if _, err := rand.Read(a); err != nil {
+ panic(err) // should never panic but check in case
+ }
+ return [32]byte(a)
+}
+
+func Uint64ToLE(chain uint64) []byte {
+ chainLE := make([]byte, 8)
+ binary.LittleEndian.PutUint64(chainLE, chain)
+ return chainLE
+}
+
+func To28BytesLE(value uint64) [28]byte {
+ le := make([]byte, 28)
+ binary.LittleEndian.PutUint64(le, value)
+ return [28]byte(le)
+}
+
+func Map[T, V any](ts []T, fn func(T) V) []V {
+ result := make([]V, len(ts))
+ for i, t := range ts {
+ result[i] = fn(t)
+ }
+ return result
+}
+
+func Discriminator(namespace, name string) []byte {
+ h := sha256.New()
+ h.Write([]byte(fmt.Sprintf("%s:%s", namespace, name)))
+ return h.Sum(nil)[:8]
+}
+
+func DeployAllPrograms(t *testing.T, pathToAnchorConfig string, admin solana.PrivateKey) *rpc.Client {
+ return rpc.New(SetupTestValidatorWithAnchorPrograms(t, pathToAnchorConfig, admin.PublicKey().String()))
+}
+
+func FundAccounts(ctx context.Context, accounts []solana.PrivateKey, solanaGoClient *rpc.Client, t *testing.T) {
+ sigs := []solana.Signature{}
+ for _, v := range accounts {
+ sig, err := solanaGoClient.RequestAirdrop(ctx, v.PublicKey(), 1000*solana.LAMPORTS_PER_SOL, rpc.CommitmentFinalized)
+ require.NoError(t, err)
+ sigs = append(sigs, sig)
+ }
+
+ // wait for confirmation so later transactions don't fail
+ remaining := len(sigs)
+ count := 0
+ for remaining > 0 {
+ count++
+ statusRes, sigErr := solanaGoClient.GetSignatureStatuses(ctx, true, sigs...)
+ require.NoError(t, sigErr)
+ require.NotNil(t, statusRes)
+ require.NotNil(t, statusRes.Value)
+
+ unconfirmedTxCount := 0
+ for _, res := range statusRes.Value {
+ if res == nil || res.ConfirmationStatus == rpc.ConfirmationStatusProcessed || res.ConfirmationStatus == rpc.ConfirmationStatusConfirmed {
+ unconfirmedTxCount++
+ }
+ }
+ remaining = unconfirmedTxCount
+
+ time.Sleep(500 * time.Millisecond)
+ if count > 60 {
+ require.NoError(t, fmt.Errorf("unable to find transaction within timeout"))
+ }
+ }
+}
+
+func SetupTestValidatorWithAnchorPrograms(t *testing.T, pathToAnchorConfig string, upgradeAuthority string) string {
+ anchorData := struct {
+ Programs struct {
+ Localnet map[string]string
+ }
+ }{}
+
+ // upload programs to validator
+ anchorBytes, err := os.ReadFile(pathToAnchorConfig)
+ require.NoError(t, err)
+ require.NoError(t, toml.Unmarshal(anchorBytes, &anchorData))
+
+ flags := []string{}
+ for k, v := range anchorData.Programs.Localnet {
+ flags = append(flags, "--upgradeable-program", v, filepath.Join(ContractsDir, k+".so"), upgradeAuthority)
+ }
+ url, _ := SetupLocalSolNodeWithFlags(t, flags...)
+ return url
+}
+
+func IsEvent(event string, data []byte) bool {
+ if len(data) < 8 {
+ return false
+ }
+ d := Discriminator("event", event)
+ return bytes.Equal(d, data[:8])
+}
+
+func ParseEvent(logs []string, event string, obj interface{}, shouldPrint ...bool) error {
+ for _, v := range logs {
+ if strings.Contains(v, "Program data:") {
+ encodedData := strings.TrimSpace(strings.TrimPrefix(v, "Program data:"))
+ data, err := base64.StdEncoding.DecodeString(encodedData)
+ if err != nil {
+ return err
+ }
+ if IsEvent(event, data) {
+ if err := bin.UnmarshalBorsh(obj, data); err != nil {
+ return err
+ }
+
+ if len(shouldPrint) > 0 && shouldPrint[0] {
+ fmt.Printf("%s: %+v\n", event, obj)
+ }
+ return nil
+ }
+ }
+ }
+ return fmt.Errorf("%s: event not found", event)
+}
+
+func ParseMultipleEvents[T any](logs []string, event string, shouldPrint bool) ([]T, error) {
+ var results []T
+ for _, v := range logs {
+ if strings.Contains(v, "Program data:") {
+ encodedData := strings.TrimSpace(strings.TrimPrefix(v, "Program data:"))
+ data, err := base64.StdEncoding.DecodeString(encodedData)
+ if err != nil {
+ return nil, err
+ }
+ if IsEvent(event, data) {
+ var obj T
+ if err := bin.UnmarshalBorsh(&obj, data); err != nil {
+ return nil, err
+ }
+
+ if shouldPrint {
+ fmt.Printf("%s: %+v\n", event, obj)
+ }
+
+ results = append(results, obj)
+ }
+ }
+ }
+ if len(results) == 0 {
+ return nil, fmt.Errorf("%s: event not found", event)
+ }
+
+ return results, nil
+}
+
+type AnchorInstruction struct {
+ Name string
+ ProgramID string
+ Logs []string
+ ComputeUnits int
+ InnerCalls []*AnchorInstruction
+}
+
+// Parses the log messages from an Anchor program and returns a list of AnchorInstructions.
+func ParseLogMessages(logMessages []string) []*AnchorInstruction {
+ var instructions []*AnchorInstruction
+ var stack []*AnchorInstruction
+ var currentInstruction *AnchorInstruction
+
+ programInvokeRegex := regexp.MustCompile(`Program (\w+) invoke`)
+ programSuccessRegex := regexp.MustCompile(`Program (\w+) success`)
+ computeUnitsRegex := regexp.MustCompile(`Program (\w+) consumed (\d+) of \d+ compute units`)
+
+ for _, line := range logMessages {
+ line = strings.TrimSpace(line)
+
+ // Program invocation - push to stack
+ if match := programInvokeRegex.FindStringSubmatch(line); len(match) > 1 {
+ newInstruction := &AnchorInstruction{
+ ProgramID: match[1],
+ Name: "",
+ Logs: []string{},
+ ComputeUnits: 0,
+ InnerCalls: []*AnchorInstruction{},
+ }
+
+ if len(stack) == 0 {
+ instructions = append(instructions, newInstruction)
+ } else {
+ stack[len(stack)-1].InnerCalls = append(stack[len(stack)-1].InnerCalls, newInstruction)
+ }
+
+ stack = append(stack, newInstruction)
+ currentInstruction = newInstruction
+ continue
+ }
+
+ // Program success - pop from stack
+ if match := programSuccessRegex.FindStringSubmatch(line); len(match) > 1 {
+ if len(stack) > 0 {
+ stack = stack[:len(stack)-1] // pop
+ if len(stack) > 0 {
+ currentInstruction = stack[len(stack)-1]
+ } else {
+ currentInstruction = nil
+ }
+ }
+ continue
+ }
+
+ // Instruction name
+ if strings.Contains(line, "Instruction:") {
+ if currentInstruction != nil {
+ currentInstruction.Name = strings.TrimSpace(strings.Split(line, "Instruction:")[1])
+ }
+ continue
+ }
+
+ // Program logs
+ if strings.HasPrefix(line, "Program log:") {
+ if currentInstruction != nil {
+ logMessage := strings.TrimSpace(strings.TrimPrefix(line, "Program log:"))
+ currentInstruction.Logs = append(currentInstruction.Logs, logMessage)
+ }
+ continue
+ }
+
+ // Compute units
+ if match := computeUnitsRegex.FindStringSubmatch(line); len(match) > 1 {
+ programID := match[1]
+ computeUnits, _ := strconv.Atoi(match[2])
+
+ // Find the instruction in the stack that matches this program ID
+ for i := len(stack) - 1; i >= 0; i-- {
+ if stack[i].ProgramID == programID {
+ stack[i].ComputeUnits = computeUnits
+ break
+ }
+ }
+ }
+ }
+
+ return instructions
+}
+
+// Pretty prints the given Anchor instructions.
+// Example usage:
+// parsed := utils.ParseLogMessages(result.Meta.LogMessages)
+// output := utils.PrintInstructions(parsed)
+// t.Logf("Parsed Instructions: %s", output)
+func PrintInstructions(instructions []*AnchorInstruction) string {
+ var output strings.Builder
+
+ var printInstruction func(*AnchorInstruction, int, string)
+ printInstruction = func(instruction *AnchorInstruction, index int, indent string) {
+ output.WriteString(fmt.Sprintf("%sInstruction %d: %s\n", indent, index, instruction.Name))
+ output.WriteString(fmt.Sprintf("%s Program ID: %s\n", indent, instruction.ProgramID))
+ output.WriteString(fmt.Sprintf("%s Compute Units: %d\n", indent, instruction.ComputeUnits))
+ output.WriteString(fmt.Sprintf("%s Logs:\n", indent))
+ for _, log := range instruction.Logs {
+ output.WriteString(fmt.Sprintf("%s %s\n", indent, log))
+ }
+ if len(instruction.InnerCalls) > 0 {
+ output.WriteString(fmt.Sprintf("%s Inner Calls:\n", indent))
+ for i, innerCall := range instruction.InnerCalls {
+ printInstruction(innerCall, i+1, indent+" ")
+ }
+ }
+ }
+
+ for i, instruction := range instructions {
+ printInstruction(instruction, i+1, "")
+ }
+
+ return output.String()
+}
+
+func GetBlockTime(ctx context.Context, client *rpc.Client, commitment rpc.CommitmentType) (*solana.UnixTimeSeconds, error) {
+ block, err := client.GetBlockHeight(ctx, commitment)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get block height: %w", err)
+ }
+
+ blockTime, err := client.GetBlockTime(ctx, block)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get block time: %w", err)
+ }
+
+ return blockTime, nil
+}
diff --git a/chains/solana/contracts/tests/utils/anchor_test.go b/chains/solana/contracts/tests/utils/anchor_test.go
new file mode 100644
index 000000000..1db2d6d53
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/anchor_test.go
@@ -0,0 +1,189 @@
+package utils
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestParseLogMessages(t *testing.T) {
+ tests := []struct {
+ name string
+ logs []string
+ expected []*AnchorInstruction
+ }{
+ {
+ name: "Test Case 1 - Empty Instruction",
+ logs: []string{
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX invoke [1]",
+ "Program log: Instruction: Execute",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ invoke [2]",
+ "Program log: Instruction: Empty",
+ "Program log: Called `empty` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: Empty, remaining_accounts: [], bumps: EmptyBumps }",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ consumed 13620 of 180083 compute units",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ success",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX consumed 35400 of 200000 compute units",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX success",
+ },
+ expected: []*AnchorInstruction{
+ {
+ Name: "Execute",
+ ProgramID: "6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX",
+ Logs: []string{},
+ ComputeUnits: 35400,
+ InnerCalls: []*AnchorInstruction{
+ {
+ Name: "Empty",
+ ProgramID: "4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ",
+ Logs: []string{
+ "Called `empty` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: Empty, remaining_accounts: [], bumps: EmptyBumps }",
+ },
+ ComputeUnits: 13620,
+ InnerCalls: []*AnchorInstruction{},
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "Test Case 2 - U8InstructionData",
+ logs: []string{
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX invoke [1]",
+ "Program log: Instruction: Execute",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ invoke [2]",
+ "Program log: Instruction: U8InstructionData",
+ "Program log: Called `u8_instruction_data` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: Empty, remaining_accounts: [], bumps: EmptyBumps } and data 123",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ consumed 13648 of 180048 compute units",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ success",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX consumed 35463 of 200000 compute units",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX success",
+ },
+ expected: []*AnchorInstruction{
+ {
+ Name: "Execute",
+ ProgramID: "6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX",
+ Logs: []string{},
+ ComputeUnits: 35463,
+ InnerCalls: []*AnchorInstruction{
+ {
+ Name: "U8InstructionData",
+ ProgramID: "4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ",
+ Logs: []string{
+ "Called `u8_instruction_data` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: Empty, remaining_accounts: [], bumps: EmptyBumps } and data 123",
+ },
+ ComputeUnits: 13648,
+ InnerCalls: []*AnchorInstruction{},
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "Test Case 3 - StructInstructionData",
+ logs: []string{
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX invoke [1]",
+ "Program log: Instruction: Execute",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ invoke [2]",
+ "Program log: Instruction: StructInstructionData",
+ "Program log: Called `struct_instruction_data` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: Empty, remaining_accounts: [], bumps: EmptyBumps } and data Value { value: 234 }",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ consumed 13920 of 180631 compute units",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ success",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX consumed 35152 of 200000 compute units",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX success",
+ },
+ expected: []*AnchorInstruction{
+ {
+ Name: "Execute",
+ ProgramID: "6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX",
+ Logs: []string{},
+ ComputeUnits: 35152,
+ InnerCalls: []*AnchorInstruction{
+ {
+ Name: "StructInstructionData",
+ ProgramID: "4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ",
+ Logs: []string{
+ "Called `struct_instruction_data` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: Empty, remaining_accounts: [], bumps: EmptyBumps } and data Value { value: 234 }",
+ },
+ ComputeUnits: 13920,
+ InnerCalls: []*AnchorInstruction{},
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "Test Case 4 - AccountRead",
+ logs: []string{
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX invoke [1]",
+ "Program log: Instruction: Execute",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ invoke [2]",
+ "Program log: Instruction: AccountRead",
+ "Program log: Called `account_read` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: AccountRead { u8_value: Account { account: Value { value: 1 }, info: AccountInfo { key: 8WGXBpVJrBATopzT8iXvRuvp5f3U63uB13tfQjGoi6rM, owner: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, is_signer: false, is_writable: false, executable: false, rent_epoch: 18446744073709551615, lamports: 953520, data.len: 9, data: 879ef47548cb18c201, .. } } }, remaining_accounts: [], bumps: AccountReadBumps { u8_value: 255 } }",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ consumed 45559 of 177765 compute units",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ success",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX consumed 69682 of 200000 compute units",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX success",
+ },
+ expected: []*AnchorInstruction{
+ {
+ Name: "Execute",
+ ProgramID: "6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX",
+ Logs: []string{},
+ ComputeUnits: 69682,
+ InnerCalls: []*AnchorInstruction{
+ {
+ Name: "AccountRead",
+ ProgramID: "4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ",
+ Logs: []string{
+ "Called `account_read` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: AccountRead { u8_value: Account { account: Value { value: 1 }, info: AccountInfo { key: 8WGXBpVJrBATopzT8iXvRuvp5f3U63uB13tfQjGoi6rM, owner: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, is_signer: false, is_writable: false, executable: false, rent_epoch: 18446744073709551615, lamports: 953520, data.len: 9, data: 879ef47548cb18c201, .. } } }, remaining_accounts: [], bumps: AccountReadBumps { u8_value: 255 } }",
+ },
+ ComputeUnits: 45559,
+ InnerCalls: []*AnchorInstruction{},
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "Test Case 5 - AccountMut",
+ logs: []string{
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX invoke [1]",
+ "Program log: Instruction: Execute",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ invoke [2]",
+ "Program log: Instruction: AccountMut",
+ "Program log: Called `account_mut` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: AccountMut { u8_value: Account { account: Value { value: 1 }, info: AccountInfo { key: 8WGXBpVJrBATopzT8iXvRuvp5f3U63uB13tfQjGoi6rM, owner: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, is_signer: false, is_writable: true, executable: false, rent_epoch: 18446744073709551615, lamports: 953520, data.len: 9, data: 879ef47548cb18c201, .. } }, stub_caller: Signer { info: AccountInfo { key: BUx7YZMoVXCnT2BewMZc2hr8yxoiihtHdDuoa19D9R5q, owner: 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX, is_signer: true, is_writable: true, executable: false, rent_epoch: 18446744073709551615, lamports: 2874480, data.len: 285, data: 2c3eace1f603b2211266a21317e30848020102010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000, .. } }, system_program: Program { info: AccountInfo { key: 11111111111111111111111111111111, owner: NativeLoader1111111111111111111111111111111, is_signer: false, is_writable: false, executable: true, rent_epoch: 0, lamports: 1, data.len: 14, data: 73797374656d5f70726f6772616d, .. } } }, remaining_accounts: [], bumps: AccountMutBumps { u8_value: 255 } }",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ consumed 111015 of 173365 compute units",
+ "Program 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ success",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX consumed 139571 of 200000 compute units",
+ "Program 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX success",
+ },
+ expected: []*AnchorInstruction{
+ {
+ Name: "Execute",
+ ProgramID: "6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX",
+ Logs: []string{},
+ ComputeUnits: 139571,
+ InnerCalls: []*AnchorInstruction{
+ {
+ Name: "AccountMut",
+ ProgramID: "4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ",
+ Logs: []string{
+ "Called `account_mut` Context { program_id: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, accounts: AccountMut { u8_value: Account { account: Value { value: 1 }, info: AccountInfo { key: 8WGXBpVJrBATopzT8iXvRuvp5f3U63uB13tfQjGoi6rM, owner: 4HeqEoSyfYpeC2goFLj9eHgkxV33mR5G7JYAbRsN14uQ, is_signer: false, is_writable: true, executable: false, rent_epoch: 18446744073709551615, lamports: 953520, data.len: 9, data: 879ef47548cb18c201, .. } }, stub_caller: Signer { info: AccountInfo { key: BUx7YZMoVXCnT2BewMZc2hr8yxoiihtHdDuoa19D9R5q, owner: 6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX, is_signer: true, is_writable: true, executable: false, rent_epoch: 18446744073709551615, lamports: 2874480, data.len: 285, data: 2c3eace1f603b2211266a21317e30848020102010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000, .. } }, system_program: Program { info: AccountInfo { key: 11111111111111111111111111111111, owner: NativeLoader1111111111111111111111111111111, is_signer: false, is_writable: false, executable: true, rent_epoch: 0, lamports: 1, data.len: 14, data: 73797374656d5f70726f6772616d, .. } } }, remaining_accounts: [], bumps: AccountMutBumps { u8_value: 255 } }",
+ },
+ ComputeUnits: 111015,
+ InnerCalls: []*AnchorInstruction{},
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := ParseLogMessages(tt.logs)
+ if !reflect.DeepEqual(result, tt.expected) {
+ t.Errorf("Test %s failed.\nExpected:\n%#v\nGot:\n%#v", tt.name, tt.expected, result)
+ }
+ })
+ }
+}
diff --git a/chains/solana/contracts/tests/utils/eth/signer.go b/chains/solana/contracts/tests/utils/eth/signer.go
new file mode 100644
index 000000000..9920e6838
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/eth/signer.go
@@ -0,0 +1,148 @@
+package eth
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "math/big"
+ "sort"
+
+ "github.com/decred/dcrd/dcrec/secp256k1/v4"
+ "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
+ "golang.org/x/crypto/sha3"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/mcm"
+)
+
+type Signer struct {
+ PrivateKey []byte
+ PubKey []byte
+ Address [20]byte
+}
+
+func (s *Signer) Sign(msg []byte) (mcm.Signature, error) {
+ if len(s.PrivateKey) != 32 {
+ return mcm.Signature{}, errors.New("invalid private key length")
+ }
+
+ privateKey := secp256k1.PrivKeyFromBytes(s.PrivateKey)
+
+ if len(msg) != 32 {
+ return mcm.Signature{}, errors.New("message must be a 32-byte hash")
+ }
+
+ signature := ecdsa.SignCompact(privateKey, msg, false)
+
+ vByte := signature[0] // V is already adjusted with +27 in SignCompact
+ var rBytes, sBytes [32]byte
+ copy(rBytes[:], signature[1:33])
+ copy(sBytes[:], signature[33:65])
+
+ sig := mcm.Signature{
+ V: vByte,
+ R: rBytes,
+ S: sBytes,
+ }
+ return sig, nil
+}
+
+func (s Signer) String() string {
+ return "0x" + hex.EncodeToString(s.Address[:])
+}
+
+func GetEvmSigners(privateKeys []string) ([]Signer, error) {
+ signers := make([]Signer, 0, len(privateKeys))
+ for _, key := range privateKeys {
+ signer, err := GetSignerFromPk(key)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get signer from private key: %v", err)
+ }
+ signers = append(signers, signer)
+ }
+
+ sort.Slice(signers, func(i, j int) bool {
+ return string(signers[i].Address[:]) < string(signers[j].Address[:])
+ })
+ return signers, nil
+}
+
+func GetSignerFromPk(key string) (Signer, error) {
+ pkBytes, err := hex.DecodeString(key)
+ if err != nil {
+ return Signer{}, fmt.Errorf("failed to decode private key: %v", err)
+ }
+
+ privateKey := secp256k1.PrivKeyFromBytes(pkBytes)
+ publicKey := privateKey.PubKey()
+
+ pubKeyBytes := publicKey.SerializeUncompressed()
+ hash := Keccak256(pubKeyBytes[1:]) // skip the leading 0x04 byte
+ var address [20]byte
+ copy(address[:], hash[12:])
+
+ return Signer{
+ PrivateKey: pkBytes,
+ PubKey: pubKeyBytes,
+ Address: address,
+ }, nil
+}
+
+// TODO: helper functions for n of signer tests on set_config - will be removed after refactor
+// N is the order of the secp256k1 curve
+var secp256k1N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16)
+
+// Returns a slice of private keys as raw hexadecimal strings without "0x" prefix
+func GenerateEthPrivateKeys(n int) ([]string, error) {
+ if n <= 0 {
+ return nil, fmt.Errorf("number of keys must be positive")
+ }
+
+ privateKeys := make([]string, n)
+
+ for i := 0; i < n; i++ {
+ // Generate a valid private key
+ pk, err := generateValidPrivateKey()
+ if err != nil {
+ return nil, fmt.Errorf("failed to generate private key: %v", err)
+ }
+
+ // Convert to hex without 0x prefix
+ pkBytes := pk.Bytes()
+ // Ensure the key is exactly 32 bytes by left-padding with zeros if necessary
+ paddedPk := make([]byte, 32)
+ copy(paddedPk[32-len(pkBytes):], pkBytes)
+ privateKeys[i] = hex.EncodeToString(paddedPk)
+ }
+
+ return privateKeys, nil
+}
+
+func generateValidPrivateKey() (*big.Int, error) {
+ zero := big.NewInt(0)
+
+ for {
+ // Generate 32 random bytes
+ b := make([]byte, 32)
+ _, err := rand.Read(b)
+ if err != nil {
+ return nil, err
+ }
+
+ k := new(big.Int).SetBytes(b)
+
+ // Check if private key is valid:
+ // 1. k must be greater than 0
+ // 2. k must be less than the curve order (secp256k1N)
+ if k.Cmp(zero) > 0 && k.Cmp(secp256k1N) < 0 {
+ return k, nil
+ }
+ // If invalid, continue loop to generate another key
+ }
+}
+
+func Keccak256(data []byte) []byte {
+ hash := sha3.NewLegacyKeccak256()
+ hash.Write(data)
+ return hash.Sum(nil)
+}
diff --git a/chains/solana/contracts/tests/utils/fees/computebudget.go b/chains/solana/contracts/tests/utils/fees/computebudget.go
new file mode 100644
index 000000000..4fd7ab808
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/fees/computebudget.go
@@ -0,0 +1,203 @@
+package fees
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+
+ "github.com/gagliardetto/solana-go"
+ "golang.org/x/exp/constraints"
+)
+
+// https://github.com/solana-labs/solana/blob/60858d043ca612334de300805d93ea3014e8ab37/sdk/src/compute_budget.rs#L25
+const (
+ // deprecated: will not support for building instruction
+ InstructionRequestUnitsDeprecated computeBudgetInstruction = iota
+
+ // Request a specific transaction-wide program heap region size in bytes.
+ // The value requested must be a multiple of 1024. This new heap region
+ // size applies to each program executed in the transaction, including all
+ // calls to CPIs.
+ // note: uses ag_binary.Varuint32
+ InstructionRequestHeapFrame
+
+ // Set a specific compute unit limit that the transaction is allowed to consume.
+ // note: uses ag_binary.Varuint32
+ InstructionSetComputeUnitLimit
+
+ // Set a compute unit price in "micro-lamports" to pay a higher transaction
+ // fee for higher transaction prioritization.
+ // note: uses ag_binary.Uint64
+ InstructionSetComputeUnitPrice
+)
+
+var (
+ ComputeBudgetProgram = solana.MustPublicKeyFromBase58("ComputeBudget111111111111111111111111111111")
+)
+
+type computeBudgetInstruction uint8
+
+func (ins computeBudgetInstruction) String() (out string) {
+ out = "INVALID"
+ switch ins {
+ case InstructionRequestUnitsDeprecated:
+ out = "RequestUnitsDeprecated"
+ case InstructionRequestHeapFrame:
+ out = "RequestHeapFrame"
+ case InstructionSetComputeUnitLimit:
+ out = "SetComputeUnitLimit"
+ case InstructionSetComputeUnitPrice:
+ out = "SetComputeUnitPrice"
+ }
+ return out
+}
+
+// instruction is an internal interface for encoding instruction data
+type instruction interface {
+ Data() ([]byte, error)
+ Selector() computeBudgetInstruction
+}
+
+// https://docs.solana.com/developing/programming-model/runtime
+type ComputeUnitPrice uint64
+
+// simple encoding into program expected format
+func (val ComputeUnitPrice) Data() ([]byte, error) {
+ return encode(InstructionSetComputeUnitPrice, val)
+}
+
+func (val ComputeUnitPrice) Selector() computeBudgetInstruction {
+ return InstructionSetComputeUnitPrice
+}
+
+type ComputeUnitLimit uint32
+
+func (val ComputeUnitLimit) Data() ([]byte, error) {
+ return encode(InstructionSetComputeUnitLimit, val)
+}
+
+func (val ComputeUnitLimit) Selector() computeBudgetInstruction {
+ return InstructionSetComputeUnitLimit
+}
+
+// encode combines the identifier and little encoded value into a byte array
+func encode[V constraints.Unsigned](identifier computeBudgetInstruction, val V) ([]byte, error) {
+ buf := new(bytes.Buffer)
+
+ // encode method identifier
+ if err := buf.WriteByte(uint8(identifier)); err != nil {
+ return []byte{}, err
+ }
+
+ // encode value
+ if err := binary.Write(buf, binary.LittleEndian, val); err != nil {
+ return []byte{}, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+func ParseComputeUnitPrice(data []byte) (ComputeUnitPrice, error) {
+ v, err := parse(InstructionSetComputeUnitPrice, data, binary.LittleEndian.Uint64)
+ return ComputeUnitPrice(v), err
+}
+
+func ParseComputeUnitLimit(data []byte) (ComputeUnitLimit, error) {
+ v, err := parse(InstructionSetComputeUnitLimit, data, binary.LittleEndian.Uint32)
+ return ComputeUnitLimit(v), err
+}
+
+// parse implements tx data parsing for the provided instruction type and specified decoder
+func parse[V constraints.Unsigned](ins computeBudgetInstruction, data []byte, decoder func([]byte) V) (V, error) {
+ if len(data) != (1 + binary.Size(V(0))) { // instruction byte + uintXXX length
+ return 0, fmt.Errorf("invalid length: %d", len(data))
+ }
+
+ // validate instruction identifier
+ if data[0] != uint8(ins) {
+ return 0, fmt.Errorf("not %s identifier: %d", ins, data[0])
+ }
+
+ // guarantees length to fit the binary decoder
+ return decoder(data[1:]), nil
+}
+
+// modifies passed in tx to set compute unit price
+func SetComputeUnitPrice(tx *solana.Transaction, value ComputeUnitPrice) error {
+ return set(tx, value, true) // data feeds expects SetComputeUnitPrice instruction to be right before report instruction
+}
+
+func SetComputeUnitLimit(tx *solana.Transaction, value ComputeUnitLimit) error {
+ return set(tx, value, false) // appends instruction to the end
+}
+
+// set adds or modifies instructions for the compute budget program
+func set(tx *solana.Transaction, baseData instruction, appendToFront bool) error {
+ // find ComputeBudget program to accounts if it exists
+ // reimplements HasAccount to retrieve index: https://github.com/gagliardetto/solana-go/blob/618f56666078f8131a384ab27afd918d248c08b7/message.go#L233
+ var exists bool
+ var programIdx int
+ for i, a := range tx.Message.AccountKeys {
+ if a.Equals(ComputeBudgetProgram) {
+ exists = true
+ programIdx = i
+ break
+ }
+ }
+ // if it doesn't exist, add to account keys
+ if !exists {
+ tx.Message.AccountKeys = append(tx.Message.AccountKeys, ComputeBudgetProgram)
+ programIdx = len(tx.Message.AccountKeys) - 1 // last index of account keys
+
+ // https://github.com/gagliardetto/solana-go/blob/618f56666078f8131a384ab27afd918d248c08b7/transaction.go#L293
+ tx.Message.Header.NumReadonlyUnsignedAccounts++
+
+ // lookup table addresses are indexed after the tx.Message.AccountKeys
+ // higher indices must be increased
+ // https://github.com/gagliardetto/solana-go/blob/da2193071f56059aa35010a239cece016c4e827f/transaction.go#L440
+ for i, ix := range tx.Message.Instructions {
+ for j, v := range ix.Accounts {
+ if int(v) >= programIdx {
+ tx.Message.Instructions[i].Accounts[j]++
+ }
+ }
+ }
+ }
+
+ // get instruction data
+ data, err := baseData.Data()
+ if err != nil {
+ return err
+ }
+
+ // compiled instruction
+ instruction := solana.CompiledInstruction{
+ ProgramIDIndex: uint16(programIdx), //nolint:gosec // max value would exceed tx size
+ Data: data,
+ }
+
+ // check if there is an instruction for setcomputeunitprice
+ var found bool
+ var instructionIdx int
+ for i := range tx.Message.Instructions {
+ if int(tx.Message.Instructions[i].ProgramIDIndex) == programIdx &&
+ len(tx.Message.Instructions[i].Data) > 0 &&
+ tx.Message.Instructions[i].Data[0] == uint8(baseData.Selector()) {
+ found = true
+ instructionIdx = i
+ break
+ }
+ }
+
+ if found {
+ tx.Message.Instructions[instructionIdx] = instruction
+ } else {
+ if appendToFront {
+ tx.Message.Instructions = append([]solana.CompiledInstruction{instruction}, tx.Message.Instructions...)
+ } else {
+ tx.Message.Instructions = append(tx.Message.Instructions, instruction)
+ }
+ }
+
+ return nil
+}
diff --git a/chains/solana/contracts/tests/utils/fees/computebudget_test.go b/chains/solana/contracts/tests/utils/fees/computebudget_test.go
new file mode 100644
index 000000000..a6cba383b
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/fees/computebudget_test.go
@@ -0,0 +1,224 @@
+package fees
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/programs/system"
+ "github.com/gagliardetto/solana-go/programs/token"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSet(t *testing.T) {
+ t.Run("ComputeUnitPrice", func(t *testing.T) {
+ t.Parallel()
+ testSet(t, func(v uint) ComputeUnitPrice {
+ return ComputeUnitPrice(v)
+ }, SetComputeUnitPrice, true)
+ })
+ t.Run("ComputeUnitLimit", func(t *testing.T) {
+ t.Parallel()
+ testSet(t, func(v uint) ComputeUnitLimit {
+ return ComputeUnitLimit(v)
+ }, SetComputeUnitLimit, false)
+ })
+}
+
+func testSet[V instruction](t *testing.T, builder func(uint) V, setter func(*solana.Transaction, V) error, expectFirstInstruction bool) {
+ key, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+
+ getIndex := func(count int) int {
+ index := count - 1
+ if expectFirstInstruction {
+ index = 0
+ }
+ return index
+ }
+
+ t.Run("noAccount_nofee", func(t *testing.T) {
+ t.Parallel()
+ // build base tx (no fee)
+ tx, err := solana.NewTransaction([]solana.Instruction{
+ system.NewTransferInstruction(
+ 0,
+ key.PublicKey(),
+ key.PublicKey(),
+ ).Build(),
+ }, solana.Hash{})
+ require.NoError(t, err)
+ instructionCount := len(tx.Message.Instructions)
+
+ // add fee
+ require.NoError(t, setter(tx, builder(1)))
+
+ // evaluate
+ currentCount := len(tx.Message.Instructions)
+ assert.Greater(t, currentCount, instructionCount)
+ assert.Equal(t, 2, currentCount)
+ i := getIndex(currentCount)
+ assert.Equal(t, ComputeBudgetProgram, tx.Message.AccountKeys[tx.Message.Instructions[i].ProgramIDIndex])
+ data, err := builder(1).Data()
+ assert.NoError(t, err)
+ assert.Equal(t, data, []byte(tx.Message.Instructions[i].Data))
+ })
+
+ t.Run("accountExists_noFee", func(t *testing.T) {
+ t.Parallel()
+ // build base tx (no fee)
+ tx, err := solana.NewTransaction([]solana.Instruction{
+ system.NewTransferInstruction(
+ 0,
+ key.PublicKey(),
+ key.PublicKey(),
+ ).Build(),
+ }, solana.Hash{})
+ require.NoError(t, err)
+ accountCount := len(tx.Message.AccountKeys)
+ tx.Message.AccountKeys = append(tx.Message.AccountKeys, ComputeBudgetProgram)
+ accountCount++
+
+ // add fee
+ require.NoError(t, setter(tx, builder(1)))
+
+ // accounts should not have changed
+ assert.Equal(t, accountCount, len(tx.Message.AccountKeys))
+ assert.Equal(t, 2, len(tx.Message.Instructions))
+ i := getIndex(len(tx.Message.Instructions))
+ assert.Equal(t, ComputeBudgetProgram, tx.Message.AccountKeys[tx.Message.Instructions[i].ProgramIDIndex])
+ data, err := builder(1).Data()
+ assert.NoError(t, err)
+ assert.Equal(t, data, []byte(tx.Message.Instructions[i].Data))
+ })
+
+ // // not a valid test, account must exist for tx to be added
+ // t.Run("noAccount_feeExists", func(t *testing.T) {})
+
+ t.Run("exists_unknownOrder", func(t *testing.T) {
+ t.Parallel()
+ // build base tx (no fee)
+ tx, err := solana.NewTransaction([]solana.Instruction{
+ system.NewTransferInstruction(
+ 0,
+ key.PublicKey(),
+ key.PublicKey(),
+ ).Build(),
+ }, solana.Hash{})
+ require.NoError(t, err)
+ transferInstruction := tx.Message.Instructions[0]
+
+ // add fee
+ require.NoError(t, setter(tx, builder(0)))
+
+ // swap order of instructions
+ tx.Message.Instructions[0], tx.Message.Instructions[1] = tx.Message.Instructions[1], tx.Message.Instructions[0]
+
+ // after swap
+ computeIndex := 0
+ transferIndex := 1
+ if expectFirstInstruction {
+ computeIndex = 1
+ transferIndex = 0
+ }
+
+ require.Equal(t, transferInstruction, tx.Message.Instructions[transferIndex])
+ oldComputeInstruction := tx.Message.Instructions[computeIndex]
+ accountCount := len(tx.Message.AccountKeys)
+
+ // set fee with existing fee instruction
+ require.NoError(t, setter(tx, builder(100)))
+ require.Equal(t, transferInstruction, tx.Message.Instructions[transferIndex]) // transfer should not have been touched
+ assert.NotEqual(t, oldComputeInstruction, tx.Message.Instructions[computeIndex])
+ assert.Equal(t, accountCount, len(tx.Message.AccountKeys))
+ assert.Equal(t, 2, len(tx.Message.Instructions)) // instruction count did not change
+ data, err := builder(100).Data()
+ assert.NoError(t, err)
+ assert.Equal(t, data, []byte(tx.Message.Instructions[computeIndex].Data))
+ })
+
+ t.Run("with_lookuptables", func(t *testing.T) {
+ t.Parallel()
+
+ // build base tx (no fee)
+ tx, err := solana.NewTransaction([]solana.Instruction{
+ system.NewTransferInstruction(
+ 1,
+ solana.PublicKey{1},
+ solana.PublicKey{2},
+ ).Build(),
+ token.NewTransferInstruction(
+ uint64(1),
+ solana.PublicKey{11},
+ solana.PublicKey{12},
+ solana.PublicKey{13},
+ []solana.PublicKey{
+ solana.PublicKey{14},
+ },
+ ).Build(),
+ },
+ solana.Hash{},
+ solana.TransactionAddressTables(map[solana.PublicKey]solana.PublicKeySlice{
+ solana.PublicKey{}: solana.PublicKeySlice{solana.PublicKey{1}, solana.PublicKey{2}, solana.PublicKey{11}, solana.PublicKey{12}, solana.PublicKey{13}, solana.PublicKey{14}},
+ }),
+ )
+ require.NoError(t, err)
+
+ // check current account indices
+ assert.Equal(t, 2, len(tx.Message.Instructions))
+ assert.Equal(t, []uint16{0, 4}, tx.Message.Instructions[0].Accounts)
+ assert.Equal(t, []uint16{5, 6, 7, 1}, tx.Message.Instructions[1].Accounts)
+ assert.Equal(t, 4, len(tx.Message.AccountKeys))
+
+ // add fee
+ require.NoError(t, setter(tx, builder(0)))
+
+ // evaluate
+ assert.Equal(t, 3, len(tx.Message.Instructions))
+ computeUnitIndex := getIndex(len(tx.Message.Instructions))
+ transferIndex := 0
+ if computeUnitIndex == transferIndex {
+ transferIndex = 1
+ }
+ assert.Equal(t, 5, len(tx.Message.AccountKeys))
+ assert.Equal(t, uint16(4), tx.Message.Instructions[computeUnitIndex].ProgramIDIndex)
+ assert.Equal(t, []uint16{0, 5}, tx.Message.Instructions[transferIndex].Accounts)
+ assert.Equal(t, []uint16{6, 7, 8, 1}, tx.Message.Instructions[transferIndex+1].Accounts)
+ })
+}
+
+func TestParse(t *testing.T) {
+ t.Run("ComputeUnitPrice", func(t *testing.T) {
+ t.Parallel()
+ testParse(t, func(v uint) ComputeUnitPrice {
+ return ComputeUnitPrice(v)
+ }, ParseComputeUnitPrice)
+ })
+ t.Run("ComputeUnitLimit", func(t *testing.T) {
+ t.Parallel()
+ testParse(t, func(v uint) ComputeUnitLimit {
+ return ComputeUnitLimit(v)
+ }, ParseComputeUnitLimit)
+ })
+}
+
+func testParse[V instruction](t *testing.T, builder func(uint) V, parser func([]byte) (V, error)) {
+ data, err := builder(100).Data()
+ assert.NoError(t, err)
+
+ v, err := parser(data)
+ assert.NoError(t, err)
+ assert.Equal(t, builder(100), v)
+
+ _, err = parser([]byte{})
+ assert.ErrorContains(t, err, "invalid length")
+ tooLong := [10]byte{}
+ _, err = parser(tooLong[:])
+ assert.ErrorContains(t, err, "invalid length")
+
+ invalidData := data
+ invalidData[0] = uint8(InstructionRequestHeapFrame)
+ _, err = parser(invalidData)
+ assert.ErrorContains(t, err, fmt.Sprintf("not %s identifier", builder(0).Selector()))
+}
diff --git a/chains/solana/contracts/tests/utils/localvalidator.go b/chains/solana/contracts/tests/utils/localvalidator.go
new file mode 100644
index 000000000..9acb9c5ac
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/localvalidator.go
@@ -0,0 +1,94 @@
+package utils
+
+import (
+ "bytes"
+ "os/exec"
+ "strconv"
+ "testing"
+ "time"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-common/pkg/utils"
+ "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+)
+
+// SetupLocalSolNode sets up a local solana node via solana cli, and returns the url
+func SetupLocalSolNode(t *testing.T) string {
+ t.Helper()
+
+ url, _ := SetupLocalSolNodeWithFlags(t)
+
+ return url
+}
+
+// SetupLocalSolNode sets up a local solana node via solana cli, and returns the url
+func SetupLocalSolNodeWithFlags(t *testing.T, flags ...string) (string, string) {
+ t.Helper()
+
+ port := utils.MustRandomPort(t)
+ portInt, _ := strconv.Atoi(port)
+
+ faucetPort := utils.MustRandomPort(t)
+ url := "http://127.0.0.1:" + port
+ wsURL := "ws://127.0.0.1:" + strconv.Itoa(portInt+1)
+
+ args := append([]string{
+ "--reset",
+ "--rpc-port", port,
+ "--faucet-port", faucetPort,
+ "--ledger", t.TempDir(),
+ }, flags...)
+
+ cmd := exec.Command("solana-test-validator", args...)
+
+ var stdErr bytes.Buffer
+ cmd.Stderr = &stdErr
+ var stdOut bytes.Buffer
+ cmd.Stdout = &stdOut
+ require.NoError(t, cmd.Start())
+ t.Cleanup(func() {
+ assert.NoError(t, cmd.Process.Kill())
+ if err2 := cmd.Wait(); assert.Error(t, err2) {
+ if !assert.Contains(t, err2.Error(), "signal: killed", cmd.ProcessState.String()) {
+ t.Logf("solana-test-validator\n stdout: %s\n stderr: %s", stdOut.String(), stdErr.String())
+ }
+ }
+ })
+
+ // Wait for api server to boot
+ var ready bool
+ for i := 0; i < 30; i++ {
+ time.Sleep(time.Second)
+ client := rpc.New(url)
+ out, err := client.GetHealth(tests.Context(t))
+ if err != nil || out != rpc.HealthOk {
+ t.Logf("API server not ready yet (attempt %d)\n", i+1)
+ continue
+ }
+ ready = true
+ break
+ }
+ if !ready {
+ t.Logf("Cmd output: %s\nCmd error: %s\n", stdOut.String(), stdErr.String())
+ }
+ require.True(t, ready)
+
+ return url, wsURL
+}
+
+func FundTestAccounts(t *testing.T, keys []solana.PublicKey, url string) {
+ t.Helper()
+
+ for i := range keys {
+ account := keys[i].String()
+ _, err := exec.Command("solana", "airdrop", "100",
+ account,
+ "--url", url,
+ ).Output()
+ require.NoError(t, err)
+ }
+}
diff --git a/chains/solana/contracts/tests/utils/lookuptable.go b/chains/solana/contracts/tests/utils/lookuptable.go
new file mode 100644
index 000000000..5eae09f7c
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/lookuptable.go
@@ -0,0 +1,157 @@
+package utils
+
+import (
+ "context"
+ "encoding/binary"
+ "testing"
+ "time"
+
+ "github.com/gagliardetto/solana-go"
+ addresslookuptable "github.com/gagliardetto/solana-go/programs/address-lookup-table"
+ "github.com/gagliardetto/solana-go/rpc"
+)
+
+var (
+ AddressLookupTableProgram = solana.MustPublicKeyFromBase58("AddressLookupTab1e1111111111111111111111111")
+)
+
+// https://github.com/anza-xyz/agave/blob/master/programs/address-lookup-table/src/processor.rs
+// https://github.com/anza-xyz/agave/blob/489f483e1d7b30ef114e0123994818b2accfa389/sdk/program/src/address_lookup_table/instruction.rs
+const (
+ InstructionCreateLookupTable uint32 = iota
+ InstructionFreezeLookupTable
+ InstructionExtendLookupTable
+ InstructionDeactivateLookupTable
+ InstructionCloseLookupTable
+)
+
+func NewCreateLookupTableInstruction(
+ authority,
+ funder solana.PublicKey,
+ slot uint64,
+) (solana.PublicKey, solana.Instruction, error) {
+ // https://github.com/solana-labs/solana-web3.js/blob/c1c98715b0c7900ce37c59bffd2056fa0037213d/src/programs/address-lookup-table/index.ts#L274
+ slotLE := make([]byte, 8)
+ binary.LittleEndian.PutUint64(slotLE, slot)
+ account, bumpSeed, err := solana.FindProgramAddress([][]byte{authority.Bytes(), slotLE}, AddressLookupTableProgram)
+ if err != nil {
+ return solana.PublicKey{}, nil, err
+ }
+
+ data := binary.LittleEndian.AppendUint32([]byte{}, InstructionCreateLookupTable)
+ data = binary.LittleEndian.AppendUint64(data, slot)
+ data = append(data, bumpSeed)
+ return account, solana.NewInstruction(
+ AddressLookupTableProgram,
+ solana.AccountMetaSlice{
+ solana.Meta(account).WRITE(),
+ solana.Meta(authority).SIGNER(),
+ solana.Meta(funder).SIGNER().WRITE(),
+ solana.Meta(solana.SystemProgramID),
+ },
+ data,
+ ), nil
+}
+
+func NewExtendLookupTableInstruction(
+ table, authority, funder solana.PublicKey,
+ accounts []solana.PublicKey,
+) solana.Instruction {
+ // https://github.com/solana-labs/solana-web3.js/blob/c1c98715b0c7900ce37c59bffd2056fa0037213d/src/programs/address-lookup-table/index.ts#L113
+
+ data := binary.LittleEndian.AppendUint32([]byte{}, InstructionExtendLookupTable)
+ data = binary.LittleEndian.AppendUint64(data, uint64(len(accounts))) // note: this is usually u32 + 8 byte buffer
+ for _, a := range accounts {
+ data = append(data, a.Bytes()...)
+ }
+
+ return solana.NewInstruction(
+ AddressLookupTableProgram,
+ solana.AccountMetaSlice{
+ solana.Meta(table).WRITE(),
+ solana.Meta(authority).SIGNER(),
+ solana.Meta(funder).SIGNER().WRITE(),
+ solana.Meta(solana.SystemProgramID),
+ },
+ data,
+ )
+}
+
+// TODO remove dependency on all methods on *testing.T once SendAndConfirm no longer requires it
+
+func CreateLookupTable(ctx context.Context, t *testing.T, client *rpc.Client, admin solana.PrivateKey) (solana.PublicKey, error) {
+ slot, serr := client.GetSlot(ctx, rpc.CommitmentFinalized)
+ if serr != nil {
+ return solana.PublicKey{}, serr
+ }
+
+ table, instruction, ierr := NewCreateLookupTableInstruction(
+ admin.PublicKey(),
+ admin.PublicKey(),
+ slot-1, // Using the most recent slot sometimes results in errors when submitting the transaction.
+ )
+ if ierr != nil {
+ return solana.PublicKey{}, ierr
+ }
+
+ SendAndConfirm(ctx, t, client, []solana.Instruction{instruction}, admin, rpc.CommitmentConfirmed)
+
+ return table, nil
+}
+
+func ExtendLookupTable(ctx context.Context, t *testing.T, client *rpc.Client, table solana.PublicKey, admin solana.PrivateKey, entries []solana.PublicKey) {
+ SendAndConfirm(ctx, t, client, []solana.Instruction{
+ NewExtendLookupTableInstruction(
+ table,
+ admin.PublicKey(),
+ admin.PublicKey(),
+ entries,
+ ),
+ }, admin, rpc.CommitmentConfirmed)
+}
+
+func AwaitSlotChange(ctx context.Context, client *rpc.Client) error {
+ originalSlot, err := client.GetSlot(ctx, rpc.CommitmentConfirmed)
+ if err != nil {
+ return err
+ }
+ newSlot := originalSlot
+ for newSlot == originalSlot {
+ newSlot, err = client.GetSlot(ctx, rpc.CommitmentConfirmed)
+ if err != nil {
+ return err
+ }
+ time.Sleep(10 * time.Millisecond)
+ }
+ return nil
+}
+
+func SetupLookupTable(ctx context.Context, t *testing.T, client *rpc.Client, admin solana.PrivateKey, entries []solana.PublicKey) (solana.PublicKey, error) {
+ table, err := CreateLookupTable(ctx, t, client, admin)
+ if err != nil {
+ return solana.PublicKey{}, err
+ }
+
+ ExtendLookupTable(ctx, t, client, table, admin, entries)
+
+ // Address lookup tables have to "warm up" for at least 1 slot before they can be used.
+ // So, we wait for a new slot to be produced before returning the table, so it's available
+ // and warmed up as soon as this method returns it.
+ err = AwaitSlotChange(ctx, client)
+ if err != nil {
+ return solana.PublicKey{}, err
+ }
+
+ return table, nil
+}
+
+func GetAddressLookupTable(ctx context.Context, client *rpc.Client, lookupTablePublicKey solana.PublicKey) ([]solana.PublicKey, error) {
+ lookupTableState, err := addresslookuptable.GetAddressLookupTableStateWithOpts(ctx, client, lookupTablePublicKey, &rpc.GetAccountInfoOpts{
+ Commitment: rpc.CommitmentConfirmed,
+ })
+ if err != nil {
+ return []solana.PublicKey{}, err
+ }
+
+ return lookupTableState.Addresses, nil
+}
diff --git a/chains/solana/contracts/tests/utils/mcms/common.go b/chains/solana/contracts/tests/utils/mcms/common.go
new file mode 100644
index 000000000..eae848a37
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/mcms/common.go
@@ -0,0 +1,133 @@
+package mcms
+
+import (
+ "bytes"
+ crypto_rand "crypto/rand"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "math"
+ "slices"
+ "time"
+
+ "github.com/gagliardetto/solana-go"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+)
+
+type McmConfigArgs struct {
+ MultisigName [32]uint8
+ SignerAddresses [][20]uint8
+ SignerGroups []byte
+ GroupQuorums [32]uint8
+ GroupParents [32]uint8
+ ClearRoot bool
+}
+
+func NewValidMcmConfig(msigName [32]byte, signerPrivateKeys []string, signerGroups []byte, quorums []uint8, parents []uint8, clearRoot bool) (*McmConfigArgs, error) {
+ if len(signerGroups) == 0 {
+ return nil, fmt.Errorf("signerGroups cannot be empty")
+ }
+
+ signers, err := eth.GetEvmSigners(signerPrivateKeys)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get test EVM signers: %w", err)
+ }
+
+ if len(signers) != len(signerGroups) {
+ return nil, fmt.Errorf("number of signers (%d) does not match length of signerGroups (%d)", len(signers), len(signerGroups))
+ }
+
+ signerAddresses := make([][20]uint8, len(signers))
+ for i, signer := range signers {
+ signerAddresses[i] = signer.Address
+ }
+
+ var groupQuorums [32]uint8
+ var groupParents [32]uint8
+
+ copy(groupQuorums[:], quorums)
+ copy(groupParents[:], parents)
+
+ // Create new config vars to ensure atomic test configs
+ newSignerAddresses := make([][20]uint8, len(signerAddresses))
+ copy(newSignerAddresses, signerAddresses)
+
+ newSignerGroups := make([]byte, len(signerGroups))
+ copy(newSignerGroups, signerGroups)
+
+ newGroupQuorums := groupQuorums
+ newGroupParents := groupParents
+ newClearRoot := clearRoot
+
+ config := &McmConfigArgs{
+ MultisigName: msigName,
+ }
+ config.SignerAddresses = newSignerAddresses
+ config.SignerGroups = newSignerGroups
+ config.GroupQuorums = newGroupQuorums
+ config.GroupParents = newGroupParents
+ config.ClearRoot = newClearRoot
+ return config, nil
+}
+
+func FindInSortedList(list []solana.PublicKey, target solana.PublicKey) (int, bool) {
+ return slices.BinarySearchFunc(list, target, func(a, b solana.PublicKey) int {
+ return bytes.Compare(a.Bytes(), b.Bytes())
+ })
+}
+
+func SafeToUint8(n int) (uint8, error) {
+ if n < 0 || n > 255 {
+ return 0, fmt.Errorf("value %d is outside uint8 range [0,255]", n)
+ }
+ return uint8(n), nil
+}
+
+func SafeToUint32(n int) (uint32, error) {
+ if n < 0 || n > math.MaxUint32 {
+ return 0, fmt.Errorf("value %d is outside uint32 range [0,%d]", n, math.MaxUint32)
+ }
+ return uint32(n), nil
+}
+
+func PadString32(input string) ([32]byte, error) {
+ var result [32]byte
+ inputBytes := []byte(input)
+ inputLen := len(inputBytes)
+ if inputLen > 32 {
+ return result, errors.New("input string exceeds 32 bytes")
+ }
+ startPos := 32 - inputLen
+ copy(result[startPos:], inputBytes)
+ return result, nil
+}
+
+func UnpadString32(input [32]byte) string {
+ startPos := 0
+ for i := 0; i < len(input); i++ {
+ if input[i] != 0 {
+ startPos = i
+ break
+ }
+ }
+ return string(input[startPos:])
+}
+
+// simple salt generator that uses the current Unix timestamp(in mills)
+func SimpleSalt() ([32]byte, error) {
+ var salt [32]byte
+ now := time.Now().UnixMilli()
+ if now < 0 {
+ return salt, fmt.Errorf("negative timestamp: %d", now)
+ }
+ // unix timestamp in millseconds
+ binary.BigEndian.PutUint64(salt[:8], uint64(now))
+ // Next 8 bytes: Crypto random
+ randBytes := make([]byte, 8)
+ if _, err := crypto_rand.Read(randBytes); err != nil {
+ return salt, fmt.Errorf("failed to generate random bytes: %w", err)
+ }
+ copy(salt[8:16], randBytes)
+ return salt, nil
+}
diff --git a/chains/solana/contracts/tests/utils/mcms/common_test.go b/chains/solana/contracts/tests/utils/mcms/common_test.go
new file mode 100644
index 000000000..ed226b2f2
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/mcms/common_test.go
@@ -0,0 +1,77 @@
+package mcms
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestPadString32(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ shouldFail bool
+ expectedOutput string
+ }{
+ {
+ name: "basic case - test-mcm",
+ input: "test-mcm",
+ shouldFail: false,
+ expectedOutput: "000000000000000000000000000000000000000000000000746573742d6d636d",
+ },
+ {
+ name: "empty string",
+ input: "",
+ shouldFail: false,
+ expectedOutput: "0000000000000000000000000000000000000000000000000000000000000000",
+ },
+ {
+ name: "single character",
+ input: "a",
+ shouldFail: false,
+ expectedOutput: "0000000000000000000000000000000000000000000000000000000000000061",
+ },
+ {
+ name: "exactly 32 bytes",
+ input: "12345678901234567890123456789012",
+ shouldFail: false,
+ expectedOutput: "3132333435363738393031323334353637383930313233343536373839303132",
+ },
+ {
+ name: "too long - over 32 bytes",
+ input: "123456789012345678901234567890123",
+ shouldFail: true,
+ },
+ {
+ name: "special characters",
+ input: "test@#$%",
+ shouldFail: false,
+ expectedOutput: "0000000000000000000000000000000000000000000000007465737440232425",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result, err := PadString32(tt.input)
+
+ if (err != nil) != tt.shouldFail {
+ t.Errorf("PadString32() error = %v, wantError %v", err, tt.shouldFail)
+ return
+ }
+
+ if tt.shouldFail {
+ return
+ }
+
+ if len(result) != 32 {
+ t.Errorf("PadString32() returned buffer length = %d, want 32", len(result))
+ }
+
+ if tt.expectedOutput != "" {
+ gotHex := fmt.Sprintf("%x", result)
+ if gotHex != tt.expectedOutput {
+ t.Errorf("PadString32() = %v, want %v", gotHex, tt.expectedOutput)
+ }
+ }
+ })
+ }
+}
diff --git a/chains/solana/contracts/tests/utils/mcms/mcm.go b/chains/solana/contracts/tests/utils/mcms/mcm.go
new file mode 100644
index 000000000..7d8fdbd42
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/mcms/mcm.go
@@ -0,0 +1,130 @@
+package mcms
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/gagliardetto/solana-go"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+)
+
+type Multisig struct {
+ PaddedName [32]byte
+ SignerPDA solana.PublicKey
+ ConfigPDA solana.PublicKey
+ RootMetadataPDA solana.PublicKey
+ ExpiringRootAndOpCountPDA solana.PublicKey
+ ConfigSignersPDA solana.PublicKey
+ RootSignaturesPDA func(root [32]byte, validUntil uint32) solana.PublicKey
+ SeenSignedHashesPDA func(root [32]byte, validUntil uint32) solana.PublicKey
+
+ RawConfig McmConfigArgs
+ Signers []eth.Signer
+}
+
+type SignatureAnalyzer struct {
+ signerGroups []byte
+ groupQuorums [32]uint8
+ groupParents [32]uint8
+ groupMembers map[byte][]string
+ visitedGroups map[byte]bool
+ numGroups int // Track actual number of groups
+}
+
+func NewSignatureAnalyzer(signerGroups []byte, groupQuorums, groupParents [32]uint8) *SignatureAnalyzer {
+ // Find actual number of groups
+ numGroups := 0
+ for i, quorum := range groupQuorums {
+ if quorum > 0 {
+ if i+1 > numGroups {
+ numGroups = i + 1
+ }
+ }
+ }
+
+ sa := &SignatureAnalyzer{
+ signerGroups: signerGroups,
+ groupQuorums: groupQuorums,
+ groupParents: groupParents,
+ groupMembers: make(map[byte][]string),
+ visitedGroups: make(map[byte]bool),
+ numGroups: numGroups,
+ }
+
+ for i, group := range signerGroups {
+ if int(group) < numGroups {
+ sa.groupMembers[group] = append(sa.groupMembers[group], fmt.Sprintf("signer_%d", i))
+ }
+ }
+
+ return sa
+}
+
+func (sa *SignatureAnalyzer) getChildGroups(groupID byte) []byte {
+ var children []byte
+ for i := 0; i < sa.numGroups; i++ {
+ if sa.groupParents[i] == groupID {
+ children = append(children, byte(i))
+ }
+ }
+ return children
+}
+
+func (sa *SignatureAnalyzer) getRequiredSigners(groupID byte) map[string]bool {
+ if sa.visitedGroups[groupID] {
+ return make(map[string]bool)
+ }
+ sa.visitedGroups[groupID] = true
+
+ possibleSigners := make(map[string]bool)
+
+ // Add direct members
+ for _, signer := range sa.groupMembers[groupID] {
+ possibleSigners[signer] = true
+ }
+
+ // Add child group members
+ childGroups := sa.getChildGroups(groupID)
+ for _, child := range childGroups {
+ childSigners := sa.getRequiredSigners(child)
+ for signer := range childSigners {
+ possibleSigners[signer] = true
+ }
+ }
+
+ return possibleSigners
+}
+
+func (sa *SignatureAnalyzer) PrintGroupStructure() {
+ fmt.Println("\nGroup Structure Analysis:")
+
+ for groupID := 0; groupID < sa.numGroups; groupID++ {
+ fmt.Printf("\nGroup %d:\n", groupID)
+ fmt.Printf("Quorum required: %d\n", sa.groupQuorums[groupID])
+
+ parentID := sa.groupParents[groupID]
+ parentStr := "root"
+ groupIDUint8, err := SafeToUint8(groupID)
+ if err != nil {
+ panic(err)
+ }
+ if parentID != groupIDUint8 {
+ parentStr = fmt.Sprintf("%d", parentID)
+ }
+ fmt.Printf("Parent group: %s\n", parentStr)
+
+ fmt.Printf("Direct members: %v\n", sa.groupMembers[byte(groupID)])
+ fmt.Printf("Child groups: %v\n", sa.getChildGroups(byte(groupID)))
+ }
+
+ possibleSigners := sa.getRequiredSigners(0)
+ signersList := make([]string, 0, len(possibleSigners))
+ for signer := range possibleSigners {
+ signersList = append(signersList, signer)
+ }
+
+ fmt.Printf("\nSignature Requirements:\n")
+ fmt.Printf("Root group (Group 0) requires %d signatures\n", sa.groupQuorums[0])
+ fmt.Printf("Possible signers: [%s]\n", strings.Join(signersList, ", "))
+}
diff --git a/chains/solana/contracts/tests/utils/mcms/merkle.go b/chains/solana/contracts/tests/utils/mcms/merkle.go
new file mode 100644
index 000000000..f479a5929
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/mcms/merkle.go
@@ -0,0 +1,404 @@
+package mcms
+
+import (
+ "bytes"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "strings"
+
+ "github.com/gagliardetto/solana-go"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config"
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+)
+
+// Note: mcms tree reference can be found in https://github.com/smartcontractkit/mcms/blob/main/internal/core/merkle/tree.go
+
+type MerkleNode interface {
+ Hash() [32]byte
+ Buffers() [][]byte
+ Print(padding int)
+ Size() int
+ Parent() *OpMerkleTree
+ SetParent(t *OpMerkleTree)
+ Proofs() ([][32]byte, error)
+ Leaves() []MerkleNode
+}
+
+type BaseNode struct {
+ parent *OpMerkleTree
+ size int
+}
+
+func (b *BaseNode) SetParent(p *OpMerkleTree) { b.parent = p }
+
+func (b *BaseNode) Parent() *OpMerkleTree {
+ return b.parent
+}
+
+func (b *BaseNode) Size() int {
+ return 1
+}
+
+type McmOpNode struct {
+ BaseNode
+ Nonce uint64
+ Data []byte
+ Multisig solana.PublicKey // this is config PDA
+ To solana.PublicKey
+ RemainingAccounts []*solana.AccountMeta
+}
+
+func (t *McmOpNode) Buffers() [][]byte {
+ hashBytes := eth.Keccak256([]byte("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP"))
+ buffers := [][]byte{
+ hashBytes[:],
+ config.TestChainIDPaddedBuffer[:],
+ t.Multisig.Bytes(),
+ numToU64LePaddedEncoding(t.Nonce),
+ t.To.Bytes(),
+ numToU64LePaddedEncoding(uint64(len(t.Data))),
+ t.Data,
+ numToU64LePaddedEncoding(uint64(len(t.RemainingAccounts))),
+ }
+
+ for _, account := range t.RemainingAccounts {
+ buffers = append(buffers, serializeAccountMeta(account))
+ }
+
+ return buffers
+}
+
+func (t *McmOpNode) Hash() [32]byte {
+ return CalculateHash(t.Buffers())
+}
+
+func (t *McmOpNode) Leaves() []MerkleNode {
+ return []MerkleNode{t}
+}
+
+func (t *McmOpNode) Proofs() ([][32]byte, error) {
+ if t.Parent() == nil {
+ return nil, fmt.Errorf("cannot generate proof: TestOp is not part of a tree")
+ }
+ return GenerateProof(t)
+}
+
+func (t *McmOpNode) Print(padding int) {
+ for i := 0; i < padding; i++ {
+ fmt.Print("| ")
+ }
+ h := fmt.Sprintf("%x", t.Hash())
+ parentHash := "ROOT"
+ if t.parent != nil {
+ parentHash = fmt.Sprintf("%x", t.parent.Hash())
+ }
+ fmt.Printf("-> Op #%d %s - Child of %s\n", t.Nonce, h, parentHash)
+}
+
+type RootMetadataNode struct {
+ BaseNode
+ PreOpCount uint64
+ PostOpCount uint64
+ Multisig solana.PublicKey
+ OverridePreviousRoot bool
+}
+
+func (rm *RootMetadataNode) Buffers() [][]byte {
+ hashBytes := eth.Keccak256([]byte("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA"))
+ return [][]byte{
+ hashBytes[:],
+ config.TestChainIDPaddedBuffer[:],
+ rm.Multisig.Bytes(),
+ numToU64LePaddedEncoding(rm.PreOpCount),
+ numToU64LePaddedEncoding(rm.PostOpCount),
+ boolToPaddedEncoding(rm.OverridePreviousRoot),
+ }
+}
+
+func (rm *RootMetadataNode) Hash() [32]byte {
+ return CalculateHash(rm.Buffers())
+}
+
+func (rm *RootMetadataNode) Leaves() []MerkleNode {
+ return []MerkleNode{rm}
+}
+
+func (rm *RootMetadataNode) Proofs() ([][32]byte, error) {
+ if rm.Parent() == nil {
+ return nil, fmt.Errorf("cannot generate proof: RootMetadata is not part of a tree")
+ }
+ return GenerateProof(rm)
+}
+
+func (rm *RootMetadataNode) Print(padding int) {
+ for i := 0; i < padding; i++ {
+ fmt.Print("| ")
+ }
+ h := fmt.Sprintf("%x", rm.Hash())
+ parentHash := "ROOT"
+ if rm.parent != nil {
+ parentHash = fmt.Sprintf("%x", rm.parent.Hash())
+ }
+ fmt.Printf("-> Metadata %s - Child of %s\n", h, parentHash)
+}
+
+type OpMerkleTree struct {
+ BaseNode
+ Left MerkleNode
+ Right MerkleNode
+}
+
+func (o *OpMerkleTree) Size() int {
+ if o.Left == nil && o.Right == nil {
+ return 1
+ }
+ return o.Left.Size() + o.Right.Size()
+}
+
+func (o *OpMerkleTree) Buffers() [][]byte {
+ if o.Left == nil && o.Right == nil {
+ fmt.Println("Warning: OpMerkleTree: both Left and Right are nil")
+ return [][]byte{}
+ }
+ var leftHash, rightHash [32]byte
+ if o.Left != nil {
+ leftHash = o.Left.Hash()
+ }
+ if o.Right != nil {
+ rightHash = o.Right.Hash()
+ }
+ return [][]byte{leftHash[:], rightHash[:]}
+}
+
+func (o *OpMerkleTree) Hash() [32]byte {
+ return CalculateHash(o.Buffers())
+}
+
+func (o *OpMerkleTree) EthMsgHash(validUntil uint32) []byte {
+ hash := o.Hash()
+ hashedEncodedParams := eth.Keccak256(append(hash[:], numToU64BePaddedEncoding(uint64(validUntil))...))
+ return eth.Keccak256(append([]byte("\x19Ethereum Signed Message:\n32"), hashedEncodedParams[:]...))
+}
+
+func (o *OpMerkleTree) Leaves() []MerkleNode {
+ if o == nil {
+ return []MerkleNode{}
+ }
+ if o.Left == nil && o.Right == nil {
+ return []MerkleNode{o}
+ }
+ var leaves []MerkleNode
+ if o.Left != nil {
+ leaves = append(leaves, o.Left.Leaves()...)
+ }
+ if o.Right != nil {
+ leaves = append(leaves, o.Right.Leaves()...)
+ }
+ return leaves
+}
+
+func (o *OpMerkleTree) Proofs() ([][32]byte, error) {
+ var proofs [][32]byte
+ for current := o; current.Parent() != nil; current = current.Parent() {
+ sibling, err := current.Parent().FindSiblingOf(current)
+ if err != nil {
+ return nil, fmt.Errorf("failed to find sibling: %w", err)
+ }
+ proofs = append(proofs, sibling.Hash())
+ }
+ return proofs, nil
+}
+
+func (o *OpMerkleTree) FindSiblingOf(n MerkleNode) (MerkleNode, error) {
+ if Eq(o.Left, n) {
+ return o.Right, nil
+ }
+ if Eq(o.Right, n) {
+ return o.Left, nil
+ }
+ return nil, fmt.Errorf("node is not a direct child of this tree")
+}
+
+func (o *OpMerkleTree) Print(padding int) {
+ for i := 0; i < padding; i++ {
+ fmt.Print("| ")
+ }
+ h := fmt.Sprintf("%x", o.Hash())
+ parentHash := "ROOT"
+ if o.parent != nil {
+ parentHash = fmt.Sprintf("%x", o.parent.Hash())
+ }
+ fmt.Printf("Node %d %s - Child of %s\n", o.size, h, parentHash)
+ if o.Left != nil {
+ o.Left.Print(padding + 1)
+ }
+ if o.Right != nil {
+ o.Right.Print(padding + 1)
+ }
+}
+
+func NewOpMerkleTree(nodes []MerkleNode) (MerkleNode, error) {
+ switch len(nodes) {
+ case 0:
+ return nil, fmt.Errorf("cannot create tree with no nodes")
+ case 1:
+ return nodes[0], nil
+ default:
+ pivot := (len(nodes) + 1) / 2
+
+ left, err := NewOpMerkleTree(nodes[:pivot])
+ if err != nil {
+ return nil, fmt.Errorf("failed to create left subtree: %w", err)
+ }
+
+ right, err := NewOpMerkleTree(nodes[pivot:])
+ if err != nil {
+ return nil, fmt.Errorf("failed to create right subtree: %w", err)
+ }
+
+ tree := &OpMerkleTree{
+ Left: left,
+ Right: right,
+ }
+
+ if !Lt(left, right) {
+ tree.Left, tree.Right = right, left
+ }
+
+ tree.Left.SetParent(tree)
+ tree.Right.SetParent(tree)
+
+ return tree, nil
+ }
+}
+
+func numToU64LePaddedEncoding(n uint64) []byte {
+ b := make([]byte, 32)
+ binary.LittleEndian.PutUint64(b[24:], n)
+ return b
+}
+
+func numToU64BePaddedEncoding(n uint64) []byte {
+ b := make([]byte, 32)
+ binary.BigEndian.PutUint64(b[24:], n)
+ return b
+}
+
+func boolToPaddedEncoding(b bool) []byte {
+ result := make([]byte, 32)
+ if b {
+ result[31] = 1
+ }
+ return result
+}
+
+func serializeAccountMeta(a *solana.AccountMeta) []byte {
+ var flags byte
+ if a.IsSigner {
+ flags |= 0b10
+ }
+ if a.IsWritable {
+ flags |= 0b01
+ }
+ result := append(a.PublicKey.Bytes(), flags)
+ return result
+}
+
+func Lt(a, b MerkleNode) bool {
+ hashA := a.Hash()
+ hashB := b.Hash()
+ return bytes.Compare(hashA[:], hashB[:]) < 0
+}
+
+func Eq(a, b MerkleNode) bool {
+ hashA := a.Hash()
+ hashB := b.Hash()
+ return bytes.Equal(hashA[:], hashB[:])
+}
+
+func CalculateHash(buffers [][]byte) [32]byte {
+ hash := eth.Keccak256(bytes.Join(buffers, nil))
+ var hash32 [32]byte
+ copy(hash32[:], hash)
+ return hash32
+}
+
+func GenerateProof(node MerkleNode) ([][32]byte, error) {
+ var proofs [][32]byte
+ for current := node; current.Parent() != nil; current = current.Parent() {
+ sibling, err := current.Parent().FindSiblingOf(current)
+ if err != nil {
+ return nil, fmt.Errorf("failed to find sibling: %w", err)
+ }
+ proofs = append(proofs, sibling.Hash())
+ }
+ return proofs, nil
+}
+
+func ConvertProof(proof [][]byte) ([][32]uint8, error) {
+ fixedProof := make([][32]uint8, len(proof))
+ for i, p := range proof {
+ if len(p) != 32 {
+ return nil, fmt.Errorf("proof element %d is not 32 bytes long", i)
+ }
+ copy(fixedProof[i][:], p)
+ }
+ return fixedProof, nil
+}
+
+// dump function for debugging
+func DumpOpDetails(op *McmOpNode) string {
+ var sb strings.Builder
+
+ sb.WriteString("Operation Details:\n")
+ sb.WriteString("=================\n\n")
+
+ // Print basic info
+ sb.WriteString(fmt.Sprintf("Nonce: %d\n", op.Nonce))
+ sb.WriteString(fmt.Sprintf("Multisig: %s\n", op.Multisig.String()))
+ sb.WriteString(fmt.Sprintf("To: %s\n", op.To.String()))
+
+ // Print raw buffers
+ sb.WriteString("Raw Buffers:\n")
+ buffers := op.Buffers()
+ for i, buf := range buffers {
+ sb.WriteString(fmt.Sprintf("Buffer[%d]: %s\n", i, hex.EncodeToString(buf)))
+ }
+ sb.WriteString("\n")
+
+ // Print execution data
+ sb.WriteString(fmt.Sprintf("Execution Data: %s\n\n", hex.EncodeToString(op.Data)))
+
+ // Print hash
+ hash := op.Hash()
+ sb.WriteString(fmt.Sprintf("Leaf Hash: %s\n", hex.EncodeToString(hash[:])))
+
+ // Print Merkle proofs if available
+ if op.Parent() != nil {
+ proofs, err := op.Proofs()
+ if err == nil {
+ sb.WriteString("\nMerkle Proofs:\n")
+ for i, proof := range proofs {
+ sb.WriteString(fmt.Sprintf("Proof[%d]: %s\n", i, hex.EncodeToString(proof[:])))
+ }
+ }
+ }
+
+ // Print remaining accounts if any
+ if len(op.RemainingAccounts) > 0 {
+ sb.WriteString("\nRemaining Accounts:\n")
+ for i, acc := range op.RemainingAccounts {
+ sb.WriteString(fmt.Sprintf("Account[%d]:\n", i))
+ sb.WriteString(fmt.Sprintf(" Address: %s\n", acc.PublicKey.String()))
+ sb.WriteString(fmt.Sprintf(" Is Signer: %v\n", acc.IsSigner))
+ sb.WriteString(fmt.Sprintf(" Is Writable: %v\n", acc.IsWritable))
+ // Print serialized form for verification
+ sb.WriteString(fmt.Sprintf(" Serialized: %s\n",
+ hex.EncodeToString(serializeAccountMeta(acc))))
+ }
+ }
+
+ return sb.String()
+}
diff --git a/chains/solana/contracts/tests/utils/mcms/merkle_test.go b/chains/solana/contracts/tests/utils/mcms/merkle_test.go
new file mode 100644
index 000000000..89f512222
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/mcms/merkle_test.go
@@ -0,0 +1,174 @@
+package mcms
+
+import (
+ "testing"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" // todo: make utils pure
+)
+
+func TestMcmMerkle(t *testing.T) {
+ multisig := config.McmProgram
+ to := config.ExternalCpiStubProgram
+
+ op := &McmOpNode{
+ Nonce: 1,
+ Data: []byte("test data"),
+ Multisig: multisig,
+ To: to,
+ RemainingAccounts: []*solana.AccountMeta{
+ {PublicKey: solana.NewWallet().PublicKey(), IsSigner: true, IsWritable: false},
+ },
+ }
+
+ t.Run("Buffers", func(t *testing.T) {
+ buffers := op.Buffers()
+ assert.Equal(t, 9, len(buffers), "Expected 9 buffers")
+ })
+
+ t.Run("Hash", func(t *testing.T) {
+ assert.Equal(t, 32, len(op.Hash()), "Expected hash length of 32 bytes")
+ })
+
+ t.Run("Leaves", func(t *testing.T) {
+ leaves := op.Leaves()
+ assert.Equal(t, 1, len(leaves), "Expected 1 leaf")
+ assert.Equal(t, op, leaves[0], "Expected leaf to be the op itself")
+ })
+}
+
+func TestMcmRootMetadata(t *testing.T) {
+ multisig := config.McmProgram
+
+ metadata := &RootMetadataNode{
+ PreOpCount: 10,
+ PostOpCount: 15,
+ Multisig: multisig,
+ OverridePreviousRoot: true,
+ }
+
+ t.Run("Buffers", func(t *testing.T) {
+ buffers := metadata.Buffers()
+ assert.Equal(t, 6, len(buffers), "Expected 6 buffers")
+ })
+
+ t.Run("Hash", func(t *testing.T) {
+ assert.Equal(t, 32, len(metadata.Hash()), "Expected hash length of 32 bytes")
+ })
+}
+
+func TestMcmOpMerkleTree(t *testing.T) {
+ multisig := config.McmProgram
+ to := config.ExternalCpiStubProgram
+
+ op1 := &McmOpNode{Nonce: 1, Data: []byte("data1"), Multisig: multisig, To: to}
+ op2 := &McmOpNode{Nonce: 2, Data: []byte("data2"), Multisig: multisig, To: to}
+ op3 := &McmOpNode{Nonce: 3, Data: []byte("data3"), Multisig: multisig, To: to}
+
+ t.Run("NewOpMerkleTree", func(t *testing.T) {
+ tree, err := NewOpMerkleTree([]MerkleNode{op1, op2, op3})
+ require.NoError(t, err)
+ assert.Equal(t, 3, tree.Size(), "Expected tree size of 3")
+ })
+
+ t.Run("EthMsgHash", func(t *testing.T) {
+ tree, err := NewOpMerkleTree([]MerkleNode{op1, op2})
+ require.NoError(t, err)
+ hash := tree.(*OpMerkleTree).EthMsgHash(1234)
+ assert.Equal(t, 32, len(hash), "Expected hash length of 32 bytes")
+ })
+
+ t.Run("Proofs", func(t *testing.T) {
+ _, err := NewOpMerkleTree([]MerkleNode{op1, op2, op3})
+ require.NoError(t, err)
+ proofs, err := op1.Proofs()
+ require.NoError(t, err)
+ assert.Equal(t, 2, len(proofs), "Expected 2 proof hashes for a tree with 3 nodes")
+ })
+}
+
+func TestMcmHelperFunctions(t *testing.T) {
+ t.Run("numToU64LePaddedEncoding", func(t *testing.T) {
+ result := numToU64LePaddedEncoding(42)
+ assert.Equal(t, 32, len(result), "Expected 32-byte result")
+ assert.Equal(t, byte(42), result[24], "Expected 42 in the correct position")
+ })
+
+ t.Run("boolToPaddedEncoding", func(t *testing.T) {
+ trueResult := boolToPaddedEncoding(true)
+ falseResult := boolToPaddedEncoding(false)
+ assert.Equal(t, byte(1), trueResult[31], "Expected 1 for true")
+ assert.Equal(t, byte(0), falseResult[31], "Expected 0 for false")
+ })
+
+ t.Run("serializeAccountMeta", func(t *testing.T) {
+ account := solana.AccountMeta{
+ PublicKey: solana.NewWallet().PublicKey(),
+ IsSigner: true,
+ IsWritable: false,
+ }
+ result := serializeAccountMeta(&account)
+ assert.Equal(t, 33, len(result), "Expected 33-byte result")
+ assert.Equal(t, byte(0b10), result[32], "Expected correct flags")
+ })
+
+ t.Run("CalculateHash", func(t *testing.T) {
+ buffers := [][]byte{[]byte("test1"), []byte("test2")}
+ assert.Equal(t, 32, len(CalculateHash(buffers)), "Expected 32-byte hash")
+ })
+}
+
+func TestMcmMerkleNodeInterface(t *testing.T) {
+ multisig := config.McmProgram
+ to := config.ExternalCpiStubProgram
+
+ op := &McmOpNode{Nonce: 1, Data: []byte("test"), Multisig: multisig, To: to}
+ metadata := &RootMetadataNode{PreOpCount: 1, PostOpCount: 2, Multisig: multisig}
+
+ nodes := []MerkleNode{op, metadata}
+
+ for _, node := range nodes {
+ t.Run("MerkleNode Interface", func(t *testing.T) {
+ assert.NotNil(t, node.Hash(), "Hash should not be nil")
+ assert.NotEmpty(t, node.Buffers(), "Buffers should not be empty")
+ assert.Greater(t, node.Size(), 0, "Size should be greater than 0")
+ assert.NotNil(t, node.Leaves(), "Leaves should not be nil")
+
+ proofs, err := node.Proofs()
+ assert.Error(t, err, "Proofs should return an error for standalone nodes")
+ assert.Nil(t, proofs, "Proofs should be nil for standalone nodes")
+ })
+ }
+
+ // Test proofs for nodes in a tree
+ tree, err := NewOpMerkleTree(nodes)
+ require.NoError(t, err, "Failed to create tree")
+
+ for _, node := range tree.Leaves() {
+ t.Run("MerkleNode in Tree", func(t *testing.T) {
+ proofs, err := node.Proofs()
+ assert.NoError(t, err, "Proofs should not return an error for nodes in a tree")
+ assert.NotEmpty(t, proofs, "Proofs should not be empty for nodes in a tree")
+ })
+ }
+}
+
+func TestMcmMerkleTreeOrdering(t *testing.T) {
+ multisig := config.McmProgram
+ to := config.ExternalCpiStubProgram
+
+ op1 := &McmOpNode{Nonce: 1, Data: []byte("data1"), Multisig: multisig, To: to}
+ op2 := &McmOpNode{Nonce: 2, Data: []byte("data2"), Multisig: multisig, To: to}
+
+ tree, err := NewOpMerkleTree([]MerkleNode{op1, op2})
+ require.NoError(t, err)
+
+ opTree, ok := tree.(*OpMerkleTree)
+ require.True(t, ok, "Expected OpMerkleTree type")
+
+ assert.True(t, Lt(opTree.Left, opTree.Right), "Left node should be less than right node")
+ assert.False(t, Eq(opTree.Left, opTree.Right), "Left and right nodes should not be equal")
+}
diff --git a/chains/solana/contracts/tests/utils/mcms/timelock.go b/chains/solana/contracts/tests/utils/mcms/timelock.go
new file mode 100644
index 000000000..d0917db94
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/mcms/timelock.go
@@ -0,0 +1,85 @@
+package mcms
+
+import (
+ crypto_rand "crypto/rand"
+ "math/big"
+ "testing"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
+)
+
+// test helper with normal accounts
+type RoleMap map[timelock.Role]RoleAccounts
+
+type RoleAccounts struct {
+ Role timelock.Role
+ Accounts []solana.PrivateKey
+ AccessController solana.PrivateKey
+}
+
+func (r RoleAccounts) RandomPick() solana.PrivateKey {
+ if len(r.Accounts) == 0 {
+ panic("no accounts to pick from")
+ }
+
+ maxN := big.NewInt(int64(len(r.Accounts)))
+ n, err := crypto_rand.Int(crypto_rand.Reader, maxN)
+ if err != nil {
+ panic(err)
+ }
+
+ return r.Accounts[n.Int64()]
+}
+
+func TestRoleAccounts(t *testing.T, numAccounts int) ([]RoleAccounts, RoleMap) {
+ roles := []RoleAccounts{
+ {
+ Role: timelock.Proposer_Role,
+ Accounts: createRoleAccounts(t, numAccounts),
+ AccessController: getPrivateKey(t),
+ },
+ {
+ Role: timelock.Executor_Role,
+ Accounts: createRoleAccounts(t, numAccounts),
+ AccessController: getPrivateKey(t),
+ },
+ {
+ Role: timelock.Canceller_Role,
+ Accounts: createRoleAccounts(t, numAccounts),
+ AccessController: getPrivateKey(t),
+ },
+ {
+ Role: timelock.Bypasser_Role,
+ Accounts: createRoleAccounts(t, numAccounts),
+ AccessController: getPrivateKey(t),
+ },
+ }
+
+ roleMap := make(RoleMap)
+ for _, role := range roles {
+ roleMap[role.Role] = role
+ }
+ return roles, roleMap
+}
+
+func createRoleAccounts(t *testing.T, num int) []solana.PrivateKey {
+ if num < 1 || num > 64 {
+ panic("num should be between 1 and 64")
+ }
+ accounts := make([]solana.PrivateKey, num)
+ for i := 0; i < num; i++ {
+ account, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+ accounts[i] = account
+ }
+ return accounts
+}
+
+func getPrivateKey(t *testing.T) solana.PrivateKey {
+ key, err := solana.NewRandomPrivateKey()
+ require.NoError(t, err)
+ return key
+}
diff --git a/chains/solana/contracts/tests/utils/ocr.go b/chains/solana/contracts/tests/utils/ocr.go
new file mode 100644
index 000000000..59695a2bf
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/ocr.go
@@ -0,0 +1,67 @@
+package utils
+
+import (
+ cryptorand "crypto/rand"
+ "fmt"
+ "math/big"
+ "testing"
+
+ "github.com/gagliardetto/solana-go"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/eth"
+)
+
+type OcrPlugin uint8
+
+func (p OcrPlugin) String() string {
+ switch p {
+ case OcrCommitPlugin:
+ return "CommitPlugin"
+ case OcrExecutePlugin:
+ return "ExecutePlugin"
+ }
+ return "INVALID"
+}
+
+const (
+ OcrCommitPlugin OcrPlugin = iota
+ OcrExecutePlugin
+)
+
+func GenerateSignersAndTransmitters(t *testing.T, maxOracles int) ([]eth.Signer, []solana.PrivateKey, func() solana.PrivateKey) {
+ signers, err := generateUnique(maxOracles, func() (eth.Signer, error) {
+ ks, err := eth.GenerateEthPrivateKeys(1)
+ if err != nil {
+ return eth.Signer{}, err
+ }
+ return eth.GetSignerFromPk(ks[0])
+ })
+ require.NoError(t, err)
+ transmitters, err := generateUnique(maxOracles, solana.NewRandomPrivateKey)
+ require.NoError(t, err)
+ getTransmitter := func() solana.PrivateKey {
+ index, err := cryptorand.Int(cryptorand.Reader, big.NewInt(int64(len(transmitters))))
+ require.NoError(t, err)
+ return transmitters[index.Int64()]
+ }
+ return signers, transmitters, getTransmitter
+}
+
+func generateUnique[T fmt.Stringer](count int, generator func() (T, error)) ([]T, error) {
+ out := []T{}
+ seen := map[string]bool{}
+
+ for len(out) < count {
+ v, err := generator()
+ if err != nil {
+ return nil, err
+ }
+
+ if _, exists := seen[v.String()]; !exists {
+ out = append(out, v)
+ seen[v.String()] = true
+ }
+ }
+ return out, nil
+}
diff --git a/chains/solana/contracts/tests/utils/offchainConfig.go b/chains/solana/contracts/tests/utils/offchainConfig.go
new file mode 100644
index 000000000..5853a013a
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/offchainConfig.go
@@ -0,0 +1,9 @@
+package utils
+
+func ChunkSlice(items []byte, chunkSize int) (chunks [][]byte) {
+ for chunkSize < len(items) {
+ chunks = append(chunks, items[0:chunkSize])
+ items = items[chunkSize:]
+ }
+ return append(chunks, items)
+}
diff --git a/chains/solana/contracts/tests/utils/project_path.go b/chains/solana/contracts/tests/utils/project_path.go
new file mode 100644
index 000000000..b06d36d93
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/project_path.go
@@ -0,0 +1,14 @@
+package utils
+
+import (
+ "path/filepath"
+ "runtime"
+)
+
+var (
+ _, b, _, _ = runtime.Caller(0)
+ // ProjectRoot Root folder of this project
+ ProjectRoot = filepath.Join(filepath.Dir(b), "/../..")
+ // ContractsDir path to our contracts
+ ContractsDir = filepath.Join(ProjectRoot, "target", "deploy")
+)
diff --git a/chains/solana/contracts/tests/utils/token.go b/chains/solana/contracts/tests/utils/token.go
new file mode 100644
index 000000000..c3288c63e
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/token.go
@@ -0,0 +1,153 @@
+package utils
+
+import (
+ "context"
+ "encoding/binary"
+ "fmt"
+ "strconv"
+
+ "github.com/gagliardetto/solana-go"
+ ata "github.com/gagliardetto/solana-go/programs/associated-token-account"
+ "github.com/gagliardetto/solana-go/programs/system"
+ "github.com/gagliardetto/solana-go/programs/token"
+ "github.com/gagliardetto/solana-go/rpc"
+)
+
+// parallel test execution can cause race conditions for setting the token program in the solana_go SDK
+// the race occurs when multiple programs attempt to utilize the solana_go/programs/token and set different token programs to build transactions from
+// the SDK uses a global program ID that is called via ProgramID(): https://github.com/gagliardetto/solana-go/blob/da2193071f56059aa35010a239cece016c4e827f/programs/token/instructions.go#L310
+// this is called when the transaction is assembled from multiple instructions (not in ValidateAndBuild) - so it is not static until NewTransaction() is called
+var _ solana.Instruction = (*TokenInstruction)(nil)
+
+// TokenInstruction wraps the base token instruction and provides the requested ProgramID rather than depending on the SDK global
+type TokenInstruction struct {
+ solana.Instruction
+ Program solana.PublicKey
+}
+
+// ProgramID overrides the default solana.Instruction.ProgramID behavior
+func (inst *TokenInstruction) ProgramID() solana.PublicKey {
+ return inst.Program
+}
+
+// NOTE: functions in this file are mainly wrapped version of the versions that exist in `solana-go` but these allow specifying the token program
+
+func CreateToken(ctx context.Context, program, mint, admin solana.PublicKey, decimals uint8, client *rpc.Client, commitment rpc.CommitmentType) ([]solana.Instruction, error) {
+ // get stake amount for init
+ lamports, err := client.GetMinimumBalanceForRentExemption(ctx, token.MINT_SIZE, commitment)
+ if err != nil {
+ return nil, err
+ }
+
+ // initialize mint account
+ initI, err := system.NewCreateAccountInstruction(lamports, token.MINT_SIZE, program, admin, mint).ValidateAndBuild()
+ if err != nil {
+ return nil, err
+ }
+
+ // initialize mint
+ mintI, err := token.NewInitializeMintInstruction(decimals, admin, admin, mint, solana.SysVarRentPubkey).ValidateAndBuild()
+ if err != nil {
+ return nil, err
+ }
+
+ mintWrap := &TokenInstruction{mintI, program}
+ return []solana.Instruction{initI, mintWrap}, nil
+}
+
+var AssociatedTokenProgramID solana.PublicKey = ata.ProgramID
+
+func CreateAssociatedTokenAccount(tokenProgram, mint, address, payer solana.PublicKey) (ins solana.Instruction, ataAddress solana.PublicKey, err error) {
+ base := ata.NewCreateInstruction(payer, address, mint)
+ ix, err := base.ValidateAndBuild()
+ if err != nil {
+ return nil, solana.PublicKey{}, err
+ }
+
+ associatedTokenAddress, _, err := FindAssociatedTokenAddress(tokenProgram, mint, address)
+ if err != nil {
+ return nil, solana.PublicKey{}, err
+ }
+
+ accounts := ix.Accounts()
+ // replace token program with specified tokenProgram
+ accounts[5].PublicKey = tokenProgram
+ // replace associated token account with the new one derived with the proper program
+ accounts[1].PublicKey = associatedTokenAddress
+
+ // set into instruction
+ base.AccountMetaSlice = accounts
+ ix.Impl = base
+ return ix, associatedTokenAddress, nil
+}
+
+func FindAssociatedTokenAddress(tokenProgram, mint, address solana.PublicKey) (addr solana.PublicKey, bump uint8, err error) {
+ // NOTE: Do not use `solana.FindAssociatedTokenAddress` as it hardcodes the token program ID to the pre-2022 one.
+ // So, instead, manually construct the seeds for associated token addresses.
+ return solana.FindProgramAddress([][]byte{
+ address[:],
+ tokenProgram[:],
+ mint[:],
+ },
+ solana.SPLAssociatedTokenAccountProgramID,
+ )
+}
+
+func MintTo(amount uint64, program, mint, toAddress, authority solana.PublicKey) (solana.Instruction, error) {
+ ix, err := token.NewMintToInstruction(amount, mint, toAddress, authority, nil).ValidateAndBuild()
+ return &TokenInstruction{ix, program}, err
+}
+
+func TokenTransferChecked(amount uint64, decimals uint8, program, source, mint, destination, owner solana.PublicKey, multisigSigners []solana.PublicKey) (solana.Instruction, error) {
+ ix, err := token.NewTransferCheckedInstruction(amount, decimals, source, mint, destination, owner, multisigSigners).ValidateAndBuild()
+ return &TokenInstruction{ix, program}, err
+}
+
+func SetTokenMintAuthority(program, newAuth, mint, signer solana.PublicKey) (solana.Instruction, error) {
+ ix, err := token.NewSetAuthorityInstruction(token.AuthorityMintTokens, newAuth, mint, signer, solana.PublicKeySlice{}).ValidateAndBuild()
+ return &TokenInstruction{ix, program}, err
+}
+
+func TokenApproveChecked(amount uint64, decimals uint8, program, source, mint, delegate, owner solana.PublicKey, multisigSigners []solana.PublicKey) (solana.Instruction, error) {
+ ix, err := token.NewApproveCheckedInstruction(amount, decimals, source, mint, delegate, owner, multisigSigners).ValidateAndBuild()
+ return &TokenInstruction{ix, program}, err
+}
+
+func TokenSupply(ctx context.Context, client *rpc.Client, mint solana.PublicKey, commitment rpc.CommitmentType) (uint8, int, error) {
+ res, err := client.GetTokenSupply(ctx, mint, commitment)
+ if err != nil {
+ return 0, 0, err
+ }
+ if res == nil || res.Value == nil {
+ return 0, 0, fmt.Errorf("rpc returned nil")
+ }
+ v, err := strconv.Atoi(res.Value.Amount)
+ return res.Value.Decimals, v, err
+}
+
+func TokenBalance(ctx context.Context, client *rpc.Client, acc solana.PublicKey, commitment rpc.CommitmentType) (uint8, int, error) {
+ res, err := client.GetTokenAccountBalance(ctx, acc, commitment)
+ if err != nil {
+ return 0, 0, err
+ }
+ if res == nil || res.Value == nil {
+ return 0, 0, fmt.Errorf("rpc returned nil")
+ }
+ v, err := strconv.Atoi(res.Value.Amount)
+ return res.Value.Decimals, v, err
+}
+
+func NativeTransfer(program solana.PublicKey, lamports uint64, from solana.PublicKey, to solana.PublicKey) (solana.Instruction, error) {
+ return system.NewTransferInstruction(lamports, from, to).ValidateAndBuild()
+}
+
+func SyncNative(program solana.PublicKey, tokenAccount solana.PublicKey) (solana.Instruction, error) {
+ ix, err := token.NewSyncNativeInstruction(tokenAccount).ValidateAndBuild()
+ return &TokenInstruction{ix, program}, err
+}
+
+func ToLittleEndianU256(v uint64) [32]byte {
+ out := [32]byte{}
+ binary.LittleEndian.PutUint64(out[:], v)
+ return out
+}
diff --git a/chains/solana/contracts/tests/utils/transactions.go b/chains/solana/contracts/tests/utils/transactions.go
new file mode 100644
index 000000000..3f26e8360
--- /dev/null
+++ b/chains/solana/contracts/tests/utils/transactions.go
@@ -0,0 +1,259 @@
+package utils
+
+import (
+ "context"
+ "encoding/base64"
+ "fmt"
+ "strings"
+ "testing"
+ "time"
+
+ "strconv"
+
+ bin "github.com/gagliardetto/binary"
+ "github.com/gagliardetto/solana-go"
+ "github.com/gagliardetto/solana-go/rpc"
+ "github.com/stretchr/testify/require"
+
+ "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/utils/fees"
+)
+
+func SendAndConfirm(ctx context.Context, t *testing.T, rpcClient *rpc.Client, instructions []solana.Instruction,
+ signer solana.PrivateKey, commitment rpc.CommitmentType, opts ...TxModifier) *rpc.GetTransactionResult {
+ emptyLookupTables := map[solana.PublicKey]solana.PublicKeySlice{}
+ txres := sendTransactionWithLookupTables(ctx, rpcClient, t, instructions, signer, commitment, false, emptyLookupTables, opts...) // do not skipPreflight when expected to pass, preflight can help debug
+
+ require.NotNil(t, txres.Meta)
+ require.Nil(t, txres.Meta.Err, fmt.Sprintf("tx failed with: %+v", txres.Meta)) // tx should not err, print meta if it does (contains logs)
+ return txres
+}
+
+func SendAndConfirmWithLookupTables(ctx context.Context, t *testing.T, rpcClient *rpc.Client, instructions []solana.Instruction,
+ signer solana.PrivateKey, commitment rpc.CommitmentType, lookupTables map[solana.PublicKey]solana.PublicKeySlice, opts ...TxModifier) *rpc.GetTransactionResult {
+ txres := sendTransactionWithLookupTables(ctx, rpcClient, t, instructions, signer, commitment, false, lookupTables, opts...) // do not skipPreflight when expected to pass, preflight can help debug
+
+ require.NotNil(t, txres.Meta)
+ require.Nil(t, txres.Meta.Err, fmt.Sprintf("tx failed with: %+v", txres.Meta)) // tx should not err, print meta if it does (contains logs)
+ return txres
+}
+
+func SendAndFailWith(ctx context.Context, t *testing.T, rpcClient *rpc.Client, instructions []solana.Instruction,
+ signer solana.PrivateKey, commitment rpc.CommitmentType, expectedErrors []string, opts ...TxModifier) *rpc.GetTransactionResult {
+ emptyLookupTables := map[solana.PublicKey]solana.PublicKeySlice{}
+ txres := sendTransactionWithLookupTables(ctx, rpcClient, t, instructions, signer, commitment, true, emptyLookupTables, opts...) // skipPreflight when expected to fail so revert captured onchain
+
+ require.NotNil(t, txres.Meta)
+ require.NotNil(t, txres.Meta.Err)
+ logs := strings.Join(txres.Meta.LogMessages, " ")
+ for _, expectedError := range expectedErrors {
+ require.Contains(t, logs, expectedError, fmt.Sprintf("The logs did not contain '%s'. The logs were: %s", expectedError, logs))
+ }
+ return txres
+}
+
+func SendAndFailWithLookupTables(ctx context.Context, t *testing.T, rpcClient *rpc.Client, instructions []solana.Instruction,
+ signer solana.PrivateKey, commitment rpc.CommitmentType, lookupTables map[solana.PublicKey]solana.PublicKeySlice, expectedErrors []string, opts ...TxModifier) *rpc.GetTransactionResult {
+ txres := sendTransactionWithLookupTables(ctx, rpcClient, t, instructions, signer, commitment, true, lookupTables, opts...) // skipPreflight when expected to fail so revert captured onchain
+
+ require.NotNil(t, txres.Meta)
+ require.NotNil(t, txres.Meta.Err)
+ logs := strings.Join(txres.Meta.LogMessages, " ")
+ for _, expectedError := range expectedErrors {
+ require.Contains(t, logs, expectedError, fmt.Sprintf("The logs did not contain '%s'. The logs were: %s", expectedError, logs))
+ }
+ return txres
+}
+
+func SendAndFailWithRPCError(ctx context.Context, t *testing.T, rpcClient *rpc.Client, instructions []solana.Instruction,
+ signer solana.PrivateKey, commitment rpc.CommitmentType, expectedErrors []string) {
+ hashRes, err := rpcClient.GetLatestBlockhash(ctx, rpc.CommitmentFinalized)
+ require.NoError(t, err)
+
+ tx, err := solana.NewTransaction(
+ instructions,
+ hashRes.Value.Blockhash,
+ solana.TransactionPayer(signer.PublicKey()),
+ )
+ require.NoError(t, err)
+
+ _, err = tx.Sign(func(_ solana.PublicKey) *solana.PrivateKey {
+ return &signer
+ })
+ require.NoError(t, err)
+
+ _, err = rpcClient.SendTransactionWithOpts(ctx, tx, rpc.TransactionOpts{SkipPreflight: false, PreflightCommitment: rpc.CommitmentProcessed})
+ require.NotNil(t, err)
+
+ errStr := err.Error()
+
+ for _, expectedError := range expectedErrors {
+ require.Contains(t, errStr, expectedError)
+ }
+}
+
+// TxModifier is a dynamic function used to flexibly add components to a transaction such as additional signers, and compute budget parameters
+type TxModifier func(tx *solana.Transaction, signers map[solana.PublicKey]solana.PrivateKey) error
+
+func AddSigners(additionalSigners ...solana.PrivateKey) TxModifier {
+ return func(_ *solana.Transaction, s map[solana.PublicKey]solana.PrivateKey) error {
+ for _, v := range additionalSigners {
+ s[v.PublicKey()] = v
+ }
+ return nil
+ }
+}
+
+// AddComputeUnitLimit allows for configuring the total compute unit limit for a transaction - solana network default is 200K, maximum is 1.4M
+// signature verification compute units can vary depending on searching for signatures
+func AddComputeUnitLimit(v fees.ComputeUnitLimit) TxModifier {
+ return func(tx *solana.Transaction, _ map[solana.PublicKey]solana.PrivateKey) error {
+ return fees.SetComputeUnitLimit(tx, v)
+ }
+}
+
+func sendTransactionWithLookupTables(ctx context.Context, rpcClient *rpc.Client, t *testing.T, instructions []solana.Instruction,
+ signerAndPayer solana.PrivateKey, commitment rpc.CommitmentType, skipPreflight bool, lookupTables map[solana.PublicKey]solana.PublicKeySlice, opts ...TxModifier) *rpc.GetTransactionResult {
+ hashRes, err := rpcClient.GetLatestBlockhash(ctx, rpc.CommitmentFinalized)
+ require.NoError(t, err)
+
+ tx, err := solana.NewTransaction(
+ instructions,
+ hashRes.Value.Blockhash,
+ solana.TransactionAddressTables(lookupTables),
+ solana.TransactionPayer(signerAndPayer.PublicKey()),
+ )
+
+ require.NoError(t, err)
+
+ // build signers map
+ signers := map[solana.PublicKey]solana.PrivateKey{}
+ signers[signerAndPayer.PublicKey()] = signerAndPayer
+
+ // set options before signing transaction
+ for _, o := range opts {
+ require.NoError(t, o(tx, signers))
+ }
+
+ _, err = tx.Sign(func(pub solana.PublicKey) *solana.PrivateKey {
+ priv, ok := signers[pub]
+ require.True(t, ok, fmt.Sprintf("Missing signer private key for %s", pub))
+ return &priv
+ })
+ require.NoError(t, err)
+
+ txsig, err := rpcClient.SendTransactionWithOpts(ctx, tx, rpc.TransactionOpts{SkipPreflight: skipPreflight, PreflightCommitment: rpc.CommitmentProcessed})
+ require.NoError(t, err)
+
+ var txStatus rpc.ConfirmationStatusType
+ count := 0
+ for txStatus != rpc.ConfirmationStatusConfirmed && txStatus != rpc.ConfirmationStatusFinalized {
+ count++
+ statusRes, sigErr := rpcClient.GetSignatureStatuses(ctx, true, txsig)
+ require.NoError(t, sigErr)
+ if statusRes != nil && len(statusRes.Value) > 0 && statusRes.Value[0] != nil {
+ txStatus = statusRes.Value[0].ConfirmationStatus
+ }
+ time.Sleep(50 * time.Millisecond)
+ if count > 500 {
+ require.NoError(t, fmt.Errorf("unable to find transaction within timeout"))
+ }
+ }
+
+ v := uint64(0)
+ txres, err := rpcClient.GetTransaction(ctx, txsig, &rpc.GetTransactionOpts{
+ Commitment: commitment,
+ MaxSupportedTransactionVersion: &v,
+ })
+ require.NoError(t, err)
+ return txres
+}
+
+func SimulateTransaction(ctx context.Context, t *testing.T, rpcClient *rpc.Client, instructions []solana.Instruction, signer solana.PrivateKey) *rpc.SimulateTransactionResponse {
+ simRes, err := simulateTransaction(ctx, t, rpcClient, instructions, signer)
+ require.NoError(t, err)
+
+ return simRes
+}
+
+func simulateTransaction(ctx context.Context, t *testing.T, rpcClient *rpc.Client, instructions []solana.Instruction,
+ signer solana.PrivateKey) (*rpc.SimulateTransactionResponse, error) {
+ hashRes, err := rpcClient.GetLatestBlockhash(ctx, rpc.CommitmentFinalized)
+ require.NoError(t, err)
+
+ tx, err := solana.NewTransaction(
+ instructions,
+ hashRes.Value.Blockhash,
+ solana.TransactionPayer(signer.PublicKey()),
+ )
+ require.NoError(t, err)
+
+ _, err = tx.Sign(func(_ solana.PublicKey) *solana.PrivateKey {
+ return &signer
+ })
+ require.NoError(t, err)
+
+ return rpcClient.SimulateTransaction(ctx, tx)
+}
+
+func ExtractReturnValue(ctx context.Context, t *testing.T, logs []string, programID string) []byte {
+ if logs == nil {
+ return []byte{}
+ }
+ for _, log := range logs {
+ if strings.HasPrefix(log, "Program return: "+programID) {
+ parts := strings.Split(log, " ")
+ encoded := parts[len(parts)-1]
+ ret, err := base64.StdEncoding.DecodeString(encoded)
+ require.NoError(t, err)
+ return ret
+ }
+ }
+ return []byte{}
+}
+
+func ExtractReturnedError(ctx context.Context, t *testing.T, logs []string, programID string) *int {
+ if logs == nil {
+ return nil
+ }
+
+ for _, log := range logs {
+ if strings.Contains(log, "Error Number: ") {
+ // extract error number from the string
+ parts := strings.Split(log, "Error Number: ")
+ if len(parts) > 1 {
+ numberPart := strings.Split(parts[1], ".")[0]
+ errorNumber, err := strconv.Atoi(strings.TrimSpace(numberPart))
+ require.NoError(t, err)
+ return &errorNumber
+ }
+ }
+ }
+ return nil
+}
+
+func ExtractTypedReturnValue[T any](ctx context.Context, t *testing.T, logs []string, programID string, decoderFn func([]byte) T) T {
+ bytes := ExtractReturnValue(ctx, t, logs, programID)
+ return decoderFn(bytes)
+}
+
+func GetAccountDataBorshInto(ctx context.Context, solanaGoClient *rpc.Client, account solana.PublicKey, commitment rpc.CommitmentType, data interface{}) error {
+ resp, err := solanaGoClient.GetAccountInfoWithOpts(
+ ctx,
+ account,
+ &rpc.GetAccountInfoOpts{
+ Commitment: commitment,
+ DataSlice: nil,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ return bin.NewBorshDecoder(resp.Value.Data.GetBinary()).Decode(data)
+}
+
+func AssertClosedAccount(ctx context.Context, t *testing.T, solanaGoClient *rpc.Client, accountKey solana.PublicKey, commitment rpc.CommitmentType) {
+ _, err := solanaGoClient.GetAccountInfoWithOpts(ctx, accountKey, &rpc.GetAccountInfoOpts{
+ Commitment: commitment,
+ })
+ require.Error(t, err)
+}
diff --git a/chains/solana/contracts/tsconfig.json b/chains/solana/contracts/tsconfig.json
new file mode 100644
index 000000000..ea70f0722
--- /dev/null
+++ b/chains/solana/contracts/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "typeRoots": ["./node_modules/@types"],
+ "lib": ["es2020"],
+ "target": "es2020",
+ "moduleResolution": "node",
+ "esModuleInterop": true
+ }
+}
diff --git a/chains/solana/go.mod b/chains/solana/go.mod
new file mode 100644
index 000000000..f5f3e1586
--- /dev/null
+++ b/chains/solana/go.mod
@@ -0,0 +1,62 @@
+module github.com/smartcontractkit/chainlink-ccip/chains/solana
+
+go 1.23.3
+
+require (
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
+ github.com/gagliardetto/binary v0.8.0
+ github.com/gagliardetto/gofuzz v1.2.2
+ github.com/gagliardetto/solana-go v1.12.0
+ github.com/gagliardetto/treeout v0.1.4
+ github.com/pelletier/go-toml/v2 v2.2.3
+ github.com/smartcontractkit/chainlink-common v0.3.0
+ github.com/stretchr/testify v1.10.0
+ golang.org/x/crypto v0.27.0
+ golang.org/x/exp v0.0.0-20241210194714-1829a127f884
+)
+
+require (
+ filippo.io/edwards25519 v1.0.0-rc.1 // indirect
+ github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/blendle/zapdriver v1.3.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/fatih/color v1.17.0 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.4 // indirect
+ github.com/google/go-cmp v0.6.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/compress v1.15.15 // indirect
+ github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/mitchellh/go-testing-interface v1.14.1 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect
+ github.com/mr-tron/base58 v1.2.0 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/prometheus/client_golang v1.17.0 // indirect
+ github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
+ github.com/prometheus/common v0.44.0 // indirect
+ github.com/prometheus/procfs v0.11.1 // indirect
+ github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 // indirect
+ github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect
+ go.mongodb.org/mongo-driver v1.12.2 // indirect
+ go.opentelemetry.io/otel v1.28.0 // indirect
+ go.opentelemetry.io/otel/metric v1.28.0 // indirect
+ go.opentelemetry.io/otel/trace v1.28.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ go.uber.org/ratelimit v0.2.0 // indirect
+ go.uber.org/zap v1.27.0 // indirect
+ golang.org/x/sys v0.25.0 // indirect
+ golang.org/x/term v0.24.0 // indirect
+ golang.org/x/time v0.3.0 // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/chains/solana/go.sum b/chains/solana/go.sum
new file mode 100644
index 000000000..84bdb05cd
--- /dev/null
+++ b/chains/solana/go.sum
@@ -0,0 +1,224 @@
+filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
+filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
+github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
+github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
+github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
+github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE=
+github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
+github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
+github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
+github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
+github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg=
+github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c=
+github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw=
+github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY=
+github.com/gagliardetto/solana-go v1.12.0 h1:rzsbilDPj6p+/DOPXBMLhwMZeBgeRuXjm5zQFCoXgsg=
+github.com/gagliardetto/solana-go v1.12.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k=
+github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw=
+github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
+github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
+github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
+github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk=
+github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE=
+github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
+github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
+github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
+github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
+github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
+github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
+github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
+github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
+github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
+github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
+github.com/smartcontractkit/chainlink-common v0.3.0 h1:mUXHBzzw2qPKyw6gPAC8JhO+ryT8maY+rBi9NFtqEy0=
+github.com/smartcontractkit/chainlink-common v0.3.0/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko=
+github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 h1:NzZGjaqez21I3DU7objl3xExTH4fxYvzTqar8DC6360=
+github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM=
+github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 h1:RN5mrigyirb8anBEtdjtHFIufXdacyTi6i4KBfeNXeo=
+github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
+github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
+github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws=
+go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
+go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
+go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
+go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
+go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
+go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
+go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
+go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
+golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
+golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
+golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
+golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
+golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/chains/solana/gobindings/access_controller/.gitkeep b/chains/solana/gobindings/access_controller/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/chains/solana/gobindings/access_controller/AcceptOwnership.go b/chains/solana/gobindings/access_controller/AcceptOwnership.go
new file mode 100644
index 000000000..6c5177cde
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/AcceptOwnership.go
@@ -0,0 +1,117 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AcceptOwnership is the `acceptOwnership` instruction.
+type AcceptOwnership struct {
+
+ // [0] = [WRITE] state
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAcceptOwnershipInstructionBuilder creates a new `AcceptOwnership` instruction builder.
+func NewAcceptOwnershipInstructionBuilder() *AcceptOwnership {
+ nd := &AcceptOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetStateAccount sets the "state" account.
+func (inst *AcceptOwnership) SetStateAccount(state ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(state).WRITE()
+ return inst
+}
+
+// GetStateAccount gets the "state" account.
+func (inst *AcceptOwnership) GetStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AcceptOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AcceptOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst AcceptOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AcceptOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AcceptOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AcceptOwnership) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.State is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AcceptOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AcceptOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" state", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj AcceptOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *AcceptOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewAcceptOwnershipInstruction declares a new AcceptOwnership instruction with the provided parameters and accounts.
+func NewAcceptOwnershipInstruction(
+ // Accounts:
+ state ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *AcceptOwnership {
+ return NewAcceptOwnershipInstructionBuilder().
+ SetStateAccount(state).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/access_controller/AcceptOwnership_test.go b/chains/solana/gobindings/access_controller/AcceptOwnership_test.go
new file mode 100644
index 000000000..9c325399c
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/AcceptOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AcceptOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AcceptOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AcceptOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AcceptOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/access_controller/AddAccess.go b/chains/solana/gobindings/access_controller/AddAccess.go
new file mode 100644
index 000000000..3c7488630
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/AddAccess.go
@@ -0,0 +1,136 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AddAccess is the `addAccess` instruction.
+type AddAccess struct {
+
+ // [0] = [WRITE] state
+ //
+ // [1] = [SIGNER] owner
+ //
+ // [2] = [] address
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAddAccessInstructionBuilder creates a new `AddAccess` instruction builder.
+func NewAddAccessInstructionBuilder() *AddAccess {
+ nd := &AddAccess{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetStateAccount sets the "state" account.
+func (inst *AddAccess) SetStateAccount(state ag_solanago.PublicKey) *AddAccess {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(state).WRITE()
+ return inst
+}
+
+// GetStateAccount gets the "state" account.
+func (inst *AddAccess) GetStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetOwnerAccount sets the "owner" account.
+func (inst *AddAccess) SetOwnerAccount(owner ag_solanago.PublicKey) *AddAccess {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(owner).SIGNER()
+ return inst
+}
+
+// GetOwnerAccount gets the "owner" account.
+func (inst *AddAccess) GetOwnerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAddressAccount sets the "address" account.
+func (inst *AddAccess) SetAddressAccount(address ag_solanago.PublicKey) *AddAccess {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(address)
+ return inst
+}
+
+// GetAddressAccount gets the "address" account.
+func (inst *AddAccess) GetAddressAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst AddAccess) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AddAccess,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AddAccess) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AddAccess) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.State is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Owner is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Address is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AddAccess) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AddAccess")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" state", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" owner", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("address", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj AddAccess) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *AddAccess) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewAddAccessInstruction declares a new AddAccess instruction with the provided parameters and accounts.
+func NewAddAccessInstruction(
+ // Accounts:
+ state ag_solanago.PublicKey,
+ owner ag_solanago.PublicKey,
+ address ag_solanago.PublicKey) *AddAccess {
+ return NewAddAccessInstructionBuilder().
+ SetStateAccount(state).
+ SetOwnerAccount(owner).
+ SetAddressAccount(address)
+}
diff --git a/chains/solana/gobindings/access_controller/AddAccess_test.go b/chains/solana/gobindings/access_controller/AddAccess_test.go
new file mode 100644
index 000000000..d6acb424a
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/AddAccess_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AddAccess(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AddAccess"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AddAccess)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AddAccess)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/access_controller/Initialize.go b/chains/solana/gobindings/access_controller/Initialize.go
new file mode 100644
index 000000000..ce22ef71b
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/Initialize.go
@@ -0,0 +1,117 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Initialize is the `initialize` instruction.
+type Initialize struct {
+
+ // [0] = [WRITE] state
+ //
+ // [1] = [SIGNER] owner
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitializeInstructionBuilder creates a new `Initialize` instruction builder.
+func NewInitializeInstructionBuilder() *Initialize {
+ nd := &Initialize{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetStateAccount sets the "state" account.
+func (inst *Initialize) SetStateAccount(state ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(state).WRITE()
+ return inst
+}
+
+// GetStateAccount gets the "state" account.
+func (inst *Initialize) GetStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetOwnerAccount sets the "owner" account.
+func (inst *Initialize) SetOwnerAccount(owner ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(owner).SIGNER()
+ return inst
+}
+
+// GetOwnerAccount gets the "owner" account.
+func (inst *Initialize) GetOwnerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst Initialize) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Initialize,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Initialize) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Initialize) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.State is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Owner is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Initialize) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Initialize")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("state", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("owner", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj Initialize) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewInitializeInstruction declares a new Initialize instruction with the provided parameters and accounts.
+func NewInitializeInstruction(
+ // Accounts:
+ state ag_solanago.PublicKey,
+ owner ag_solanago.PublicKey) *Initialize {
+ return NewInitializeInstructionBuilder().
+ SetStateAccount(state).
+ SetOwnerAccount(owner)
+}
diff --git a/chains/solana/gobindings/access_controller/Initialize_test.go b/chains/solana/gobindings/access_controller/Initialize_test.go
new file mode 100644
index 000000000..c7c9be87c
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/Initialize_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Initialize(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Initialize"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Initialize)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Initialize)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/access_controller/RemoveAccess.go b/chains/solana/gobindings/access_controller/RemoveAccess.go
new file mode 100644
index 000000000..567a66b5a
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/RemoveAccess.go
@@ -0,0 +1,136 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// RemoveAccess is the `removeAccess` instruction.
+type RemoveAccess struct {
+
+ // [0] = [WRITE] state
+ //
+ // [1] = [SIGNER] owner
+ //
+ // [2] = [] address
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewRemoveAccessInstructionBuilder creates a new `RemoveAccess` instruction builder.
+func NewRemoveAccessInstructionBuilder() *RemoveAccess {
+ nd := &RemoveAccess{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetStateAccount sets the "state" account.
+func (inst *RemoveAccess) SetStateAccount(state ag_solanago.PublicKey) *RemoveAccess {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(state).WRITE()
+ return inst
+}
+
+// GetStateAccount gets the "state" account.
+func (inst *RemoveAccess) GetStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetOwnerAccount sets the "owner" account.
+func (inst *RemoveAccess) SetOwnerAccount(owner ag_solanago.PublicKey) *RemoveAccess {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(owner).SIGNER()
+ return inst
+}
+
+// GetOwnerAccount gets the "owner" account.
+func (inst *RemoveAccess) GetOwnerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAddressAccount sets the "address" account.
+func (inst *RemoveAccess) SetAddressAccount(address ag_solanago.PublicKey) *RemoveAccess {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(address)
+ return inst
+}
+
+// GetAddressAccount gets the "address" account.
+func (inst *RemoveAccess) GetAddressAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst RemoveAccess) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_RemoveAccess,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst RemoveAccess) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *RemoveAccess) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.State is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Owner is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Address is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *RemoveAccess) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("RemoveAccess")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" state", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" owner", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("address", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj RemoveAccess) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *RemoveAccess) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewRemoveAccessInstruction declares a new RemoveAccess instruction with the provided parameters and accounts.
+func NewRemoveAccessInstruction(
+ // Accounts:
+ state ag_solanago.PublicKey,
+ owner ag_solanago.PublicKey,
+ address ag_solanago.PublicKey) *RemoveAccess {
+ return NewRemoveAccessInstructionBuilder().
+ SetStateAccount(state).
+ SetOwnerAccount(owner).
+ SetAddressAccount(address)
+}
diff --git a/chains/solana/gobindings/access_controller/RemoveAccess_test.go b/chains/solana/gobindings/access_controller/RemoveAccess_test.go
new file mode 100644
index 000000000..484796fa7
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/RemoveAccess_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_RemoveAccess(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("RemoveAccess"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(RemoveAccess)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(RemoveAccess)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/access_controller/TransferOwnership.go b/chains/solana/gobindings/access_controller/TransferOwnership.go
new file mode 100644
index 000000000..c6b8f3f50
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/TransferOwnership.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// TransferOwnership is the `transferOwnership` instruction.
+type TransferOwnership struct {
+ ProposedOwner *ag_solanago.PublicKey
+
+ // [0] = [WRITE] state
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewTransferOwnershipInstructionBuilder creates a new `TransferOwnership` instruction builder.
+func NewTransferOwnershipInstructionBuilder() *TransferOwnership {
+ nd := &TransferOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetProposedOwner sets the "proposedOwner" parameter.
+func (inst *TransferOwnership) SetProposedOwner(proposedOwner ag_solanago.PublicKey) *TransferOwnership {
+ inst.ProposedOwner = &proposedOwner
+ return inst
+}
+
+// SetStateAccount sets the "state" account.
+func (inst *TransferOwnership) SetStateAccount(state ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(state).WRITE()
+ return inst
+}
+
+// GetStateAccount gets the "state" account.
+func (inst *TransferOwnership) GetStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *TransferOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *TransferOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst TransferOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_TransferOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst TransferOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *TransferOwnership) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ProposedOwner == nil {
+ return errors.New("ProposedOwner parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.State is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *TransferOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("TransferOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("ProposedOwner", *inst.ProposedOwner))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" state", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj TransferOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *TransferOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewTransferOwnershipInstruction declares a new TransferOwnership instruction with the provided parameters and accounts.
+func NewTransferOwnershipInstruction(
+ // Parameters:
+ proposedOwner ag_solanago.PublicKey,
+ // Accounts:
+ state ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *TransferOwnership {
+ return NewTransferOwnershipInstructionBuilder().
+ SetProposedOwner(proposedOwner).
+ SetStateAccount(state).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/access_controller/TransferOwnership_test.go b/chains/solana/gobindings/access_controller/TransferOwnership_test.go
new file mode 100644
index 000000000..950bacbf2
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/TransferOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_TransferOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("TransferOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(TransferOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(TransferOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/access_controller/accounts.go b/chains/solana/gobindings/access_controller/accounts.go
new file mode 100644
index 000000000..dedada59d
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/accounts.go
@@ -0,0 +1,73 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type AccessController struct {
+ Owner ag_solanago.PublicKey
+ ProposedOwner ag_solanago.PublicKey
+ AccessList AccessList
+}
+
+var AccessControllerDiscriminator = [8]byte{143, 45, 12, 204, 220, 20, 114, 87}
+
+func (obj AccessController) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(AccessControllerDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Owner` param:
+ err = encoder.Encode(obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Serialize `AccessList` param:
+ err = encoder.Encode(obj.AccessList)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *AccessController) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(AccessControllerDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[143 45 12 204 220 20 114 87]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Owner`:
+ err = decoder.Decode(&obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `AccessList`:
+ err = decoder.Decode(&obj.AccessList)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/access_controller/instructions.go b/chains/solana/gobindings/access_controller/instructions.go
new file mode 100644
index 000000000..c5488cafe
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/instructions.go
@@ -0,0 +1,145 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "bytes"
+ "fmt"
+ ag_spew "github.com/davecgh/go-spew/spew"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_text "github.com/gagliardetto/solana-go/text"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+var ProgramID ag_solanago.PublicKey
+
+func SetProgramID(pubkey ag_solanago.PublicKey) {
+ ProgramID = pubkey
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+}
+
+const ProgramName = "AccessController"
+
+func init() {
+ if !ProgramID.IsZero() {
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+ }
+}
+
+var (
+ Instruction_Initialize = ag_binary.TypeID([8]byte{175, 175, 109, 31, 13, 152, 155, 237})
+
+ Instruction_TransferOwnership = ag_binary.TypeID([8]byte{65, 177, 215, 73, 53, 45, 99, 47})
+
+ Instruction_AcceptOwnership = ag_binary.TypeID([8]byte{172, 23, 43, 13, 238, 213, 85, 150})
+
+ Instruction_AddAccess = ag_binary.TypeID([8]byte{151, 189, 105, 24, 113, 60, 99, 138})
+
+ Instruction_RemoveAccess = ag_binary.TypeID([8]byte{92, 172, 70, 124, 83, 45, 88, 22})
+)
+
+// InstructionIDToName returns the name of the instruction given its ID.
+func InstructionIDToName(id ag_binary.TypeID) string {
+ switch id {
+ case Instruction_Initialize:
+ return "Initialize"
+ case Instruction_TransferOwnership:
+ return "TransferOwnership"
+ case Instruction_AcceptOwnership:
+ return "AcceptOwnership"
+ case Instruction_AddAccess:
+ return "AddAccess"
+ case Instruction_RemoveAccess:
+ return "RemoveAccess"
+ default:
+ return ""
+ }
+}
+
+type Instruction struct {
+ ag_binary.BaseVariant
+}
+
+func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) {
+ if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok {
+ enToTree.EncodeToTree(parent)
+ } else {
+ parent.Child(ag_spew.Sdump(inst))
+ }
+}
+
+var InstructionImplDef = ag_binary.NewVariantDefinition(
+ ag_binary.AnchorTypeIDEncoding,
+ []ag_binary.VariantType{
+ {
+ "initialize", (*Initialize)(nil),
+ },
+ {
+ "transfer_ownership", (*TransferOwnership)(nil),
+ },
+ {
+ "accept_ownership", (*AcceptOwnership)(nil),
+ },
+ {
+ "add_access", (*AddAccess)(nil),
+ },
+ {
+ "remove_access", (*RemoveAccess)(nil),
+ },
+ },
+)
+
+func (inst *Instruction) ProgramID() ag_solanago.PublicKey {
+ return ProgramID
+}
+
+func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) {
+ return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts()
+}
+
+func (inst *Instruction) Data() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil {
+ return nil, fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return buf.Bytes(), nil
+}
+
+func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
+ return encoder.Encode(inst.Impl, option)
+}
+
+func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
+ return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
+}
+
+func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
+ err := encoder.WriteBytes(inst.TypeID.Bytes(), false)
+ if err != nil {
+ return fmt.Errorf("unable to write variant type: %w", err)
+ }
+ return encoder.Encode(inst.Impl)
+}
+
+func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) {
+ inst, err := DecodeInstruction(accounts, data)
+ if err != nil {
+ return nil, err
+ }
+ return inst, nil
+}
+
+func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) {
+ inst := new(Instruction)
+ if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil {
+ return nil, fmt.Errorf("unable to decode instruction: %w", err)
+ }
+ if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok {
+ err := v.SetAccounts(accounts)
+ if err != nil {
+ return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
+ }
+ }
+ return inst, nil
+}
diff --git a/chains/solana/gobindings/access_controller/testing_utils.go b/chains/solana/gobindings/access_controller/testing_utils.go
new file mode 100644
index 000000000..395b23b54
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/testing_utils.go
@@ -0,0 +1,20 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ "bytes"
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+func encodeT(data interface{}, buf *bytes.Buffer) error {
+ if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil {
+ return fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return nil
+}
+
+func decodeT(dst interface{}, data []byte) error {
+ return ag_binary.NewBorshDecoder(data).Decode(dst)
+}
diff --git a/chains/solana/gobindings/access_controller/types.go b/chains/solana/gobindings/access_controller/types.go
new file mode 100644
index 000000000..4a6182668
--- /dev/null
+++ b/chains/solana/gobindings/access_controller/types.go
@@ -0,0 +1,41 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package access_controller
+
+import (
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type AccessList struct {
+ Xs [64]ag_solanago.PublicKey
+ Len uint64
+}
+
+func (obj AccessList) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Xs` param:
+ err = encoder.Encode(obj.Xs)
+ if err != nil {
+ return err
+ }
+ // Serialize `Len` param:
+ err = encoder.Encode(obj.Len)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *AccessList) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Xs`:
+ err = decoder.Decode(&obj.Xs)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Len`:
+ err = decoder.Decode(&obj.Len)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/ccip_invalid_receiver/CcipReceive.go b/chains/solana/gobindings/ccip_invalid_receiver/CcipReceive.go
new file mode 100644
index 000000000..112f7e0b3
--- /dev/null
+++ b/chains/solana/gobindings/ccip_invalid_receiver/CcipReceive.go
@@ -0,0 +1,165 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_invalid_receiver
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// CcipReceive is the `ccipReceive` instruction.
+type CcipReceive struct {
+ Message *Any2SolanaMessage
+
+ // [0] = [WRITE, SIGNER] authority
+ //
+ // [1] = [WRITE] counter
+ //
+ // [2] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewCcipReceiveInstructionBuilder creates a new `CcipReceive` instruction builder.
+func NewCcipReceiveInstructionBuilder() *CcipReceive {
+ nd := &CcipReceive{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetMessage sets the "message" parameter.
+func (inst *CcipReceive) SetMessage(message Any2SolanaMessage) *CcipReceive {
+ inst.Message = &message
+ return inst
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *CcipReceive) SetAuthorityAccount(authority ag_solanago.PublicKey) *CcipReceive {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *CcipReceive) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetCounterAccount sets the "counter" account.
+func (inst *CcipReceive) SetCounterAccount(counter ag_solanago.PublicKey) *CcipReceive {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(counter).WRITE()
+ return inst
+}
+
+// GetCounterAccount gets the "counter" account.
+func (inst *CcipReceive) GetCounterAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *CcipReceive) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *CcipReceive {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *CcipReceive) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst CcipReceive) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_CcipReceive,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst CcipReceive) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *CcipReceive) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Message == nil {
+ return errors.New("Message parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Counter is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *CcipReceive) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("CcipReceive")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Message", *inst.Message))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" counter", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj CcipReceive) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Message` param:
+ err = encoder.Encode(obj.Message)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *CcipReceive) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Message`:
+ err = decoder.Decode(&obj.Message)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewCcipReceiveInstruction declares a new CcipReceive instruction with the provided parameters and accounts.
+func NewCcipReceiveInstruction(
+ // Parameters:
+ message Any2SolanaMessage,
+ // Accounts:
+ authority ag_solanago.PublicKey,
+ counter ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *CcipReceive {
+ return NewCcipReceiveInstructionBuilder().
+ SetMessage(message).
+ SetAuthorityAccount(authority).
+ SetCounterAccount(counter).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_invalid_receiver/CcipReceive_test.go b/chains/solana/gobindings/ccip_invalid_receiver/CcipReceive_test.go
new file mode 100644
index 000000000..1028a4af1
--- /dev/null
+++ b/chains/solana/gobindings/ccip_invalid_receiver/CcipReceive_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_invalid_receiver
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_CcipReceive(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("CcipReceive"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(CcipReceive)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(CcipReceive)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_invalid_receiver/accounts.go b/chains/solana/gobindings/ccip_invalid_receiver/accounts.go
new file mode 100644
index 000000000..e205f3522
--- /dev/null
+++ b/chains/solana/gobindings/ccip_invalid_receiver/accounts.go
@@ -0,0 +1,50 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_invalid_receiver
+
+import (
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+type Counter struct {
+ Value uint8
+}
+
+var CounterDiscriminator = [8]byte{255, 176, 4, 245, 188, 253, 124, 25}
+
+func (obj Counter) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(CounterDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Value` param:
+ err = encoder.Encode(obj.Value)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Counter) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(CounterDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[255 176 4 245 188 253 124 25]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Value`:
+ err = decoder.Decode(&obj.Value)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/ccip_invalid_receiver/instructions.go b/chains/solana/gobindings/ccip_invalid_receiver/instructions.go
new file mode 100644
index 000000000..cb9c11786
--- /dev/null
+++ b/chains/solana/gobindings/ccip_invalid_receiver/instructions.go
@@ -0,0 +1,117 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_invalid_receiver
+
+import (
+ "bytes"
+ "fmt"
+ ag_spew "github.com/davecgh/go-spew/spew"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_text "github.com/gagliardetto/solana-go/text"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+var ProgramID ag_solanago.PublicKey
+
+func SetProgramID(pubkey ag_solanago.PublicKey) {
+ ProgramID = pubkey
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+}
+
+const ProgramName = "CcipInvalidReceiver"
+
+func init() {
+ if !ProgramID.IsZero() {
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+ }
+}
+
+var (
+ Instruction_CcipReceive = ag_binary.TypeID([8]byte{11, 244, 9, 249, 44, 83, 47, 245})
+)
+
+// InstructionIDToName returns the name of the instruction given its ID.
+func InstructionIDToName(id ag_binary.TypeID) string {
+ switch id {
+ case Instruction_CcipReceive:
+ return "CcipReceive"
+ default:
+ return ""
+ }
+}
+
+type Instruction struct {
+ ag_binary.BaseVariant
+}
+
+func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) {
+ if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok {
+ enToTree.EncodeToTree(parent)
+ } else {
+ parent.Child(ag_spew.Sdump(inst))
+ }
+}
+
+var InstructionImplDef = ag_binary.NewVariantDefinition(
+ ag_binary.AnchorTypeIDEncoding,
+ []ag_binary.VariantType{
+ {
+ "ccip_receive", (*CcipReceive)(nil),
+ },
+ },
+)
+
+func (inst *Instruction) ProgramID() ag_solanago.PublicKey {
+ return ProgramID
+}
+
+func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) {
+ return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts()
+}
+
+func (inst *Instruction) Data() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil {
+ return nil, fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return buf.Bytes(), nil
+}
+
+func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
+ return encoder.Encode(inst.Impl, option)
+}
+
+func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
+ return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
+}
+
+func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
+ err := encoder.WriteBytes(inst.TypeID.Bytes(), false)
+ if err != nil {
+ return fmt.Errorf("unable to write variant type: %w", err)
+ }
+ return encoder.Encode(inst.Impl)
+}
+
+func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) {
+ inst, err := DecodeInstruction(accounts, data)
+ if err != nil {
+ return nil, err
+ }
+ return inst, nil
+}
+
+func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) {
+ inst := new(Instruction)
+ if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil {
+ return nil, fmt.Errorf("unable to decode instruction: %w", err)
+ }
+ if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok {
+ err := v.SetAccounts(accounts)
+ if err != nil {
+ return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
+ }
+ }
+ return inst, nil
+}
diff --git a/chains/solana/gobindings/ccip_invalid_receiver/testing_utils.go b/chains/solana/gobindings/ccip_invalid_receiver/testing_utils.go
new file mode 100644
index 000000000..7ce20b776
--- /dev/null
+++ b/chains/solana/gobindings/ccip_invalid_receiver/testing_utils.go
@@ -0,0 +1,20 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_invalid_receiver
+
+import (
+ "bytes"
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+func encodeT(data interface{}, buf *bytes.Buffer) error {
+ if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil {
+ return fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return nil
+}
+
+func decodeT(dst interface{}, data []byte) error {
+ return ag_binary.NewBorshDecoder(data).Decode(dst)
+}
diff --git a/chains/solana/gobindings/ccip_invalid_receiver/types.go b/chains/solana/gobindings/ccip_invalid_receiver/types.go
new file mode 100644
index 000000000..47b37541b
--- /dev/null
+++ b/chains/solana/gobindings/ccip_invalid_receiver/types.go
@@ -0,0 +1,107 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_invalid_receiver
+
+import (
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type Any2SolanaMessage struct {
+ MessageId [32]uint8
+ SourceChainSelector uint64
+ Sender []byte
+ Data []byte
+ TokenAmounts []SolanaTokenAmount
+}
+
+func (obj Any2SolanaMessage) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MessageId` param:
+ err = encoder.Encode(obj.MessageId)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourceChainSelector` param:
+ err = encoder.Encode(obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Sender` param:
+ err = encoder.Encode(obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenAmounts` param:
+ err = encoder.Encode(obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Any2SolanaMessage) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MessageId`:
+ err = decoder.Decode(&obj.MessageId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourceChainSelector`:
+ err = decoder.Decode(&obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Sender`:
+ err = decoder.Decode(&obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenAmounts`:
+ err = decoder.Decode(&obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type SolanaTokenAmount struct {
+ Token ag_solanago.PublicKey
+ Amount uint64
+}
+
+func (obj SolanaTokenAmount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Token` param:
+ err = encoder.Encode(obj.Token)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *SolanaTokenAmount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Token`:
+ err = decoder.Decode(&obj.Token)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/ccip_receiver/CcipReceive.go b/chains/solana/gobindings/ccip_receiver/CcipReceive.go
new file mode 100644
index 000000000..75373687a
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/CcipReceive.go
@@ -0,0 +1,189 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// This function is called by the CCIP Router to execute the CCIP message.
+// The method name needs to be ccip_receive with Anchor encoding,
+// if not using Anchor the discriminator needs to be [0x0b, 0xf4, 0x09, 0xf9, 0x2c, 0x53, 0x2f, 0xf5]
+// You can send as many accounts as you need, specifying if mutable or not.
+// But none of them could be an init, realloc or close.
+// In this case, it increments the counter value by 1 and logs the parsed message.
+type CcipReceive struct {
+ Message *Any2SolanaMessage
+
+ // [0] = [SIGNER] authority
+ //
+ // [1] = [WRITE] externalExecutionConfig
+ //
+ // [2] = [WRITE] counter
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewCcipReceiveInstructionBuilder creates a new `CcipReceive` instruction builder.
+func NewCcipReceiveInstructionBuilder() *CcipReceive {
+ nd := &CcipReceive{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetMessage sets the "message" parameter.
+func (inst *CcipReceive) SetMessage(message Any2SolanaMessage) *CcipReceive {
+ inst.Message = &message
+ return inst
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *CcipReceive) SetAuthorityAccount(authority ag_solanago.PublicKey) *CcipReceive {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *CcipReceive) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetExternalExecutionConfigAccount sets the "externalExecutionConfig" account.
+func (inst *CcipReceive) SetExternalExecutionConfigAccount(externalExecutionConfig ag_solanago.PublicKey) *CcipReceive {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(externalExecutionConfig).WRITE()
+ return inst
+}
+
+// GetExternalExecutionConfigAccount gets the "externalExecutionConfig" account.
+func (inst *CcipReceive) GetExternalExecutionConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetCounterAccount sets the "counter" account.
+func (inst *CcipReceive) SetCounterAccount(counter ag_solanago.PublicKey) *CcipReceive {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(counter).WRITE()
+ return inst
+}
+
+// GetCounterAccount gets the "counter" account.
+func (inst *CcipReceive) GetCounterAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *CcipReceive) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *CcipReceive {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *CcipReceive) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst CcipReceive) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_CcipReceive,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst CcipReceive) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *CcipReceive) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Message == nil {
+ return errors.New("Message parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ExternalExecutionConfig is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Counter is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *CcipReceive) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("CcipReceive")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Message", *inst.Message))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("externalExecutionConfig", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" counter", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj CcipReceive) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Message` param:
+ err = encoder.Encode(obj.Message)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *CcipReceive) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Message`:
+ err = decoder.Decode(&obj.Message)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewCcipReceiveInstruction declares a new CcipReceive instruction with the provided parameters and accounts.
+func NewCcipReceiveInstruction(
+ // Parameters:
+ message Any2SolanaMessage,
+ // Accounts:
+ authority ag_solanago.PublicKey,
+ externalExecutionConfig ag_solanago.PublicKey,
+ counter ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *CcipReceive {
+ return NewCcipReceiveInstructionBuilder().
+ SetMessage(message).
+ SetAuthorityAccount(authority).
+ SetExternalExecutionConfigAccount(externalExecutionConfig).
+ SetCounterAccount(counter).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_receiver/CcipReceive_test.go b/chains/solana/gobindings/ccip_receiver/CcipReceive_test.go
new file mode 100644
index 000000000..69f1320d6
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/CcipReceive_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_CcipReceive(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("CcipReceive"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(CcipReceive)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(CcipReceive)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_receiver/CcipTokenLockBurn.go b/chains/solana/gobindings/ccip_receiver/CcipTokenLockBurn.go
new file mode 100644
index 000000000..c6be218b7
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/CcipTokenLockBurn.go
@@ -0,0 +1,184 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// CcipTokenLockBurn is the `ccipTokenLockBurn` instruction.
+type CcipTokenLockBurn struct {
+ Input *LockOrBurnInV1
+
+ // [0] = [SIGNER] authority
+ //
+ // [1] = [WRITE] poolTokenAccount
+ //
+ // [2] = [] mint
+ //
+ // [3] = [] tokenProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewCcipTokenLockBurnInstructionBuilder creates a new `CcipTokenLockBurn` instruction builder.
+func NewCcipTokenLockBurnInstructionBuilder() *CcipTokenLockBurn {
+ nd := &CcipTokenLockBurn{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetInput sets the "input" parameter.
+func (inst *CcipTokenLockBurn) SetInput(input LockOrBurnInV1) *CcipTokenLockBurn {
+ inst.Input = &input
+ return inst
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *CcipTokenLockBurn) SetAuthorityAccount(authority ag_solanago.PublicKey) *CcipTokenLockBurn {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *CcipTokenLockBurn) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetPoolTokenAccountAccount sets the "poolTokenAccount" account.
+func (inst *CcipTokenLockBurn) SetPoolTokenAccountAccount(poolTokenAccount ag_solanago.PublicKey) *CcipTokenLockBurn {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(poolTokenAccount).WRITE()
+ return inst
+}
+
+// GetPoolTokenAccountAccount gets the "poolTokenAccount" account.
+func (inst *CcipTokenLockBurn) GetPoolTokenAccountAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetMintAccount sets the "mint" account.
+func (inst *CcipTokenLockBurn) SetMintAccount(mint ag_solanago.PublicKey) *CcipTokenLockBurn {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(mint)
+ return inst
+}
+
+// GetMintAccount gets the "mint" account.
+func (inst *CcipTokenLockBurn) GetMintAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetTokenProgramAccount sets the "tokenProgram" account.
+func (inst *CcipTokenLockBurn) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *CcipTokenLockBurn {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(tokenProgram)
+ return inst
+}
+
+// GetTokenProgramAccount gets the "tokenProgram" account.
+func (inst *CcipTokenLockBurn) GetTokenProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst CcipTokenLockBurn) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_CcipTokenLockBurn,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst CcipTokenLockBurn) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *CcipTokenLockBurn) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Input == nil {
+ return errors.New("Input parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.PoolTokenAccount is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Mint is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.TokenProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *CcipTokenLockBurn) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("CcipTokenLockBurn")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Input", *inst.Input))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" poolToken", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("tokenProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj CcipTokenLockBurn) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Input` param:
+ err = encoder.Encode(obj.Input)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *CcipTokenLockBurn) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Input`:
+ err = decoder.Decode(&obj.Input)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewCcipTokenLockBurnInstruction declares a new CcipTokenLockBurn instruction with the provided parameters and accounts.
+func NewCcipTokenLockBurnInstruction(
+ // Parameters:
+ input LockOrBurnInV1,
+ // Accounts:
+ authority ag_solanago.PublicKey,
+ poolTokenAccount ag_solanago.PublicKey,
+ mint ag_solanago.PublicKey,
+ tokenProgram ag_solanago.PublicKey) *CcipTokenLockBurn {
+ return NewCcipTokenLockBurnInstructionBuilder().
+ SetInput(input).
+ SetAuthorityAccount(authority).
+ SetPoolTokenAccountAccount(poolTokenAccount).
+ SetMintAccount(mint).
+ SetTokenProgramAccount(tokenProgram)
+}
diff --git a/chains/solana/gobindings/ccip_receiver/CcipTokenLockBurn_test.go b/chains/solana/gobindings/ccip_receiver/CcipTokenLockBurn_test.go
new file mode 100644
index 000000000..9d34b4e6e
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/CcipTokenLockBurn_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_CcipTokenLockBurn(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("CcipTokenLockBurn"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(CcipTokenLockBurn)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(CcipTokenLockBurn)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_receiver/CcipTokenReleaseMint.go b/chains/solana/gobindings/ccip_receiver/CcipTokenReleaseMint.go
new file mode 100644
index 000000000..1ac190e49
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/CcipTokenReleaseMint.go
@@ -0,0 +1,184 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// CcipTokenReleaseMint is the `ccipTokenReleaseMint` instruction.
+type CcipTokenReleaseMint struct {
+ Input *ReleaseOrMintInV1
+
+ // [0] = [SIGNER] authority
+ //
+ // [1] = [WRITE] poolTokenAccount
+ //
+ // [2] = [] mint
+ //
+ // [3] = [] tokenProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewCcipTokenReleaseMintInstructionBuilder creates a new `CcipTokenReleaseMint` instruction builder.
+func NewCcipTokenReleaseMintInstructionBuilder() *CcipTokenReleaseMint {
+ nd := &CcipTokenReleaseMint{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetInput sets the "input" parameter.
+func (inst *CcipTokenReleaseMint) SetInput(input ReleaseOrMintInV1) *CcipTokenReleaseMint {
+ inst.Input = &input
+ return inst
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *CcipTokenReleaseMint) SetAuthorityAccount(authority ag_solanago.PublicKey) *CcipTokenReleaseMint {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *CcipTokenReleaseMint) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetPoolTokenAccountAccount sets the "poolTokenAccount" account.
+func (inst *CcipTokenReleaseMint) SetPoolTokenAccountAccount(poolTokenAccount ag_solanago.PublicKey) *CcipTokenReleaseMint {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(poolTokenAccount).WRITE()
+ return inst
+}
+
+// GetPoolTokenAccountAccount gets the "poolTokenAccount" account.
+func (inst *CcipTokenReleaseMint) GetPoolTokenAccountAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetMintAccount sets the "mint" account.
+func (inst *CcipTokenReleaseMint) SetMintAccount(mint ag_solanago.PublicKey) *CcipTokenReleaseMint {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(mint)
+ return inst
+}
+
+// GetMintAccount gets the "mint" account.
+func (inst *CcipTokenReleaseMint) GetMintAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetTokenProgramAccount sets the "tokenProgram" account.
+func (inst *CcipTokenReleaseMint) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *CcipTokenReleaseMint {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(tokenProgram)
+ return inst
+}
+
+// GetTokenProgramAccount gets the "tokenProgram" account.
+func (inst *CcipTokenReleaseMint) GetTokenProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst CcipTokenReleaseMint) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_CcipTokenReleaseMint,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst CcipTokenReleaseMint) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *CcipTokenReleaseMint) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Input == nil {
+ return errors.New("Input parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.PoolTokenAccount is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Mint is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.TokenProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *CcipTokenReleaseMint) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("CcipTokenReleaseMint")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Input", *inst.Input))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" poolToken", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("tokenProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj CcipTokenReleaseMint) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Input` param:
+ err = encoder.Encode(obj.Input)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *CcipTokenReleaseMint) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Input`:
+ err = decoder.Decode(&obj.Input)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewCcipTokenReleaseMintInstruction declares a new CcipTokenReleaseMint instruction with the provided parameters and accounts.
+func NewCcipTokenReleaseMintInstruction(
+ // Parameters:
+ input ReleaseOrMintInV1,
+ // Accounts:
+ authority ag_solanago.PublicKey,
+ poolTokenAccount ag_solanago.PublicKey,
+ mint ag_solanago.PublicKey,
+ tokenProgram ag_solanago.PublicKey) *CcipTokenReleaseMint {
+ return NewCcipTokenReleaseMintInstructionBuilder().
+ SetInput(input).
+ SetAuthorityAccount(authority).
+ SetPoolTokenAccountAccount(poolTokenAccount).
+ SetMintAccount(mint).
+ SetTokenProgramAccount(tokenProgram)
+}
diff --git a/chains/solana/gobindings/ccip_receiver/CcipTokenReleaseMint_test.go b/chains/solana/gobindings/ccip_receiver/CcipTokenReleaseMint_test.go
new file mode 100644
index 000000000..b37b389fd
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/CcipTokenReleaseMint_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_CcipTokenReleaseMint(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("CcipTokenReleaseMint"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(CcipTokenReleaseMint)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(CcipTokenReleaseMint)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_receiver/Initialize.go b/chains/solana/gobindings/ccip_receiver/Initialize.go
new file mode 100644
index 000000000..2137ba631
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/Initialize.go
@@ -0,0 +1,155 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// The initialization is responsibility of the External User, CCIP is not handling initialization of Accounts
+type Initialize struct {
+
+ // [0] = [WRITE] counter
+ //
+ // [1] = [WRITE] externalExecutionConfig
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitializeInstructionBuilder creates a new `Initialize` instruction builder.
+func NewInitializeInstructionBuilder() *Initialize {
+ nd := &Initialize{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetCounterAccount sets the "counter" account.
+func (inst *Initialize) SetCounterAccount(counter ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(counter).WRITE()
+ return inst
+}
+
+// GetCounterAccount gets the "counter" account.
+func (inst *Initialize) GetCounterAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetExternalExecutionConfigAccount sets the "externalExecutionConfig" account.
+func (inst *Initialize) SetExternalExecutionConfigAccount(externalExecutionConfig ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(externalExecutionConfig).WRITE()
+ return inst
+}
+
+// GetExternalExecutionConfigAccount gets the "externalExecutionConfig" account.
+func (inst *Initialize) GetExternalExecutionConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *Initialize) SetAuthorityAccount(authority ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *Initialize) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *Initialize) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *Initialize) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst Initialize) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Initialize,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Initialize) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Initialize) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Counter is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ExternalExecutionConfig is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Initialize) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Initialize")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" counter", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("externalExecutionConfig", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj Initialize) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewInitializeInstruction declares a new Initialize instruction with the provided parameters and accounts.
+func NewInitializeInstruction(
+ // Accounts:
+ counter ag_solanago.PublicKey,
+ externalExecutionConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *Initialize {
+ return NewInitializeInstructionBuilder().
+ SetCounterAccount(counter).
+ SetExternalExecutionConfigAccount(externalExecutionConfig).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_receiver/Initialize_test.go b/chains/solana/gobindings/ccip_receiver/Initialize_test.go
new file mode 100644
index 000000000..01b8951f6
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/Initialize_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Initialize(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Initialize"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Initialize)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Initialize)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_receiver/accounts.go b/chains/solana/gobindings/ccip_receiver/accounts.go
new file mode 100644
index 000000000..0de93c735
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/accounts.go
@@ -0,0 +1,80 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+type Counter struct {
+ Value uint8
+}
+
+var CounterDiscriminator = [8]byte{255, 176, 4, 245, 188, 253, 124, 25}
+
+func (obj Counter) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(CounterDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Value` param:
+ err = encoder.Encode(obj.Value)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Counter) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(CounterDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[255 176 4 245 188 253 124 25]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Value`:
+ err = decoder.Decode(&obj.Value)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ExternalExecutionConfig struct{}
+
+var ExternalExecutionConfigDiscriminator = [8]byte{159, 157, 150, 212, 168, 103, 117, 39}
+
+func (obj ExternalExecutionConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ExternalExecutionConfigDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ExternalExecutionConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ExternalExecutionConfigDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[159 157 150 212 168 103 117 39]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/ccip_receiver/instructions.go b/chains/solana/gobindings/ccip_receiver/instructions.go
new file mode 100644
index 000000000..4d24fe944
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/instructions.go
@@ -0,0 +1,147 @@
+// This program an example of a CCIP Receiver Program.
+// Used to test CCIP Router execute.
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "bytes"
+ "fmt"
+ ag_spew "github.com/davecgh/go-spew/spew"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_text "github.com/gagliardetto/solana-go/text"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+var ProgramID ag_solanago.PublicKey
+
+func SetProgramID(pubkey ag_solanago.PublicKey) {
+ ProgramID = pubkey
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+}
+
+const ProgramName = "CcipReceiver"
+
+func init() {
+ if !ProgramID.IsZero() {
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+ }
+}
+
+var (
+ // The initialization is responsibility of the External User, CCIP is not handling initialization of Accounts
+ Instruction_Initialize = ag_binary.TypeID([8]byte{175, 175, 109, 31, 13, 152, 155, 237})
+
+ // This function is called by the CCIP Router to execute the CCIP message.
+ // The method name needs to be ccip_receive with Anchor encoding,
+ // if not using Anchor the discriminator needs to be [0x0b, 0xf4, 0x09, 0xf9, 0x2c, 0x53, 0x2f, 0xf5]
+ // You can send as many accounts as you need, specifying if mutable or not.
+ // But none of them could be an init, realloc or close.
+ // In this case, it increments the counter value by 1 and logs the parsed message.
+ Instruction_CcipReceive = ag_binary.TypeID([8]byte{11, 244, 9, 249, 44, 83, 47, 245})
+
+ Instruction_CcipTokenReleaseMint = ag_binary.TypeID([8]byte{20, 148, 113, 198, 229, 170, 71, 48})
+
+ Instruction_CcipTokenLockBurn = ag_binary.TypeID([8]byte{200, 14, 50, 9, 44, 91, 121, 37})
+)
+
+// InstructionIDToName returns the name of the instruction given its ID.
+func InstructionIDToName(id ag_binary.TypeID) string {
+ switch id {
+ case Instruction_Initialize:
+ return "Initialize"
+ case Instruction_CcipReceive:
+ return "CcipReceive"
+ case Instruction_CcipTokenReleaseMint:
+ return "CcipTokenReleaseMint"
+ case Instruction_CcipTokenLockBurn:
+ return "CcipTokenLockBurn"
+ default:
+ return ""
+ }
+}
+
+type Instruction struct {
+ ag_binary.BaseVariant
+}
+
+func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) {
+ if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok {
+ enToTree.EncodeToTree(parent)
+ } else {
+ parent.Child(ag_spew.Sdump(inst))
+ }
+}
+
+var InstructionImplDef = ag_binary.NewVariantDefinition(
+ ag_binary.AnchorTypeIDEncoding,
+ []ag_binary.VariantType{
+ {
+ "initialize", (*Initialize)(nil),
+ },
+ {
+ "ccip_receive", (*CcipReceive)(nil),
+ },
+ {
+ "ccip_token_release_mint", (*CcipTokenReleaseMint)(nil),
+ },
+ {
+ "ccip_token_lock_burn", (*CcipTokenLockBurn)(nil),
+ },
+ },
+)
+
+func (inst *Instruction) ProgramID() ag_solanago.PublicKey {
+ return ProgramID
+}
+
+func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) {
+ return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts()
+}
+
+func (inst *Instruction) Data() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil {
+ return nil, fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return buf.Bytes(), nil
+}
+
+func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
+ return encoder.Encode(inst.Impl, option)
+}
+
+func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
+ return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
+}
+
+func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
+ err := encoder.WriteBytes(inst.TypeID.Bytes(), false)
+ if err != nil {
+ return fmt.Errorf("unable to write variant type: %w", err)
+ }
+ return encoder.Encode(inst.Impl)
+}
+
+func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) {
+ inst, err := DecodeInstruction(accounts, data)
+ if err != nil {
+ return nil, err
+ }
+ return inst, nil
+}
+
+func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) {
+ inst := new(Instruction)
+ if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil {
+ return nil, fmt.Errorf("unable to decode instruction: %w", err)
+ }
+ if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok {
+ err := v.SetAccounts(accounts)
+ if err != nil {
+ return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
+ }
+ }
+ return inst, nil
+}
diff --git a/chains/solana/gobindings/ccip_receiver/testing_utils.go b/chains/solana/gobindings/ccip_receiver/testing_utils.go
new file mode 100644
index 000000000..bf21cd2a1
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/testing_utils.go
@@ -0,0 +1,20 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ "bytes"
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+func encodeT(data interface{}, buf *bytes.Buffer) error {
+ if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil {
+ return fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return nil
+}
+
+func decodeT(dst interface{}, data []byte) error {
+ return ag_binary.NewBorshDecoder(data).Decode(dst)
+}
diff --git a/chains/solana/gobindings/ccip_receiver/types.go b/chains/solana/gobindings/ccip_receiver/types.go
new file mode 100644
index 000000000..9e3c9cbe2
--- /dev/null
+++ b/chains/solana/gobindings/ccip_receiver/types.go
@@ -0,0 +1,277 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_receiver
+
+import (
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type Any2SolanaMessage struct {
+ MessageId [32]uint8
+ SourceChainSelector uint64
+ Sender []byte
+ Data []byte
+ TokenAmounts []SolanaTokenAmount
+}
+
+func (obj Any2SolanaMessage) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MessageId` param:
+ err = encoder.Encode(obj.MessageId)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourceChainSelector` param:
+ err = encoder.Encode(obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Sender` param:
+ err = encoder.Encode(obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenAmounts` param:
+ err = encoder.Encode(obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Any2SolanaMessage) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MessageId`:
+ err = decoder.Decode(&obj.MessageId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourceChainSelector`:
+ err = decoder.Decode(&obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Sender`:
+ err = decoder.Decode(&obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenAmounts`:
+ err = decoder.Decode(&obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type SolanaTokenAmount struct {
+ Token ag_solanago.PublicKey
+ Amount uint64
+}
+
+func (obj SolanaTokenAmount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Token` param:
+ err = encoder.Encode(obj.Token)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *SolanaTokenAmount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Token`:
+ err = decoder.Decode(&obj.Token)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type LockOrBurnInV1 struct {
+ Receiver []byte
+ RemoteChainSelector uint64
+ OriginalSender ag_solanago.PublicKey
+ Amount uint64
+ LocalToken ag_solanago.PublicKey
+}
+
+func (obj LockOrBurnInV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Receiver` param:
+ err = encoder.Encode(obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Serialize `RemoteChainSelector` param:
+ err = encoder.Encode(obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `OriginalSender` param:
+ err = encoder.Encode(obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Serialize `LocalToken` param:
+ err = encoder.Encode(obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *LockOrBurnInV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Receiver`:
+ err = decoder.Decode(&obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Deserialize `RemoteChainSelector`:
+ err = decoder.Decode(&obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OriginalSender`:
+ err = decoder.Decode(&obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LocalToken`:
+ err = decoder.Decode(&obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ReleaseOrMintInV1 struct {
+ OriginalSender []byte
+ RemoteChainSelector uint64
+ Receiver ag_solanago.PublicKey
+ Amount [32]uint8
+ LocalToken ag_solanago.PublicKey
+
+ // @dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the
+ // expected pool address for the given remoteChainSelector.
+ SourcePoolAddress []byte
+ SourcePoolData []byte
+
+ // @dev WARNING: offchainTokenData is untrusted data.
+ OffchainTokenData []byte
+}
+
+func (obj ReleaseOrMintInV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `OriginalSender` param:
+ err = encoder.Encode(obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Serialize `RemoteChainSelector` param:
+ err = encoder.Encode(obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Receiver` param:
+ err = encoder.Encode(obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Serialize `LocalToken` param:
+ err = encoder.Encode(obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourcePoolAddress` param:
+ err = encoder.Encode(obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourcePoolData` param:
+ err = encoder.Encode(obj.SourcePoolData)
+ if err != nil {
+ return err
+ }
+ // Serialize `OffchainTokenData` param:
+ err = encoder.Encode(obj.OffchainTokenData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ReleaseOrMintInV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `OriginalSender`:
+ err = decoder.Decode(&obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `RemoteChainSelector`:
+ err = decoder.Decode(&obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Receiver`:
+ err = decoder.Decode(&obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LocalToken`:
+ err = decoder.Decode(&obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourcePoolAddress`:
+ err = decoder.Decode(&obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourcePoolData`:
+ err = decoder.Decode(&obj.SourcePoolData)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OffchainTokenData`:
+ err = decoder.Decode(&obj.OffchainTokenData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/ccip_router/AcceptAdminRoleTokenAdminRegistry.go b/chains/solana/gobindings/ccip_router/AcceptAdminRoleTokenAdminRegistry.go
new file mode 100644
index 000000000..e7d4cc432
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/AcceptAdminRoleTokenAdminRegistry.go
@@ -0,0 +1,153 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Accepts the admin role of the token admin registry.
+//
+// The Pending Admin must call this function to accept the admin role of the Token Admin Registry.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for accepting the admin role.
+// * `mint` - The public key of the token mint.
+type AcceptAdminRoleTokenAdminRegistry struct {
+ Mint *ag_solanago.PublicKey
+
+ // [0] = [WRITE] tokenAdminRegistry
+ //
+ // [1] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAcceptAdminRoleTokenAdminRegistryInstructionBuilder creates a new `AcceptAdminRoleTokenAdminRegistry` instruction builder.
+func NewAcceptAdminRoleTokenAdminRegistryInstructionBuilder() *AcceptAdminRoleTokenAdminRegistry {
+ nd := &AcceptAdminRoleTokenAdminRegistry{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetMint sets the "mint" parameter.
+func (inst *AcceptAdminRoleTokenAdminRegistry) SetMint(mint ag_solanago.PublicKey) *AcceptAdminRoleTokenAdminRegistry {
+ inst.Mint = &mint
+ return inst
+}
+
+// SetTokenAdminRegistryAccount sets the "tokenAdminRegistry" account.
+func (inst *AcceptAdminRoleTokenAdminRegistry) SetTokenAdminRegistryAccount(tokenAdminRegistry ag_solanago.PublicKey) *AcceptAdminRoleTokenAdminRegistry {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(tokenAdminRegistry).WRITE()
+ return inst
+}
+
+// GetTokenAdminRegistryAccount gets the "tokenAdminRegistry" account.
+func (inst *AcceptAdminRoleTokenAdminRegistry) GetTokenAdminRegistryAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AcceptAdminRoleTokenAdminRegistry) SetAuthorityAccount(authority ag_solanago.PublicKey) *AcceptAdminRoleTokenAdminRegistry {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AcceptAdminRoleTokenAdminRegistry) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst AcceptAdminRoleTokenAdminRegistry) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AcceptAdminRoleTokenAdminRegistry,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AcceptAdminRoleTokenAdminRegistry) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AcceptAdminRoleTokenAdminRegistry) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Mint == nil {
+ return errors.New("Mint parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.TokenAdminRegistry is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AcceptAdminRoleTokenAdminRegistry) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AcceptAdminRoleTokenAdminRegistry")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Mint", *inst.Mint))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("tokenAdminRegistry", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj AcceptAdminRoleTokenAdminRegistry) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *AcceptAdminRoleTokenAdminRegistry) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewAcceptAdminRoleTokenAdminRegistryInstruction declares a new AcceptAdminRoleTokenAdminRegistry instruction with the provided parameters and accounts.
+func NewAcceptAdminRoleTokenAdminRegistryInstruction(
+ // Parameters:
+ mint ag_solanago.PublicKey,
+ // Accounts:
+ tokenAdminRegistry ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *AcceptAdminRoleTokenAdminRegistry {
+ return NewAcceptAdminRoleTokenAdminRegistryInstructionBuilder().
+ SetMint(mint).
+ SetTokenAdminRegistryAccount(tokenAdminRegistry).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/AcceptAdminRoleTokenAdminRegistry_test.go b/chains/solana/gobindings/ccip_router/AcceptAdminRoleTokenAdminRegistry_test.go
new file mode 100644
index 000000000..44baeb8e0
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/AcceptAdminRoleTokenAdminRegistry_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AcceptAdminRoleTokenAdminRegistry(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AcceptAdminRoleTokenAdminRegistry"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AcceptAdminRoleTokenAdminRegistry)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AcceptAdminRoleTokenAdminRegistry)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/AcceptOwnership.go b/chains/solana/gobindings/ccip_router/AcceptOwnership.go
new file mode 100644
index 000000000..1f3b46472
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/AcceptOwnership.go
@@ -0,0 +1,124 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Accepts the ownership of the router by the proposed owner.
+//
+// # Shared func signature with other programs
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for accepting ownership.
+// The new owner must be a signer of the transaction.
+type AcceptOwnership struct {
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAcceptOwnershipInstructionBuilder creates a new `AcceptOwnership` instruction builder.
+func NewAcceptOwnershipInstructionBuilder() *AcceptOwnership {
+ nd := &AcceptOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *AcceptOwnership) SetConfigAccount(config ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *AcceptOwnership) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AcceptOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AcceptOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst AcceptOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AcceptOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AcceptOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AcceptOwnership) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AcceptOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AcceptOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj AcceptOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *AcceptOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewAcceptOwnershipInstruction declares a new AcceptOwnership instruction with the provided parameters and accounts.
+func NewAcceptOwnershipInstruction(
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *AcceptOwnership {
+ return NewAcceptOwnershipInstructionBuilder().
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/AcceptOwnership_test.go b/chains/solana/gobindings/ccip_router/AcceptOwnership_test.go
new file mode 100644
index 000000000..da475cd5d
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/AcceptOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AcceptOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AcceptOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AcceptOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AcceptOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/AddBillingTokenConfig.go b/chains/solana/gobindings/ccip_router/AddBillingTokenConfig.go
new file mode 100644
index 000000000..57d4cefd9
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/AddBillingTokenConfig.go
@@ -0,0 +1,291 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Adds a billing token configuration.
+// Only CCIP Admin can add a billing token configuration.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for adding the billing token configuration.
+// * `config` - The billing token configuration to be added.
+type AddBillingTokenConfig struct {
+ Config *BillingTokenConfig
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] billingTokenConfig
+ //
+ // [2] = [] tokenProgram
+ // ··········· type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+ // ··········· with a constraint enforcing that it is one of the two allowed programs.
+ //
+ // [3] = [] feeTokenMint
+ //
+ // [4] = [WRITE] feeTokenReceiver
+ //
+ // [5] = [WRITE, SIGNER] authority
+ //
+ // [6] = [] feeBillingSigner
+ //
+ // [7] = [] associatedTokenProgram
+ //
+ // [8] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAddBillingTokenConfigInstructionBuilder creates a new `AddBillingTokenConfig` instruction builder.
+func NewAddBillingTokenConfigInstructionBuilder() *AddBillingTokenConfig {
+ nd := &AddBillingTokenConfig{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 9),
+ }
+ return nd
+}
+
+// SetConfig sets the "config" parameter.
+func (inst *AddBillingTokenConfig) SetConfig(config BillingTokenConfig) *AddBillingTokenConfig {
+ inst.Config = &config
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *AddBillingTokenConfig) SetConfigAccount(config ag_solanago.PublicKey) *AddBillingTokenConfig {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *AddBillingTokenConfig) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetBillingTokenConfigAccount sets the "billingTokenConfig" account.
+func (inst *AddBillingTokenConfig) SetBillingTokenConfigAccount(billingTokenConfig ag_solanago.PublicKey) *AddBillingTokenConfig {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(billingTokenConfig).WRITE()
+ return inst
+}
+
+// GetBillingTokenConfigAccount gets the "billingTokenConfig" account.
+func (inst *AddBillingTokenConfig) GetBillingTokenConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetTokenProgramAccount sets the "tokenProgram" account.
+// type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+// with a constraint enforcing that it is one of the two allowed programs.
+func (inst *AddBillingTokenConfig) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *AddBillingTokenConfig {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(tokenProgram)
+ return inst
+}
+
+// GetTokenProgramAccount gets the "tokenProgram" account.
+// type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+// with a constraint enforcing that it is one of the two allowed programs.
+func (inst *AddBillingTokenConfig) GetTokenProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetFeeTokenMintAccount sets the "feeTokenMint" account.
+func (inst *AddBillingTokenConfig) SetFeeTokenMintAccount(feeTokenMint ag_solanago.PublicKey) *AddBillingTokenConfig {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(feeTokenMint)
+ return inst
+}
+
+// GetFeeTokenMintAccount gets the "feeTokenMint" account.
+func (inst *AddBillingTokenConfig) GetFeeTokenMintAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetFeeTokenReceiverAccount sets the "feeTokenReceiver" account.
+func (inst *AddBillingTokenConfig) SetFeeTokenReceiverAccount(feeTokenReceiver ag_solanago.PublicKey) *AddBillingTokenConfig {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(feeTokenReceiver).WRITE()
+ return inst
+}
+
+// GetFeeTokenReceiverAccount gets the "feeTokenReceiver" account.
+func (inst *AddBillingTokenConfig) GetFeeTokenReceiverAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AddBillingTokenConfig) SetAuthorityAccount(authority ag_solanago.PublicKey) *AddBillingTokenConfig {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AddBillingTokenConfig) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetFeeBillingSignerAccount sets the "feeBillingSigner" account.
+func (inst *AddBillingTokenConfig) SetFeeBillingSignerAccount(feeBillingSigner ag_solanago.PublicKey) *AddBillingTokenConfig {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(feeBillingSigner)
+ return inst
+}
+
+// GetFeeBillingSignerAccount gets the "feeBillingSigner" account.
+func (inst *AddBillingTokenConfig) GetFeeBillingSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+// SetAssociatedTokenProgramAccount sets the "associatedTokenProgram" account.
+func (inst *AddBillingTokenConfig) SetAssociatedTokenProgramAccount(associatedTokenProgram ag_solanago.PublicKey) *AddBillingTokenConfig {
+ inst.AccountMetaSlice[7] = ag_solanago.Meta(associatedTokenProgram)
+ return inst
+}
+
+// GetAssociatedTokenProgramAccount gets the "associatedTokenProgram" account.
+func (inst *AddBillingTokenConfig) GetAssociatedTokenProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[7]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *AddBillingTokenConfig) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *AddBillingTokenConfig {
+ inst.AccountMetaSlice[8] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *AddBillingTokenConfig) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[8]
+}
+
+func (inst AddBillingTokenConfig) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AddBillingTokenConfig,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AddBillingTokenConfig) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AddBillingTokenConfig) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Config == nil {
+ return errors.New("Config parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.BillingTokenConfig is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.TokenProgram is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.FeeTokenMint is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.FeeTokenReceiver is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.FeeBillingSigner is not set")
+ }
+ if inst.AccountMetaSlice[7] == nil {
+ return errors.New("accounts.AssociatedTokenProgram is not set")
+ }
+ if inst.AccountMetaSlice[8] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AddBillingTokenConfig) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AddBillingTokenConfig")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Config", *inst.Config))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=9]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" billingTokenConfig", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" tokenProgram", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" feeTokenMint", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" feeTokenReceiver", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" feeBillingSigner", inst.AccountMetaSlice[6]))
+ accountsBranch.Child(ag_format.Meta("associatedTokenProgram", inst.AccountMetaSlice[7]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[8]))
+ })
+ })
+ })
+}
+
+func (obj AddBillingTokenConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Config` param:
+ err = encoder.Encode(obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *AddBillingTokenConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Config`:
+ err = decoder.Decode(&obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewAddBillingTokenConfigInstruction declares a new AddBillingTokenConfig instruction with the provided parameters and accounts.
+func NewAddBillingTokenConfigInstruction(
+ // Parameters:
+ config BillingTokenConfig,
+ // Accounts:
+ configAccount ag_solanago.PublicKey,
+ billingTokenConfig ag_solanago.PublicKey,
+ tokenProgram ag_solanago.PublicKey,
+ feeTokenMint ag_solanago.PublicKey,
+ feeTokenReceiver ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ feeBillingSigner ag_solanago.PublicKey,
+ associatedTokenProgram ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *AddBillingTokenConfig {
+ return NewAddBillingTokenConfigInstructionBuilder().
+ SetConfig(config).
+ SetConfigAccount(configAccount).
+ SetBillingTokenConfigAccount(billingTokenConfig).
+ SetTokenProgramAccount(tokenProgram).
+ SetFeeTokenMintAccount(feeTokenMint).
+ SetFeeTokenReceiverAccount(feeTokenReceiver).
+ SetAuthorityAccount(authority).
+ SetFeeBillingSignerAccount(feeBillingSigner).
+ SetAssociatedTokenProgramAccount(associatedTokenProgram).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/AddBillingTokenConfig_test.go b/chains/solana/gobindings/ccip_router/AddBillingTokenConfig_test.go
new file mode 100644
index 000000000..78a2d6b7d
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/AddBillingTokenConfig_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AddBillingTokenConfig(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AddBillingTokenConfig"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AddBillingTokenConfig)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AddBillingTokenConfig)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/AddChainSelector.go b/chains/solana/gobindings/ccip_router/AddChainSelector.go
new file mode 100644
index 000000000..64c33f4fd
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/AddChainSelector.go
@@ -0,0 +1,241 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Adds a new chain selector to the router.
+//
+// The Admin needs to add any new chain supported (this means both OnRamp and OffRamp).
+// When adding a new chain, the Admin needs to specify if it's enabled or not.
+// They may enable only source, or only destination, or neither, or both.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for adding the chain selector.
+// * `new_chain_selector` - The new chain selector to be added.
+// * `source_chain_config` - The configuration for the chain as source.
+// * `dest_chain_config` - The configuration for the chain as destination.
+type AddChainSelector struct {
+ NewChainSelector *uint64
+ SourceChainConfig *SourceChainConfig
+ DestChainConfig *DestChainConfig
+
+ // [0] = [WRITE] chainState
+ //
+ // [1] = [] config
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAddChainSelectorInstructionBuilder creates a new `AddChainSelector` instruction builder.
+func NewAddChainSelectorInstructionBuilder() *AddChainSelector {
+ nd := &AddChainSelector{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetNewChainSelector sets the "newChainSelector" parameter.
+func (inst *AddChainSelector) SetNewChainSelector(newChainSelector uint64) *AddChainSelector {
+ inst.NewChainSelector = &newChainSelector
+ return inst
+}
+
+// SetSourceChainConfig sets the "sourceChainConfig" parameter.
+func (inst *AddChainSelector) SetSourceChainConfig(sourceChainConfig SourceChainConfig) *AddChainSelector {
+ inst.SourceChainConfig = &sourceChainConfig
+ return inst
+}
+
+// SetDestChainConfig sets the "destChainConfig" parameter.
+func (inst *AddChainSelector) SetDestChainConfig(destChainConfig DestChainConfig) *AddChainSelector {
+ inst.DestChainConfig = &destChainConfig
+ return inst
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *AddChainSelector) SetChainStateAccount(chainState ag_solanago.PublicKey) *AddChainSelector {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(chainState).WRITE()
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *AddChainSelector) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *AddChainSelector) SetConfigAccount(config ag_solanago.PublicKey) *AddChainSelector {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *AddChainSelector) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AddChainSelector) SetAuthorityAccount(authority ag_solanago.PublicKey) *AddChainSelector {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AddChainSelector) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *AddChainSelector) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *AddChainSelector {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *AddChainSelector) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst AddChainSelector) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AddChainSelector,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AddChainSelector) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AddChainSelector) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.NewChainSelector == nil {
+ return errors.New("NewChainSelector parameter is not set")
+ }
+ if inst.SourceChainConfig == nil {
+ return errors.New("SourceChainConfig parameter is not set")
+ }
+ if inst.DestChainConfig == nil {
+ return errors.New("DestChainConfig parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AddChainSelector) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AddChainSelector")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=3]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" NewChainSelector", *inst.NewChainSelector))
+ paramsBranch.Child(ag_format.Param("SourceChainConfig", *inst.SourceChainConfig))
+ paramsBranch.Child(ag_format.Param(" DestChainConfig", *inst.DestChainConfig))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" chainState", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj AddChainSelector) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `NewChainSelector` param:
+ err = encoder.Encode(obj.NewChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourceChainConfig` param:
+ err = encoder.Encode(obj.SourceChainConfig)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestChainConfig` param:
+ err = encoder.Encode(obj.DestChainConfig)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *AddChainSelector) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `NewChainSelector`:
+ err = decoder.Decode(&obj.NewChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourceChainConfig`:
+ err = decoder.Decode(&obj.SourceChainConfig)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestChainConfig`:
+ err = decoder.Decode(&obj.DestChainConfig)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewAddChainSelectorInstruction declares a new AddChainSelector instruction with the provided parameters and accounts.
+func NewAddChainSelectorInstruction(
+ // Parameters:
+ newChainSelector uint64,
+ sourceChainConfig SourceChainConfig,
+ destChainConfig DestChainConfig,
+ // Accounts:
+ chainState ag_solanago.PublicKey,
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *AddChainSelector {
+ return NewAddChainSelectorInstructionBuilder().
+ SetNewChainSelector(newChainSelector).
+ SetSourceChainConfig(sourceChainConfig).
+ SetDestChainConfig(destChainConfig).
+ SetChainStateAccount(chainState).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/AddChainSelector_test.go b/chains/solana/gobindings/ccip_router/AddChainSelector_test.go
new file mode 100644
index 000000000..8398360f7
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/AddChainSelector_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AddChainSelector(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AddChainSelector"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AddChainSelector)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AddChainSelector)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/CcipSend.go b/chains/solana/gobindings/ccip_router/CcipSend.go
new file mode 100644
index 000000000..12c9e55ce
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/CcipSend.go
@@ -0,0 +1,378 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// ON RAMP FLOW
+// Sends a message to the destination chain.
+//
+// Request a message to be sent to the destination chain.
+// The method name needs to be ccip_send with Anchor encoding.
+// This function is called by the CCIP Sender Contract (or final user) to send a message to the CCIP Router.
+// The message will be sent to the receiver on the destination chain selector.
+// This message emits the event CCIPSendRequested with all the necessary data to be retrieved by the OffChain Code
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for sending the message.
+// * `dest_chain_selector` - The chain selector for the destination chain.
+// * `message` - The message to be sent. The size limit of data is 256 bytes.
+type CcipSend struct {
+ DestChainSelector *uint64
+ Message *Solana2AnyMessage
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] chainState
+ //
+ // [2] = [WRITE] nonce
+ //
+ // [3] = [WRITE, SIGNER] authority
+ //
+ // [4] = [] systemProgram
+ //
+ // [5] = [] feeTokenProgram
+ // ··········· type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+ // ··········· with a constraint enforcing that it is one of the two allowed programs.
+ //
+ // [6] = [] feeTokenMint
+ //
+ // [7] = [] feeTokenConfig
+ //
+ // [8] = [WRITE] feeTokenUserAssociatedAccount
+ //
+ // [9] = [WRITE] feeTokenReceiver
+ //
+ // [10] = [] feeBillingSigner
+ //
+ // [11] = [WRITE] tokenPoolsSigner
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewCcipSendInstructionBuilder creates a new `CcipSend` instruction builder.
+func NewCcipSendInstructionBuilder() *CcipSend {
+ nd := &CcipSend{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 12),
+ }
+ return nd
+}
+
+// SetDestChainSelector sets the "destChainSelector" parameter.
+func (inst *CcipSend) SetDestChainSelector(destChainSelector uint64) *CcipSend {
+ inst.DestChainSelector = &destChainSelector
+ return inst
+}
+
+// SetMessage sets the "message" parameter.
+func (inst *CcipSend) SetMessage(message Solana2AnyMessage) *CcipSend {
+ inst.Message = &message
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *CcipSend) SetConfigAccount(config ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *CcipSend) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *CcipSend) SetChainStateAccount(chainState ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(chainState).WRITE()
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *CcipSend) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetNonceAccount sets the "nonce" account.
+func (inst *CcipSend) SetNonceAccount(nonce ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(nonce).WRITE()
+ return inst
+}
+
+// GetNonceAccount gets the "nonce" account.
+func (inst *CcipSend) GetNonceAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *CcipSend) SetAuthorityAccount(authority ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *CcipSend) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *CcipSend) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *CcipSend) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetFeeTokenProgramAccount sets the "feeTokenProgram" account.
+// type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+// with a constraint enforcing that it is one of the two allowed programs.
+func (inst *CcipSend) SetFeeTokenProgramAccount(feeTokenProgram ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(feeTokenProgram)
+ return inst
+}
+
+// GetFeeTokenProgramAccount gets the "feeTokenProgram" account.
+// type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+// with a constraint enforcing that it is one of the two allowed programs.
+func (inst *CcipSend) GetFeeTokenProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetFeeTokenMintAccount sets the "feeTokenMint" account.
+func (inst *CcipSend) SetFeeTokenMintAccount(feeTokenMint ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(feeTokenMint)
+ return inst
+}
+
+// GetFeeTokenMintAccount gets the "feeTokenMint" account.
+func (inst *CcipSend) GetFeeTokenMintAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+// SetFeeTokenConfigAccount sets the "feeTokenConfig" account.
+func (inst *CcipSend) SetFeeTokenConfigAccount(feeTokenConfig ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[7] = ag_solanago.Meta(feeTokenConfig)
+ return inst
+}
+
+// GetFeeTokenConfigAccount gets the "feeTokenConfig" account.
+func (inst *CcipSend) GetFeeTokenConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[7]
+}
+
+// SetFeeTokenUserAssociatedAccountAccount sets the "feeTokenUserAssociatedAccount" account.
+func (inst *CcipSend) SetFeeTokenUserAssociatedAccountAccount(feeTokenUserAssociatedAccount ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[8] = ag_solanago.Meta(feeTokenUserAssociatedAccount).WRITE()
+ return inst
+}
+
+// GetFeeTokenUserAssociatedAccountAccount gets the "feeTokenUserAssociatedAccount" account.
+func (inst *CcipSend) GetFeeTokenUserAssociatedAccountAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[8]
+}
+
+// SetFeeTokenReceiverAccount sets the "feeTokenReceiver" account.
+func (inst *CcipSend) SetFeeTokenReceiverAccount(feeTokenReceiver ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[9] = ag_solanago.Meta(feeTokenReceiver).WRITE()
+ return inst
+}
+
+// GetFeeTokenReceiverAccount gets the "feeTokenReceiver" account.
+func (inst *CcipSend) GetFeeTokenReceiverAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[9]
+}
+
+// SetFeeBillingSignerAccount sets the "feeBillingSigner" account.
+func (inst *CcipSend) SetFeeBillingSignerAccount(feeBillingSigner ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[10] = ag_solanago.Meta(feeBillingSigner)
+ return inst
+}
+
+// GetFeeBillingSignerAccount gets the "feeBillingSigner" account.
+func (inst *CcipSend) GetFeeBillingSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[10]
+}
+
+// SetTokenPoolsSignerAccount sets the "tokenPoolsSigner" account.
+func (inst *CcipSend) SetTokenPoolsSignerAccount(tokenPoolsSigner ag_solanago.PublicKey) *CcipSend {
+ inst.AccountMetaSlice[11] = ag_solanago.Meta(tokenPoolsSigner).WRITE()
+ return inst
+}
+
+// GetTokenPoolsSignerAccount gets the "tokenPoolsSigner" account.
+func (inst *CcipSend) GetTokenPoolsSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[11]
+}
+
+func (inst CcipSend) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_CcipSend,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst CcipSend) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *CcipSend) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.DestChainSelector == nil {
+ return errors.New("DestChainSelector parameter is not set")
+ }
+ if inst.Message == nil {
+ return errors.New("Message parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Nonce is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.FeeTokenProgram is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.FeeTokenMint is not set")
+ }
+ if inst.AccountMetaSlice[7] == nil {
+ return errors.New("accounts.FeeTokenConfig is not set")
+ }
+ if inst.AccountMetaSlice[8] == nil {
+ return errors.New("accounts.FeeTokenUserAssociatedAccount is not set")
+ }
+ if inst.AccountMetaSlice[9] == nil {
+ return errors.New("accounts.FeeTokenReceiver is not set")
+ }
+ if inst.AccountMetaSlice[10] == nil {
+ return errors.New("accounts.FeeBillingSigner is not set")
+ }
+ if inst.AccountMetaSlice[11] == nil {
+ return errors.New("accounts.TokenPoolsSigner is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *CcipSend) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("CcipSend")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("DestChainSelector", *inst.DestChainSelector))
+ paramsBranch.Child(ag_format.Param(" Message", *inst.Message))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=12]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" chainState", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" nonce", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" feeTokenProgram", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" feeTokenMint", inst.AccountMetaSlice[6]))
+ accountsBranch.Child(ag_format.Meta(" feeTokenConfig", inst.AccountMetaSlice[7]))
+ accountsBranch.Child(ag_format.Meta("feeTokenUserAssociated", inst.AccountMetaSlice[8]))
+ accountsBranch.Child(ag_format.Meta(" feeTokenReceiver", inst.AccountMetaSlice[9]))
+ accountsBranch.Child(ag_format.Meta(" feeBillingSigner", inst.AccountMetaSlice[10]))
+ accountsBranch.Child(ag_format.Meta(" tokenPoolsSigner", inst.AccountMetaSlice[11]))
+ })
+ })
+ })
+}
+
+func (obj CcipSend) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `DestChainSelector` param:
+ err = encoder.Encode(obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Message` param:
+ err = encoder.Encode(obj.Message)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *CcipSend) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `DestChainSelector`:
+ err = decoder.Decode(&obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Message`:
+ err = decoder.Decode(&obj.Message)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewCcipSendInstruction declares a new CcipSend instruction with the provided parameters and accounts.
+func NewCcipSendInstruction(
+ // Parameters:
+ destChainSelector uint64,
+ message Solana2AnyMessage,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ chainState ag_solanago.PublicKey,
+ nonce ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey,
+ feeTokenProgram ag_solanago.PublicKey,
+ feeTokenMint ag_solanago.PublicKey,
+ feeTokenConfig ag_solanago.PublicKey,
+ feeTokenUserAssociatedAccount ag_solanago.PublicKey,
+ feeTokenReceiver ag_solanago.PublicKey,
+ feeBillingSigner ag_solanago.PublicKey,
+ tokenPoolsSigner ag_solanago.PublicKey) *CcipSend {
+ return NewCcipSendInstructionBuilder().
+ SetDestChainSelector(destChainSelector).
+ SetMessage(message).
+ SetConfigAccount(config).
+ SetChainStateAccount(chainState).
+ SetNonceAccount(nonce).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram).
+ SetFeeTokenProgramAccount(feeTokenProgram).
+ SetFeeTokenMintAccount(feeTokenMint).
+ SetFeeTokenConfigAccount(feeTokenConfig).
+ SetFeeTokenUserAssociatedAccountAccount(feeTokenUserAssociatedAccount).
+ SetFeeTokenReceiverAccount(feeTokenReceiver).
+ SetFeeBillingSignerAccount(feeBillingSigner).
+ SetTokenPoolsSignerAccount(tokenPoolsSigner)
+}
diff --git a/chains/solana/gobindings/ccip_router/CcipSend_test.go b/chains/solana/gobindings/ccip_router/CcipSend_test.go
new file mode 100644
index 000000000..a9f819342
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/CcipSend_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_CcipSend(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("CcipSend"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(CcipSend)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(CcipSend)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/Commit.go b/chains/solana/gobindings/ccip_router/Commit.go
new file mode 100644
index 000000000..48c5eccaf
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/Commit.go
@@ -0,0 +1,290 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// OFF RAMP FLOW
+// Commits a report to the router.
+//
+// The method name needs to be commit with Anchor encoding.
+//
+// This function is called by the OffChain when committing one Report to the Solana Router.
+// In this Flow only one report is sent, the Commit Report. This is different as EVM does,
+// this is because here all the chain state is stored in one account per Merkle Tree Root.
+// So, to avoid having to send a dynamic size array of accounts, in this message only one Commit Report Account is sent.
+// This message validates the signatures of the report and stores the Merkle Root in the Commit Report Account.
+// The Report must contain an interval of messages, and the min of them must be the next sequence number expected.
+// The max size of the interval is 64.
+// This message emits two events: CommitReportAccepted and Transmitted.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for the commit.
+// * `report_context_byte_words` - consists of:
+// * report_context_byte_words[0]: ConfigDigest
+// * report_context_byte_words[1]: 24 byte padding, 8 byte sequence number
+// * report_context_byte_words[2]: ExtraHash
+// * `report` - The commit input report, single merkle root with RMN signatures and price updates
+// * `signatures` - The list of signatures. v0.29.0 - anchor idl does not build with ocr3base::SIGNATURE_LENGTH
+type Commit struct {
+ ReportContextByteWords *[3][32]uint8
+ Report *CommitInput
+ Signatures *[][65]uint8
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [WRITE] chainState
+ //
+ // [2] = [WRITE] commitReport
+ //
+ // [3] = [WRITE, SIGNER] authority
+ //
+ // [4] = [] systemProgram
+ //
+ // [5] = [] sysvarInstructions
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewCommitInstructionBuilder creates a new `Commit` instruction builder.
+func NewCommitInstructionBuilder() *Commit {
+ nd := &Commit{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 6),
+ }
+ return nd
+}
+
+// SetReportContextByteWords sets the "reportContextByteWords" parameter.
+func (inst *Commit) SetReportContextByteWords(reportContextByteWords [3][32]uint8) *Commit {
+ inst.ReportContextByteWords = &reportContextByteWords
+ return inst
+}
+
+// SetReport sets the "report" parameter.
+func (inst *Commit) SetReport(report CommitInput) *Commit {
+ inst.Report = &report
+ return inst
+}
+
+// SetSignatures sets the "signatures" parameter.
+func (inst *Commit) SetSignatures(signatures [][65]uint8) *Commit {
+ inst.Signatures = &signatures
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *Commit) SetConfigAccount(config ag_solanago.PublicKey) *Commit {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *Commit) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *Commit) SetChainStateAccount(chainState ag_solanago.PublicKey) *Commit {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(chainState).WRITE()
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *Commit) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetCommitReportAccount sets the "commitReport" account.
+func (inst *Commit) SetCommitReportAccount(commitReport ag_solanago.PublicKey) *Commit {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(commitReport).WRITE()
+ return inst
+}
+
+// GetCommitReportAccount gets the "commitReport" account.
+func (inst *Commit) GetCommitReportAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *Commit) SetAuthorityAccount(authority ag_solanago.PublicKey) *Commit {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *Commit) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *Commit) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Commit {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *Commit) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetSysvarInstructionsAccount sets the "sysvarInstructions" account.
+func (inst *Commit) SetSysvarInstructionsAccount(sysvarInstructions ag_solanago.PublicKey) *Commit {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(sysvarInstructions)
+ return inst
+}
+
+// GetSysvarInstructionsAccount gets the "sysvarInstructions" account.
+func (inst *Commit) GetSysvarInstructionsAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+func (inst Commit) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Commit,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Commit) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Commit) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ReportContextByteWords == nil {
+ return errors.New("ReportContextByteWords parameter is not set")
+ }
+ if inst.Report == nil {
+ return errors.New("Report parameter is not set")
+ }
+ if inst.Signatures == nil {
+ return errors.New("Signatures parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.CommitReport is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.SysvarInstructions is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Commit) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Commit")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=3]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("ReportContextByteWords", *inst.ReportContextByteWords))
+ paramsBranch.Child(ag_format.Param(" Report", *inst.Report))
+ paramsBranch.Child(ag_format.Param(" Signatures", *inst.Signatures))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=6]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" chainState", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" commitReport", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta("sysvarInstructions", inst.AccountMetaSlice[5]))
+ })
+ })
+ })
+}
+
+func (obj Commit) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ReportContextByteWords` param:
+ err = encoder.Encode(obj.ReportContextByteWords)
+ if err != nil {
+ return err
+ }
+ // Serialize `Report` param:
+ err = encoder.Encode(obj.Report)
+ if err != nil {
+ return err
+ }
+ // Serialize `Signatures` param:
+ err = encoder.Encode(obj.Signatures)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *Commit) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ReportContextByteWords`:
+ err = decoder.Decode(&obj.ReportContextByteWords)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Report`:
+ err = decoder.Decode(&obj.Report)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Signatures`:
+ err = decoder.Decode(&obj.Signatures)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewCommitInstruction declares a new Commit instruction with the provided parameters and accounts.
+func NewCommitInstruction(
+ // Parameters:
+ reportContextByteWords [3][32]uint8,
+ report CommitInput,
+ signatures [][65]uint8,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ chainState ag_solanago.PublicKey,
+ commitReport ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey,
+ sysvarInstructions ag_solanago.PublicKey) *Commit {
+ return NewCommitInstructionBuilder().
+ SetReportContextByteWords(reportContextByteWords).
+ SetReport(report).
+ SetSignatures(signatures).
+ SetConfigAccount(config).
+ SetChainStateAccount(chainState).
+ SetCommitReportAccount(commitReport).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram).
+ SetSysvarInstructionsAccount(sysvarInstructions)
+}
diff --git a/chains/solana/gobindings/ccip_router/Commit_test.go b/chains/solana/gobindings/ccip_router/Commit_test.go
new file mode 100644
index 000000000..fcaf33cc9
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/Commit_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Commit(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Commit"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Commit)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Commit)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/DisableDestChainSelector.go b/chains/solana/gobindings/ccip_router/DisableDestChainSelector.go
new file mode 100644
index 000000000..1c3085904
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/DisableDestChainSelector.go
@@ -0,0 +1,172 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Disables the destination chain selector.
+//
+// The Admin is the only one able to disable the chain selector as destination. This method is thought of as an emergency kill-switch.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for disabling the chain selector.
+// * `dest_chain_selector` - The destination chain selector to be disabled.
+type DisableDestChainSelector struct {
+ DestChainSelector *uint64
+
+ // [0] = [WRITE] chainState
+ //
+ // [1] = [] config
+ //
+ // [2] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewDisableDestChainSelectorInstructionBuilder creates a new `DisableDestChainSelector` instruction builder.
+func NewDisableDestChainSelectorInstructionBuilder() *DisableDestChainSelector {
+ nd := &DisableDestChainSelector{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetDestChainSelector sets the "destChainSelector" parameter.
+func (inst *DisableDestChainSelector) SetDestChainSelector(destChainSelector uint64) *DisableDestChainSelector {
+ inst.DestChainSelector = &destChainSelector
+ return inst
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *DisableDestChainSelector) SetChainStateAccount(chainState ag_solanago.PublicKey) *DisableDestChainSelector {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(chainState).WRITE()
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *DisableDestChainSelector) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *DisableDestChainSelector) SetConfigAccount(config ag_solanago.PublicKey) *DisableDestChainSelector {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *DisableDestChainSelector) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *DisableDestChainSelector) SetAuthorityAccount(authority ag_solanago.PublicKey) *DisableDestChainSelector {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *DisableDestChainSelector) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst DisableDestChainSelector) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_DisableDestChainSelector,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst DisableDestChainSelector) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *DisableDestChainSelector) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.DestChainSelector == nil {
+ return errors.New("DestChainSelector parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *DisableDestChainSelector) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("DisableDestChainSelector")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("DestChainSelector", *inst.DestChainSelector))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("chainState", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj DisableDestChainSelector) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `DestChainSelector` param:
+ err = encoder.Encode(obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *DisableDestChainSelector) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `DestChainSelector`:
+ err = decoder.Decode(&obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewDisableDestChainSelectorInstruction declares a new DisableDestChainSelector instruction with the provided parameters and accounts.
+func NewDisableDestChainSelectorInstruction(
+ // Parameters:
+ destChainSelector uint64,
+ // Accounts:
+ chainState ag_solanago.PublicKey,
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *DisableDestChainSelector {
+ return NewDisableDestChainSelectorInstructionBuilder().
+ SetDestChainSelector(destChainSelector).
+ SetChainStateAccount(chainState).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/DisableDestChainSelector_test.go b/chains/solana/gobindings/ccip_router/DisableDestChainSelector_test.go
new file mode 100644
index 000000000..75a31f0fd
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/DisableDestChainSelector_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_DisableDestChainSelector(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("DisableDestChainSelector"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(DisableDestChainSelector)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(DisableDestChainSelector)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/DisableSourceChainSelector.go b/chains/solana/gobindings/ccip_router/DisableSourceChainSelector.go
new file mode 100644
index 000000000..2fa7652cc
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/DisableSourceChainSelector.go
@@ -0,0 +1,172 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Disables the source chain selector.
+//
+// The Admin is the only one able to disable the chain selector as source. This method is thought of as an emergency kill-switch.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for disabling the chain selector.
+// * `source_chain_selector` - The source chain selector to be disabled.
+type DisableSourceChainSelector struct {
+ SourceChainSelector *uint64
+
+ // [0] = [WRITE] chainState
+ //
+ // [1] = [] config
+ //
+ // [2] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewDisableSourceChainSelectorInstructionBuilder creates a new `DisableSourceChainSelector` instruction builder.
+func NewDisableSourceChainSelectorInstructionBuilder() *DisableSourceChainSelector {
+ nd := &DisableSourceChainSelector{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetSourceChainSelector sets the "sourceChainSelector" parameter.
+func (inst *DisableSourceChainSelector) SetSourceChainSelector(sourceChainSelector uint64) *DisableSourceChainSelector {
+ inst.SourceChainSelector = &sourceChainSelector
+ return inst
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *DisableSourceChainSelector) SetChainStateAccount(chainState ag_solanago.PublicKey) *DisableSourceChainSelector {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(chainState).WRITE()
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *DisableSourceChainSelector) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *DisableSourceChainSelector) SetConfigAccount(config ag_solanago.PublicKey) *DisableSourceChainSelector {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *DisableSourceChainSelector) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *DisableSourceChainSelector) SetAuthorityAccount(authority ag_solanago.PublicKey) *DisableSourceChainSelector {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *DisableSourceChainSelector) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst DisableSourceChainSelector) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_DisableSourceChainSelector,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst DisableSourceChainSelector) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *DisableSourceChainSelector) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.SourceChainSelector == nil {
+ return errors.New("SourceChainSelector parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *DisableSourceChainSelector) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("DisableSourceChainSelector")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("SourceChainSelector", *inst.SourceChainSelector))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("chainState", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj DisableSourceChainSelector) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `SourceChainSelector` param:
+ err = encoder.Encode(obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *DisableSourceChainSelector) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `SourceChainSelector`:
+ err = decoder.Decode(&obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewDisableSourceChainSelectorInstruction declares a new DisableSourceChainSelector instruction with the provided parameters and accounts.
+func NewDisableSourceChainSelectorInstruction(
+ // Parameters:
+ sourceChainSelector uint64,
+ // Accounts:
+ chainState ag_solanago.PublicKey,
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *DisableSourceChainSelector {
+ return NewDisableSourceChainSelectorInstructionBuilder().
+ SetSourceChainSelector(sourceChainSelector).
+ SetChainStateAccount(chainState).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/DisableSourceChainSelector_test.go b/chains/solana/gobindings/ccip_router/DisableSourceChainSelector_test.go
new file mode 100644
index 000000000..8d1423363
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/DisableSourceChainSelector_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_DisableSourceChainSelector(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("DisableSourceChainSelector"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(DisableSourceChainSelector)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(DisableSourceChainSelector)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/Execute.go b/chains/solana/gobindings/ccip_router/Execute.go
new file mode 100644
index 000000000..25edb55c4
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/Execute.go
@@ -0,0 +1,304 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// OFF RAMP FLOW
+// Executes a message on the destination chain.
+//
+// The method name needs to be execute with Anchor encoding.
+//
+// This function is called by the OffChain when executing one Report to the Solana Router.
+// In this Flow only one message is sent, the Execution Report. This is different as EVM does,
+// this is because there is no try/catch mechanism to allow batch execution.
+// This message validates that the Merkle Tree Proof of the given message is correct and is stored in the Commit Report Account.
+// The message must be untouched to be executed.
+// This message emits the event ExecutionStateChanged with the new state of the message.
+// Finally, executes the CPI instruction to the receiver program in the ccip_receive message.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for the execute.
+// * `execution_report` - the execution report containing only one message and proofs
+// * `report_context_byte_words` - report_context after execution_report to match context for manually execute (proper decoding order)
+// * consists of:
+// * report_context_byte_words[0]: ConfigDigest
+// * report_context_byte_words[1]: 24 byte padding, 8 byte sequence number
+// * report_context_byte_words[2]: ExtraHash
+type Execute struct {
+ ExecutionReport *ExecutionReportSingleChain
+ ReportContextByteWords *[3][32]uint8
+
+ // [0] = [] config
+ //
+ // [1] = [] chainState
+ //
+ // [2] = [WRITE] commitReport
+ //
+ // [3] = [] externalExecutionConfig
+ //
+ // [4] = [WRITE, SIGNER] authority
+ //
+ // [5] = [] systemProgram
+ //
+ // [6] = [] sysvarInstructions
+ //
+ // [7] = [] tokenPoolsSigner
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewExecuteInstructionBuilder creates a new `Execute` instruction builder.
+func NewExecuteInstructionBuilder() *Execute {
+ nd := &Execute{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 8),
+ }
+ return nd
+}
+
+// SetExecutionReport sets the "executionReport" parameter.
+func (inst *Execute) SetExecutionReport(executionReport ExecutionReportSingleChain) *Execute {
+ inst.ExecutionReport = &executionReport
+ return inst
+}
+
+// SetReportContextByteWords sets the "reportContextByteWords" parameter.
+func (inst *Execute) SetReportContextByteWords(reportContextByteWords [3][32]uint8) *Execute {
+ inst.ReportContextByteWords = &reportContextByteWords
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *Execute) SetConfigAccount(config ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *Execute) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *Execute) SetChainStateAccount(chainState ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(chainState)
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *Execute) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetCommitReportAccount sets the "commitReport" account.
+func (inst *Execute) SetCommitReportAccount(commitReport ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(commitReport).WRITE()
+ return inst
+}
+
+// GetCommitReportAccount gets the "commitReport" account.
+func (inst *Execute) GetCommitReportAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetExternalExecutionConfigAccount sets the "externalExecutionConfig" account.
+func (inst *Execute) SetExternalExecutionConfigAccount(externalExecutionConfig ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(externalExecutionConfig)
+ return inst
+}
+
+// GetExternalExecutionConfigAccount gets the "externalExecutionConfig" account.
+func (inst *Execute) GetExternalExecutionConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *Execute) SetAuthorityAccount(authority ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *Execute) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *Execute) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *Execute) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetSysvarInstructionsAccount sets the "sysvarInstructions" account.
+func (inst *Execute) SetSysvarInstructionsAccount(sysvarInstructions ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(sysvarInstructions)
+ return inst
+}
+
+// GetSysvarInstructionsAccount gets the "sysvarInstructions" account.
+func (inst *Execute) GetSysvarInstructionsAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+// SetTokenPoolsSignerAccount sets the "tokenPoolsSigner" account.
+func (inst *Execute) SetTokenPoolsSignerAccount(tokenPoolsSigner ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[7] = ag_solanago.Meta(tokenPoolsSigner)
+ return inst
+}
+
+// GetTokenPoolsSignerAccount gets the "tokenPoolsSigner" account.
+func (inst *Execute) GetTokenPoolsSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[7]
+}
+
+func (inst Execute) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Execute,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Execute) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Execute) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ExecutionReport == nil {
+ return errors.New("ExecutionReport parameter is not set")
+ }
+ if inst.ReportContextByteWords == nil {
+ return errors.New("ReportContextByteWords parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.CommitReport is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.ExternalExecutionConfig is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.SysvarInstructions is not set")
+ }
+ if inst.AccountMetaSlice[7] == nil {
+ return errors.New("accounts.TokenPoolsSigner is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Execute) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Execute")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" ExecutionReport", *inst.ExecutionReport))
+ paramsBranch.Child(ag_format.Param("ReportContextByteWords", *inst.ReportContextByteWords))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=8]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" chainState", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" commitReport", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("externalExecutionConfig", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" sysvarInstructions", inst.AccountMetaSlice[6]))
+ accountsBranch.Child(ag_format.Meta(" tokenPoolsSigner", inst.AccountMetaSlice[7]))
+ })
+ })
+ })
+}
+
+func (obj Execute) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ExecutionReport` param:
+ err = encoder.Encode(obj.ExecutionReport)
+ if err != nil {
+ return err
+ }
+ // Serialize `ReportContextByteWords` param:
+ err = encoder.Encode(obj.ReportContextByteWords)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *Execute) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ExecutionReport`:
+ err = decoder.Decode(&obj.ExecutionReport)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ReportContextByteWords`:
+ err = decoder.Decode(&obj.ReportContextByteWords)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewExecuteInstruction declares a new Execute instruction with the provided parameters and accounts.
+func NewExecuteInstruction(
+ // Parameters:
+ executionReport ExecutionReportSingleChain,
+ reportContextByteWords [3][32]uint8,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ chainState ag_solanago.PublicKey,
+ commitReport ag_solanago.PublicKey,
+ externalExecutionConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey,
+ sysvarInstructions ag_solanago.PublicKey,
+ tokenPoolsSigner ag_solanago.PublicKey) *Execute {
+ return NewExecuteInstructionBuilder().
+ SetExecutionReport(executionReport).
+ SetReportContextByteWords(reportContextByteWords).
+ SetConfigAccount(config).
+ SetChainStateAccount(chainState).
+ SetCommitReportAccount(commitReport).
+ SetExternalExecutionConfigAccount(externalExecutionConfig).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram).
+ SetSysvarInstructionsAccount(sysvarInstructions).
+ SetTokenPoolsSignerAccount(tokenPoolsSigner)
+}
diff --git a/chains/solana/gobindings/ccip_router/Execute_test.go b/chains/solana/gobindings/ccip_router/Execute_test.go
new file mode 100644
index 000000000..5c0f4d7b7
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/Execute_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Execute(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Execute"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Execute)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Execute)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/GetFee.go b/chains/solana/gobindings/ccip_router/GetFee.go
new file mode 100644
index 000000000..0f8a00d6a
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/GetFee.go
@@ -0,0 +1,179 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Calculates the fee for sending a message to the destination chain.
+//
+// # Arguments
+//
+// * `_ctx` - The context containing the accounts required for the fee calculation.
+// * `dest_chain_selector` - The chain selector for the destination chain.
+// * `message` - The message to be sent.
+//
+// # Returns
+//
+// The fee amount in u64.
+type GetFee struct {
+ DestChainSelector *uint64
+ Message *Solana2AnyMessage
+
+ // [0] = [] chainState
+ //
+ // [1] = [] billingTokenConfig
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewGetFeeInstructionBuilder creates a new `GetFee` instruction builder.
+func NewGetFeeInstructionBuilder() *GetFee {
+ nd := &GetFee{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetDestChainSelector sets the "destChainSelector" parameter.
+func (inst *GetFee) SetDestChainSelector(destChainSelector uint64) *GetFee {
+ inst.DestChainSelector = &destChainSelector
+ return inst
+}
+
+// SetMessage sets the "message" parameter.
+func (inst *GetFee) SetMessage(message Solana2AnyMessage) *GetFee {
+ inst.Message = &message
+ return inst
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *GetFee) SetChainStateAccount(chainState ag_solanago.PublicKey) *GetFee {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(chainState)
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *GetFee) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetBillingTokenConfigAccount sets the "billingTokenConfig" account.
+func (inst *GetFee) SetBillingTokenConfigAccount(billingTokenConfig ag_solanago.PublicKey) *GetFee {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(billingTokenConfig)
+ return inst
+}
+
+// GetBillingTokenConfigAccount gets the "billingTokenConfig" account.
+func (inst *GetFee) GetBillingTokenConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst GetFee) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_GetFee,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst GetFee) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *GetFee) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.DestChainSelector == nil {
+ return errors.New("DestChainSelector parameter is not set")
+ }
+ if inst.Message == nil {
+ return errors.New("Message parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.BillingTokenConfig is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *GetFee) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("GetFee")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("DestChainSelector", *inst.DestChainSelector))
+ paramsBranch.Child(ag_format.Param(" Message", *inst.Message))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" chainState", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("billingTokenConfig", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj GetFee) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `DestChainSelector` param:
+ err = encoder.Encode(obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Message` param:
+ err = encoder.Encode(obj.Message)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *GetFee) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `DestChainSelector`:
+ err = decoder.Decode(&obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Message`:
+ err = decoder.Decode(&obj.Message)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewGetFeeInstruction declares a new GetFee instruction with the provided parameters and accounts.
+func NewGetFeeInstruction(
+ // Parameters:
+ destChainSelector uint64,
+ message Solana2AnyMessage,
+ // Accounts:
+ chainState ag_solanago.PublicKey,
+ billingTokenConfig ag_solanago.PublicKey) *GetFee {
+ return NewGetFeeInstructionBuilder().
+ SetDestChainSelector(destChainSelector).
+ SetMessage(message).
+ SetChainStateAccount(chainState).
+ SetBillingTokenConfigAccount(billingTokenConfig)
+}
diff --git a/chains/solana/gobindings/ccip_router/GetFee_test.go b/chains/solana/gobindings/ccip_router/GetFee_test.go
new file mode 100644
index 000000000..5798cb084
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/GetFee_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_GetFee(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("GetFee"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(GetFee)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(GetFee)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/Initialize.go b/chains/solana/gobindings/ccip_router/Initialize.go
new file mode 100644
index 000000000..2448207b6
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/Initialize.go
@@ -0,0 +1,320 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Initializes the CCIP Router.
+//
+// The initialization of the Router is responsibility of Admin, nothing more than calling this method should be done first.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for initialization.
+// * `solana_chain_selector` - The chain selector for Solana.
+// * `default_gas_limit` - The default gas limit for other destination chains.
+// * `default_allow_out_of_order_execution` - Whether out-of-order execution is allowed by default for other destination chains.
+// * `enable_execution_after` - The minimum amount of time required between a message has been committed and can be manually executed.
+type Initialize struct {
+ SolanaChainSelector *uint64
+ DefaultGasLimit *ag_binary.Uint128
+ DefaultAllowOutOfOrderExecution *bool
+ EnableExecutionAfter *int64
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [WRITE, SIGNER] authority
+ //
+ // [2] = [] systemProgram
+ //
+ // [3] = [] program
+ //
+ // [4] = [] programData
+ //
+ // [5] = [WRITE] externalExecutionConfig
+ //
+ // [6] = [WRITE] tokenPoolsSigner
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitializeInstructionBuilder creates a new `Initialize` instruction builder.
+func NewInitializeInstructionBuilder() *Initialize {
+ nd := &Initialize{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 7),
+ }
+ return nd
+}
+
+// SetSolanaChainSelector sets the "solanaChainSelector" parameter.
+func (inst *Initialize) SetSolanaChainSelector(solanaChainSelector uint64) *Initialize {
+ inst.SolanaChainSelector = &solanaChainSelector
+ return inst
+}
+
+// SetDefaultGasLimit sets the "defaultGasLimit" parameter.
+func (inst *Initialize) SetDefaultGasLimit(defaultGasLimit ag_binary.Uint128) *Initialize {
+ inst.DefaultGasLimit = &defaultGasLimit
+ return inst
+}
+
+// SetDefaultAllowOutOfOrderExecution sets the "defaultAllowOutOfOrderExecution" parameter.
+func (inst *Initialize) SetDefaultAllowOutOfOrderExecution(defaultAllowOutOfOrderExecution bool) *Initialize {
+ inst.DefaultAllowOutOfOrderExecution = &defaultAllowOutOfOrderExecution
+ return inst
+}
+
+// SetEnableExecutionAfter sets the "enableExecutionAfter" parameter.
+func (inst *Initialize) SetEnableExecutionAfter(enableExecutionAfter int64) *Initialize {
+ inst.EnableExecutionAfter = &enableExecutionAfter
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *Initialize) SetConfigAccount(config ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *Initialize) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *Initialize) SetAuthorityAccount(authority ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *Initialize) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *Initialize) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *Initialize) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetProgramAccount sets the "program" account.
+func (inst *Initialize) SetProgramAccount(program ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(program)
+ return inst
+}
+
+// GetProgramAccount gets the "program" account.
+func (inst *Initialize) GetProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetProgramDataAccount sets the "programData" account.
+func (inst *Initialize) SetProgramDataAccount(programData ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(programData)
+ return inst
+}
+
+// GetProgramDataAccount gets the "programData" account.
+func (inst *Initialize) GetProgramDataAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetExternalExecutionConfigAccount sets the "externalExecutionConfig" account.
+func (inst *Initialize) SetExternalExecutionConfigAccount(externalExecutionConfig ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(externalExecutionConfig).WRITE()
+ return inst
+}
+
+// GetExternalExecutionConfigAccount gets the "externalExecutionConfig" account.
+func (inst *Initialize) GetExternalExecutionConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetTokenPoolsSignerAccount sets the "tokenPoolsSigner" account.
+func (inst *Initialize) SetTokenPoolsSignerAccount(tokenPoolsSigner ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(tokenPoolsSigner).WRITE()
+ return inst
+}
+
+// GetTokenPoolsSignerAccount gets the "tokenPoolsSigner" account.
+func (inst *Initialize) GetTokenPoolsSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+func (inst Initialize) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Initialize,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Initialize) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Initialize) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.SolanaChainSelector == nil {
+ return errors.New("SolanaChainSelector parameter is not set")
+ }
+ if inst.DefaultGasLimit == nil {
+ return errors.New("DefaultGasLimit parameter is not set")
+ }
+ if inst.DefaultAllowOutOfOrderExecution == nil {
+ return errors.New("DefaultAllowOutOfOrderExecution parameter is not set")
+ }
+ if inst.EnableExecutionAfter == nil {
+ return errors.New("EnableExecutionAfter parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Program is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.ProgramData is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.ExternalExecutionConfig is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.TokenPoolsSigner is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Initialize) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Initialize")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=4]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" SolanaChainSelector", *inst.SolanaChainSelector))
+ paramsBranch.Child(ag_format.Param(" DefaultGasLimit", *inst.DefaultGasLimit))
+ paramsBranch.Child(ag_format.Param("DefaultAllowOutOfOrderExecution", *inst.DefaultAllowOutOfOrderExecution))
+ paramsBranch.Child(ag_format.Param(" EnableExecutionAfter", *inst.EnableExecutionAfter))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=7]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" program", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" programData", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta("externalExecutionConfig", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" tokenPoolsSigner", inst.AccountMetaSlice[6]))
+ })
+ })
+ })
+}
+
+func (obj Initialize) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `SolanaChainSelector` param:
+ err = encoder.Encode(obj.SolanaChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `DefaultGasLimit` param:
+ err = encoder.Encode(obj.DefaultGasLimit)
+ if err != nil {
+ return err
+ }
+ // Serialize `DefaultAllowOutOfOrderExecution` param:
+ err = encoder.Encode(obj.DefaultAllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ // Serialize `EnableExecutionAfter` param:
+ err = encoder.Encode(obj.EnableExecutionAfter)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `SolanaChainSelector`:
+ err = decoder.Decode(&obj.SolanaChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DefaultGasLimit`:
+ err = decoder.Decode(&obj.DefaultGasLimit)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DefaultAllowOutOfOrderExecution`:
+ err = decoder.Decode(&obj.DefaultAllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ // Deserialize `EnableExecutionAfter`:
+ err = decoder.Decode(&obj.EnableExecutionAfter)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewInitializeInstruction declares a new Initialize instruction with the provided parameters and accounts.
+func NewInitializeInstruction(
+ // Parameters:
+ solanaChainSelector uint64,
+ defaultGasLimit ag_binary.Uint128,
+ defaultAllowOutOfOrderExecution bool,
+ enableExecutionAfter int64,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey,
+ program ag_solanago.PublicKey,
+ programData ag_solanago.PublicKey,
+ externalExecutionConfig ag_solanago.PublicKey,
+ tokenPoolsSigner ag_solanago.PublicKey) *Initialize {
+ return NewInitializeInstructionBuilder().
+ SetSolanaChainSelector(solanaChainSelector).
+ SetDefaultGasLimit(defaultGasLimit).
+ SetDefaultAllowOutOfOrderExecution(defaultAllowOutOfOrderExecution).
+ SetEnableExecutionAfter(enableExecutionAfter).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram).
+ SetProgramAccount(program).
+ SetProgramDataAccount(programData).
+ SetExternalExecutionConfigAccount(externalExecutionConfig).
+ SetTokenPoolsSignerAccount(tokenPoolsSigner)
+}
diff --git a/chains/solana/gobindings/ccip_router/Initialize_test.go b/chains/solana/gobindings/ccip_router/Initialize_test.go
new file mode 100644
index 000000000..51eed3384
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/Initialize_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Initialize(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Initialize"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Initialize)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Initialize)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/ManuallyExecute.go b/chains/solana/gobindings/ccip_router/ManuallyExecute.go
new file mode 100644
index 000000000..7195db897
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/ManuallyExecute.go
@@ -0,0 +1,269 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Manually executes a report to the router.
+//
+// When a message is not being executed, then the user can trigger the execution manually.
+// No verification over the transmitter, but the message needs to be in some commit report.
+// It validates that the required time has passed since the commit and then executes the report.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for the execution.
+// * `execution_report` - The execution report containing the message and proofs.
+type ManuallyExecute struct {
+ ExecutionReport *ExecutionReportSingleChain
+
+ // [0] = [] config
+ //
+ // [1] = [] chainState
+ //
+ // [2] = [WRITE] commitReport
+ //
+ // [3] = [] externalExecutionConfig
+ //
+ // [4] = [WRITE, SIGNER] authority
+ //
+ // [5] = [] systemProgram
+ //
+ // [6] = [] sysvarInstructions
+ //
+ // [7] = [] tokenPoolsSigner
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewManuallyExecuteInstructionBuilder creates a new `ManuallyExecute` instruction builder.
+func NewManuallyExecuteInstructionBuilder() *ManuallyExecute {
+ nd := &ManuallyExecute{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 8),
+ }
+ return nd
+}
+
+// SetExecutionReport sets the "executionReport" parameter.
+func (inst *ManuallyExecute) SetExecutionReport(executionReport ExecutionReportSingleChain) *ManuallyExecute {
+ inst.ExecutionReport = &executionReport
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *ManuallyExecute) SetConfigAccount(config ag_solanago.PublicKey) *ManuallyExecute {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *ManuallyExecute) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *ManuallyExecute) SetChainStateAccount(chainState ag_solanago.PublicKey) *ManuallyExecute {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(chainState)
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *ManuallyExecute) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetCommitReportAccount sets the "commitReport" account.
+func (inst *ManuallyExecute) SetCommitReportAccount(commitReport ag_solanago.PublicKey) *ManuallyExecute {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(commitReport).WRITE()
+ return inst
+}
+
+// GetCommitReportAccount gets the "commitReport" account.
+func (inst *ManuallyExecute) GetCommitReportAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetExternalExecutionConfigAccount sets the "externalExecutionConfig" account.
+func (inst *ManuallyExecute) SetExternalExecutionConfigAccount(externalExecutionConfig ag_solanago.PublicKey) *ManuallyExecute {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(externalExecutionConfig)
+ return inst
+}
+
+// GetExternalExecutionConfigAccount gets the "externalExecutionConfig" account.
+func (inst *ManuallyExecute) GetExternalExecutionConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *ManuallyExecute) SetAuthorityAccount(authority ag_solanago.PublicKey) *ManuallyExecute {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *ManuallyExecute) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *ManuallyExecute) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *ManuallyExecute {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *ManuallyExecute) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetSysvarInstructionsAccount sets the "sysvarInstructions" account.
+func (inst *ManuallyExecute) SetSysvarInstructionsAccount(sysvarInstructions ag_solanago.PublicKey) *ManuallyExecute {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(sysvarInstructions)
+ return inst
+}
+
+// GetSysvarInstructionsAccount gets the "sysvarInstructions" account.
+func (inst *ManuallyExecute) GetSysvarInstructionsAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+// SetTokenPoolsSignerAccount sets the "tokenPoolsSigner" account.
+func (inst *ManuallyExecute) SetTokenPoolsSignerAccount(tokenPoolsSigner ag_solanago.PublicKey) *ManuallyExecute {
+ inst.AccountMetaSlice[7] = ag_solanago.Meta(tokenPoolsSigner)
+ return inst
+}
+
+// GetTokenPoolsSignerAccount gets the "tokenPoolsSigner" account.
+func (inst *ManuallyExecute) GetTokenPoolsSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[7]
+}
+
+func (inst ManuallyExecute) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_ManuallyExecute,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst ManuallyExecute) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *ManuallyExecute) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ExecutionReport == nil {
+ return errors.New("ExecutionReport parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.CommitReport is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.ExternalExecutionConfig is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.SysvarInstructions is not set")
+ }
+ if inst.AccountMetaSlice[7] == nil {
+ return errors.New("accounts.TokenPoolsSigner is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *ManuallyExecute) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("ManuallyExecute")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("ExecutionReport", *inst.ExecutionReport))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=8]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" chainState", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" commitReport", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("externalExecutionConfig", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" sysvarInstructions", inst.AccountMetaSlice[6]))
+ accountsBranch.Child(ag_format.Meta(" tokenPoolsSigner", inst.AccountMetaSlice[7]))
+ })
+ })
+ })
+}
+
+func (obj ManuallyExecute) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ExecutionReport` param:
+ err = encoder.Encode(obj.ExecutionReport)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *ManuallyExecute) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ExecutionReport`:
+ err = decoder.Decode(&obj.ExecutionReport)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewManuallyExecuteInstruction declares a new ManuallyExecute instruction with the provided parameters and accounts.
+func NewManuallyExecuteInstruction(
+ // Parameters:
+ executionReport ExecutionReportSingleChain,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ chainState ag_solanago.PublicKey,
+ commitReport ag_solanago.PublicKey,
+ externalExecutionConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey,
+ sysvarInstructions ag_solanago.PublicKey,
+ tokenPoolsSigner ag_solanago.PublicKey) *ManuallyExecute {
+ return NewManuallyExecuteInstructionBuilder().
+ SetExecutionReport(executionReport).
+ SetConfigAccount(config).
+ SetChainStateAccount(chainState).
+ SetCommitReportAccount(commitReport).
+ SetExternalExecutionConfigAccount(externalExecutionConfig).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram).
+ SetSysvarInstructionsAccount(sysvarInstructions).
+ SetTokenPoolsSignerAccount(tokenPoolsSigner)
+}
diff --git a/chains/solana/gobindings/ccip_router/ManuallyExecute_test.go b/chains/solana/gobindings/ccip_router/ManuallyExecute_test.go
new file mode 100644
index 000000000..38f8fe33c
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/ManuallyExecute_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_ManuallyExecute(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("ManuallyExecute"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(ManuallyExecute)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(ManuallyExecute)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaGetCcipAdmin.go b/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaGetCcipAdmin.go
new file mode 100644
index 000000000..b001f421d
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaGetCcipAdmin.go
@@ -0,0 +1,213 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Registers the Token Admin Registry via the CCIP Admin
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for registration.
+// * `mint` - The public key of the token mint.
+// * `token_admin_registry_admin` - The public key of the token admin registry admin.
+type RegisterTokenAdminRegistryViaGetCcipAdmin struct {
+ Mint *ag_solanago.PublicKey
+ TokenAdminRegistryAdmin *ag_solanago.PublicKey
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] tokenAdminRegistry
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewRegisterTokenAdminRegistryViaGetCcipAdminInstructionBuilder creates a new `RegisterTokenAdminRegistryViaGetCcipAdmin` instruction builder.
+func NewRegisterTokenAdminRegistryViaGetCcipAdminInstructionBuilder() *RegisterTokenAdminRegistryViaGetCcipAdmin {
+ nd := &RegisterTokenAdminRegistryViaGetCcipAdmin{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetMint sets the "mint" parameter.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) SetMint(mint ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaGetCcipAdmin {
+ inst.Mint = &mint
+ return inst
+}
+
+// SetTokenAdminRegistryAdmin sets the "tokenAdminRegistryAdmin" parameter.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) SetTokenAdminRegistryAdmin(tokenAdminRegistryAdmin ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaGetCcipAdmin {
+ inst.TokenAdminRegistryAdmin = &tokenAdminRegistryAdmin
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) SetConfigAccount(config ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaGetCcipAdmin {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetTokenAdminRegistryAccount sets the "tokenAdminRegistry" account.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) SetTokenAdminRegistryAccount(tokenAdminRegistry ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaGetCcipAdmin {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(tokenAdminRegistry).WRITE()
+ return inst
+}
+
+// GetTokenAdminRegistryAccount gets the "tokenAdminRegistry" account.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) GetTokenAdminRegistryAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) SetAuthorityAccount(authority ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaGetCcipAdmin {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaGetCcipAdmin {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst RegisterTokenAdminRegistryViaGetCcipAdmin) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_RegisterTokenAdminRegistryViaGetCcipAdmin,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst RegisterTokenAdminRegistryViaGetCcipAdmin) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Mint == nil {
+ return errors.New("Mint parameter is not set")
+ }
+ if inst.TokenAdminRegistryAdmin == nil {
+ return errors.New("TokenAdminRegistryAdmin parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.TokenAdminRegistry is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *RegisterTokenAdminRegistryViaGetCcipAdmin) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("RegisterTokenAdminRegistryViaGetCcipAdmin")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" Mint", *inst.Mint))
+ paramsBranch.Child(ag_format.Param("TokenAdminRegistryAdmin", *inst.TokenAdminRegistryAdmin))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("tokenAdminRegistry", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj RegisterTokenAdminRegistryViaGetCcipAdmin) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenAdminRegistryAdmin` param:
+ err = encoder.Encode(obj.TokenAdminRegistryAdmin)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *RegisterTokenAdminRegistryViaGetCcipAdmin) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenAdminRegistryAdmin`:
+ err = decoder.Decode(&obj.TokenAdminRegistryAdmin)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewRegisterTokenAdminRegistryViaGetCcipAdminInstruction declares a new RegisterTokenAdminRegistryViaGetCcipAdmin instruction with the provided parameters and accounts.
+func NewRegisterTokenAdminRegistryViaGetCcipAdminInstruction(
+ // Parameters:
+ mint ag_solanago.PublicKey,
+ tokenAdminRegistryAdmin ag_solanago.PublicKey,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ tokenAdminRegistry ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaGetCcipAdmin {
+ return NewRegisterTokenAdminRegistryViaGetCcipAdminInstructionBuilder().
+ SetMint(mint).
+ SetTokenAdminRegistryAdmin(tokenAdminRegistryAdmin).
+ SetConfigAccount(config).
+ SetTokenAdminRegistryAccount(tokenAdminRegistry).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaGetCcipAdmin_test.go b/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaGetCcipAdmin_test.go
new file mode 100644
index 000000000..29a2c212a
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaGetCcipAdmin_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_RegisterTokenAdminRegistryViaGetCcipAdmin(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("RegisterTokenAdminRegistryViaGetCcipAdmin"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(RegisterTokenAdminRegistryViaGetCcipAdmin)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(RegisterTokenAdminRegistryViaGetCcipAdmin)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaOwner.go b/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaOwner.go
new file mode 100644
index 000000000..8538af9f7
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaOwner.go
@@ -0,0 +1,161 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Registers the Token Admin Registry via the token owner.
+//
+// The Authority of the Mint Token can claim the registry of the token.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for registration.
+type RegisterTokenAdminRegistryViaOwner struct {
+
+ // [0] = [WRITE] tokenAdminRegistry
+ //
+ // [1] = [WRITE] mint
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewRegisterTokenAdminRegistryViaOwnerInstructionBuilder creates a new `RegisterTokenAdminRegistryViaOwner` instruction builder.
+func NewRegisterTokenAdminRegistryViaOwnerInstructionBuilder() *RegisterTokenAdminRegistryViaOwner {
+ nd := &RegisterTokenAdminRegistryViaOwner{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetTokenAdminRegistryAccount sets the "tokenAdminRegistry" account.
+func (inst *RegisterTokenAdminRegistryViaOwner) SetTokenAdminRegistryAccount(tokenAdminRegistry ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaOwner {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(tokenAdminRegistry).WRITE()
+ return inst
+}
+
+// GetTokenAdminRegistryAccount gets the "tokenAdminRegistry" account.
+func (inst *RegisterTokenAdminRegistryViaOwner) GetTokenAdminRegistryAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetMintAccount sets the "mint" account.
+func (inst *RegisterTokenAdminRegistryViaOwner) SetMintAccount(mint ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaOwner {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(mint).WRITE()
+ return inst
+}
+
+// GetMintAccount gets the "mint" account.
+func (inst *RegisterTokenAdminRegistryViaOwner) GetMintAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *RegisterTokenAdminRegistryViaOwner) SetAuthorityAccount(authority ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaOwner {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *RegisterTokenAdminRegistryViaOwner) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *RegisterTokenAdminRegistryViaOwner) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaOwner {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *RegisterTokenAdminRegistryViaOwner) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst RegisterTokenAdminRegistryViaOwner) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_RegisterTokenAdminRegistryViaOwner,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst RegisterTokenAdminRegistryViaOwner) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *RegisterTokenAdminRegistryViaOwner) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.TokenAdminRegistry is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Mint is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *RegisterTokenAdminRegistryViaOwner) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("RegisterTokenAdminRegistryViaOwner")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("tokenAdminRegistry", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj RegisterTokenAdminRegistryViaOwner) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *RegisterTokenAdminRegistryViaOwner) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewRegisterTokenAdminRegistryViaOwnerInstruction declares a new RegisterTokenAdminRegistryViaOwner instruction with the provided parameters and accounts.
+func NewRegisterTokenAdminRegistryViaOwnerInstruction(
+ // Accounts:
+ tokenAdminRegistry ag_solanago.PublicKey,
+ mint ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *RegisterTokenAdminRegistryViaOwner {
+ return NewRegisterTokenAdminRegistryViaOwnerInstructionBuilder().
+ SetTokenAdminRegistryAccount(tokenAdminRegistry).
+ SetMintAccount(mint).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaOwner_test.go b/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaOwner_test.go
new file mode 100644
index 000000000..ea293f88b
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaOwner_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_RegisterTokenAdminRegistryViaOwner(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("RegisterTokenAdminRegistryViaOwner"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(RegisterTokenAdminRegistryViaOwner)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(RegisterTokenAdminRegistryViaOwner)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/RemoveBillingTokenConfig.go b/chains/solana/gobindings/ccip_router/RemoveBillingTokenConfig.go
new file mode 100644
index 000000000..2ffc2b9df
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/RemoveBillingTokenConfig.go
@@ -0,0 +1,242 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Removes the billing token configuration.
+// Only CCIP Admin can remove a billing token configuration.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for removing the billing token configuration.
+type RemoveBillingTokenConfig struct {
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] billingTokenConfig
+ //
+ // [2] = [] tokenProgram
+ // ··········· type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+ // ··········· with a constraint enforcing that it is one of the two allowed programs.
+ //
+ // [3] = [] feeTokenMint
+ //
+ // [4] = [WRITE] feeTokenReceiver
+ //
+ // [5] = [WRITE] feeBillingSigner
+ //
+ // [6] = [WRITE, SIGNER] authority
+ //
+ // [7] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewRemoveBillingTokenConfigInstructionBuilder creates a new `RemoveBillingTokenConfig` instruction builder.
+func NewRemoveBillingTokenConfigInstructionBuilder() *RemoveBillingTokenConfig {
+ nd := &RemoveBillingTokenConfig{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 8),
+ }
+ return nd
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *RemoveBillingTokenConfig) SetConfigAccount(config ag_solanago.PublicKey) *RemoveBillingTokenConfig {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *RemoveBillingTokenConfig) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetBillingTokenConfigAccount sets the "billingTokenConfig" account.
+func (inst *RemoveBillingTokenConfig) SetBillingTokenConfigAccount(billingTokenConfig ag_solanago.PublicKey) *RemoveBillingTokenConfig {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(billingTokenConfig).WRITE()
+ return inst
+}
+
+// GetBillingTokenConfigAccount gets the "billingTokenConfig" account.
+func (inst *RemoveBillingTokenConfig) GetBillingTokenConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetTokenProgramAccount sets the "tokenProgram" account.
+// type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+// with a constraint enforcing that it is one of the two allowed programs.
+func (inst *RemoveBillingTokenConfig) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *RemoveBillingTokenConfig {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(tokenProgram)
+ return inst
+}
+
+// GetTokenProgramAccount gets the "tokenProgram" account.
+// type of a specific program (which would enforce its ID). Thus, it's an UncheckedAccount
+// with a constraint enforcing that it is one of the two allowed programs.
+func (inst *RemoveBillingTokenConfig) GetTokenProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetFeeTokenMintAccount sets the "feeTokenMint" account.
+func (inst *RemoveBillingTokenConfig) SetFeeTokenMintAccount(feeTokenMint ag_solanago.PublicKey) *RemoveBillingTokenConfig {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(feeTokenMint)
+ return inst
+}
+
+// GetFeeTokenMintAccount gets the "feeTokenMint" account.
+func (inst *RemoveBillingTokenConfig) GetFeeTokenMintAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetFeeTokenReceiverAccount sets the "feeTokenReceiver" account.
+func (inst *RemoveBillingTokenConfig) SetFeeTokenReceiverAccount(feeTokenReceiver ag_solanago.PublicKey) *RemoveBillingTokenConfig {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(feeTokenReceiver).WRITE()
+ return inst
+}
+
+// GetFeeTokenReceiverAccount gets the "feeTokenReceiver" account.
+func (inst *RemoveBillingTokenConfig) GetFeeTokenReceiverAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetFeeBillingSignerAccount sets the "feeBillingSigner" account.
+func (inst *RemoveBillingTokenConfig) SetFeeBillingSignerAccount(feeBillingSigner ag_solanago.PublicKey) *RemoveBillingTokenConfig {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(feeBillingSigner).WRITE()
+ return inst
+}
+
+// GetFeeBillingSignerAccount gets the "feeBillingSigner" account.
+func (inst *RemoveBillingTokenConfig) GetFeeBillingSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *RemoveBillingTokenConfig) SetAuthorityAccount(authority ag_solanago.PublicKey) *RemoveBillingTokenConfig {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *RemoveBillingTokenConfig) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *RemoveBillingTokenConfig) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *RemoveBillingTokenConfig {
+ inst.AccountMetaSlice[7] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *RemoveBillingTokenConfig) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[7]
+}
+
+func (inst RemoveBillingTokenConfig) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_RemoveBillingTokenConfig,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst RemoveBillingTokenConfig) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *RemoveBillingTokenConfig) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.BillingTokenConfig is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.TokenProgram is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.FeeTokenMint is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.FeeTokenReceiver is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.FeeBillingSigner is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[7] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *RemoveBillingTokenConfig) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("RemoveBillingTokenConfig")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=8]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("billingTokenConfig", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" tokenProgram", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" feeTokenMint", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" feeTokenReceiver", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" feeBillingSigner", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[6]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[7]))
+ })
+ })
+ })
+}
+
+func (obj RemoveBillingTokenConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *RemoveBillingTokenConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewRemoveBillingTokenConfigInstruction declares a new RemoveBillingTokenConfig instruction with the provided parameters and accounts.
+func NewRemoveBillingTokenConfigInstruction(
+ // Accounts:
+ config ag_solanago.PublicKey,
+ billingTokenConfig ag_solanago.PublicKey,
+ tokenProgram ag_solanago.PublicKey,
+ feeTokenMint ag_solanago.PublicKey,
+ feeTokenReceiver ag_solanago.PublicKey,
+ feeBillingSigner ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *RemoveBillingTokenConfig {
+ return NewRemoveBillingTokenConfigInstructionBuilder().
+ SetConfigAccount(config).
+ SetBillingTokenConfigAccount(billingTokenConfig).
+ SetTokenProgramAccount(tokenProgram).
+ SetFeeTokenMintAccount(feeTokenMint).
+ SetFeeTokenReceiverAccount(feeTokenReceiver).
+ SetFeeBillingSignerAccount(feeBillingSigner).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/RemoveBillingTokenConfig_test.go b/chains/solana/gobindings/ccip_router/RemoveBillingTokenConfig_test.go
new file mode 100644
index 000000000..a2b4439c2
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/RemoveBillingTokenConfig_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_RemoveBillingTokenConfig(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("RemoveBillingTokenConfig"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(RemoveBillingTokenConfig)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(RemoveBillingTokenConfig)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/SetOcrConfig.go b/chains/solana/gobindings/ccip_router/SetOcrConfig.go
new file mode 100644
index 000000000..0f7b038be
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/SetOcrConfig.go
@@ -0,0 +1,224 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Sets the OCR configuration.
+// Only CCIP Admin can set the OCR configuration.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for setting the OCR configuration.
+// * `plugin_type` - The type of OCR plugin [0: Commit, 1: Execution].
+// * `config_info` - The OCR configuration information.
+// * `signers` - The list of signers.
+// * `transmitters` - The list of transmitters.
+type SetOcrConfig struct {
+ PluginType *uint8
+ ConfigInfo *Ocr3ConfigInfo
+ Signers *[][20]uint8
+ Transmitters *[]ag_solanago.PublicKey
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewSetOcrConfigInstructionBuilder creates a new `SetOcrConfig` instruction builder.
+func NewSetOcrConfigInstructionBuilder() *SetOcrConfig {
+ nd := &SetOcrConfig{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetPluginType sets the "pluginType" parameter.
+func (inst *SetOcrConfig) SetPluginType(pluginType uint8) *SetOcrConfig {
+ inst.PluginType = &pluginType
+ return inst
+}
+
+// SetConfigInfo sets the "configInfo" parameter.
+func (inst *SetOcrConfig) SetConfigInfo(configInfo Ocr3ConfigInfo) *SetOcrConfig {
+ inst.ConfigInfo = &configInfo
+ return inst
+}
+
+// SetSigners sets the "signers" parameter.
+func (inst *SetOcrConfig) SetSigners(signers [][20]uint8) *SetOcrConfig {
+ inst.Signers = &signers
+ return inst
+}
+
+// SetTransmitters sets the "transmitters" parameter.
+func (inst *SetOcrConfig) SetTransmitters(transmitters []ag_solanago.PublicKey) *SetOcrConfig {
+ inst.Transmitters = &transmitters
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *SetOcrConfig) SetConfigAccount(config ag_solanago.PublicKey) *SetOcrConfig {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *SetOcrConfig) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *SetOcrConfig) SetAuthorityAccount(authority ag_solanago.PublicKey) *SetOcrConfig {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *SetOcrConfig) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst SetOcrConfig) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_SetOcrConfig,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst SetOcrConfig) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *SetOcrConfig) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.PluginType == nil {
+ return errors.New("PluginType parameter is not set")
+ }
+ if inst.ConfigInfo == nil {
+ return errors.New("ConfigInfo parameter is not set")
+ }
+ if inst.Signers == nil {
+ return errors.New("Signers parameter is not set")
+ }
+ if inst.Transmitters == nil {
+ return errors.New("Transmitters parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *SetOcrConfig) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("SetOcrConfig")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=4]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" PluginType", *inst.PluginType))
+ paramsBranch.Child(ag_format.Param(" ConfigInfo", *inst.ConfigInfo))
+ paramsBranch.Child(ag_format.Param(" Signers", *inst.Signers))
+ paramsBranch.Child(ag_format.Param("Transmitters", *inst.Transmitters))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj SetOcrConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `PluginType` param:
+ err = encoder.Encode(obj.PluginType)
+ if err != nil {
+ return err
+ }
+ // Serialize `ConfigInfo` param:
+ err = encoder.Encode(obj.ConfigInfo)
+ if err != nil {
+ return err
+ }
+ // Serialize `Signers` param:
+ err = encoder.Encode(obj.Signers)
+ if err != nil {
+ return err
+ }
+ // Serialize `Transmitters` param:
+ err = encoder.Encode(obj.Transmitters)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *SetOcrConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `PluginType`:
+ err = decoder.Decode(&obj.PluginType)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ConfigInfo`:
+ err = decoder.Decode(&obj.ConfigInfo)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Signers`:
+ err = decoder.Decode(&obj.Signers)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Transmitters`:
+ err = decoder.Decode(&obj.Transmitters)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewSetOcrConfigInstruction declares a new SetOcrConfig instruction with the provided parameters and accounts.
+func NewSetOcrConfigInstruction(
+ // Parameters:
+ pluginType uint8,
+ configInfo Ocr3ConfigInfo,
+ signers [][20]uint8,
+ transmitters []ag_solanago.PublicKey,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *SetOcrConfig {
+ return NewSetOcrConfigInstructionBuilder().
+ SetPluginType(pluginType).
+ SetConfigInfo(configInfo).
+ SetSigners(signers).
+ SetTransmitters(transmitters).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/SetOcrConfig_test.go b/chains/solana/gobindings/ccip_router/SetOcrConfig_test.go
new file mode 100644
index 000000000..88528fc48
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/SetOcrConfig_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_SetOcrConfig(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("SetOcrConfig"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(SetOcrConfig)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(SetOcrConfig)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/SetPool.go b/chains/solana/gobindings/ccip_router/SetPool.go
new file mode 100644
index 000000000..dce1c1ecc
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/SetPool.go
@@ -0,0 +1,177 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Sets the pool lookup table for a given token mint.
+//
+// The administrator of the token admin registry can set the pool lookup table for a given token mint.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for setting the pool.
+// * `mint` - The public key of the token mint.
+// * `pool_lookup_table` - The public key of the pool lookup table, this address will be used for validations when interacting with the pool.
+type SetPool struct {
+ Mint *ag_solanago.PublicKey
+ PoolLookupTable *ag_solanago.PublicKey
+
+ // [0] = [WRITE] tokenAdminRegistry
+ //
+ // [1] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewSetPoolInstructionBuilder creates a new `SetPool` instruction builder.
+func NewSetPoolInstructionBuilder() *SetPool {
+ nd := &SetPool{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetMint sets the "mint" parameter.
+func (inst *SetPool) SetMint(mint ag_solanago.PublicKey) *SetPool {
+ inst.Mint = &mint
+ return inst
+}
+
+// SetPoolLookupTable sets the "poolLookupTable" parameter.
+func (inst *SetPool) SetPoolLookupTable(poolLookupTable ag_solanago.PublicKey) *SetPool {
+ inst.PoolLookupTable = &poolLookupTable
+ return inst
+}
+
+// SetTokenAdminRegistryAccount sets the "tokenAdminRegistry" account.
+func (inst *SetPool) SetTokenAdminRegistryAccount(tokenAdminRegistry ag_solanago.PublicKey) *SetPool {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(tokenAdminRegistry).WRITE()
+ return inst
+}
+
+// GetTokenAdminRegistryAccount gets the "tokenAdminRegistry" account.
+func (inst *SetPool) GetTokenAdminRegistryAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *SetPool) SetAuthorityAccount(authority ag_solanago.PublicKey) *SetPool {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *SetPool) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst SetPool) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_SetPool,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst SetPool) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *SetPool) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Mint == nil {
+ return errors.New("Mint parameter is not set")
+ }
+ if inst.PoolLookupTable == nil {
+ return errors.New("PoolLookupTable parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.TokenAdminRegistry is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *SetPool) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("SetPool")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" Mint", *inst.Mint))
+ paramsBranch.Child(ag_format.Param("PoolLookupTable", *inst.PoolLookupTable))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("tokenAdminRegistry", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj SetPool) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Serialize `PoolLookupTable` param:
+ err = encoder.Encode(obj.PoolLookupTable)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *SetPool) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PoolLookupTable`:
+ err = decoder.Decode(&obj.PoolLookupTable)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewSetPoolInstruction declares a new SetPool instruction with the provided parameters and accounts.
+func NewSetPoolInstruction(
+ // Parameters:
+ mint ag_solanago.PublicKey,
+ poolLookupTable ag_solanago.PublicKey,
+ // Accounts:
+ tokenAdminRegistry ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *SetPool {
+ return NewSetPoolInstructionBuilder().
+ SetMint(mint).
+ SetPoolLookupTable(poolLookupTable).
+ SetTokenAdminRegistryAccount(tokenAdminRegistry).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/SetPool_test.go b/chains/solana/gobindings/ccip_router/SetPool_test.go
new file mode 100644
index 000000000..249513a25
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/SetPool_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_SetPool(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("SetPool"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(SetPool)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(SetPool)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/SetTokenBilling.go b/chains/solana/gobindings/ccip_router/SetTokenBilling.go
new file mode 100644
index 000000000..8511bb3d8
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/SetTokenBilling.go
@@ -0,0 +1,239 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Sets the token billing configuration.
+//
+// Only CCIP Admin can set the token billing configuration.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for setting the token billing configuration.
+// * `_chain_selector` - The chain selector.
+// * `_mint` - The public key of the token mint.
+// * `cfg` - The token billing configuration.
+type SetTokenBilling struct {
+ ChainSelector *uint64
+ Mint *ag_solanago.PublicKey
+ Cfg *TokenBilling
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] perChainPerTokenConfig
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewSetTokenBillingInstructionBuilder creates a new `SetTokenBilling` instruction builder.
+func NewSetTokenBillingInstructionBuilder() *SetTokenBilling {
+ nd := &SetTokenBilling{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetChainSelector sets the "chainSelector" parameter.
+func (inst *SetTokenBilling) SetChainSelector(chainSelector uint64) *SetTokenBilling {
+ inst.ChainSelector = &chainSelector
+ return inst
+}
+
+// SetMint sets the "mint" parameter.
+func (inst *SetTokenBilling) SetMint(mint ag_solanago.PublicKey) *SetTokenBilling {
+ inst.Mint = &mint
+ return inst
+}
+
+// SetCfg sets the "cfg" parameter.
+func (inst *SetTokenBilling) SetCfg(cfg TokenBilling) *SetTokenBilling {
+ inst.Cfg = &cfg
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *SetTokenBilling) SetConfigAccount(config ag_solanago.PublicKey) *SetTokenBilling {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *SetTokenBilling) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetPerChainPerTokenConfigAccount sets the "perChainPerTokenConfig" account.
+func (inst *SetTokenBilling) SetPerChainPerTokenConfigAccount(perChainPerTokenConfig ag_solanago.PublicKey) *SetTokenBilling {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(perChainPerTokenConfig).WRITE()
+ return inst
+}
+
+// GetPerChainPerTokenConfigAccount gets the "perChainPerTokenConfig" account.
+func (inst *SetTokenBilling) GetPerChainPerTokenConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *SetTokenBilling) SetAuthorityAccount(authority ag_solanago.PublicKey) *SetTokenBilling {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *SetTokenBilling) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *SetTokenBilling) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *SetTokenBilling {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *SetTokenBilling) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst SetTokenBilling) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_SetTokenBilling,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst SetTokenBilling) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *SetTokenBilling) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ChainSelector == nil {
+ return errors.New("ChainSelector parameter is not set")
+ }
+ if inst.Mint == nil {
+ return errors.New("Mint parameter is not set")
+ }
+ if inst.Cfg == nil {
+ return errors.New("Cfg parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.PerChainPerTokenConfig is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *SetTokenBilling) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("SetTokenBilling")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=3]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("ChainSelector", *inst.ChainSelector))
+ paramsBranch.Child(ag_format.Param(" Mint", *inst.Mint))
+ paramsBranch.Child(ag_format.Param(" Cfg", *inst.Cfg))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("perChainPerTokenConfig", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj SetTokenBilling) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ChainSelector` param:
+ err = encoder.Encode(obj.ChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Serialize `Cfg` param:
+ err = encoder.Encode(obj.Cfg)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *SetTokenBilling) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ChainSelector`:
+ err = decoder.Decode(&obj.ChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Cfg`:
+ err = decoder.Decode(&obj.Cfg)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewSetTokenBillingInstruction declares a new SetTokenBilling instruction with the provided parameters and accounts.
+func NewSetTokenBillingInstruction(
+ // Parameters:
+ chainSelector uint64,
+ mint ag_solanago.PublicKey,
+ cfg TokenBilling,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ perChainPerTokenConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *SetTokenBilling {
+ return NewSetTokenBillingInstructionBuilder().
+ SetChainSelector(chainSelector).
+ SetMint(mint).
+ SetCfg(cfg).
+ SetConfigAccount(config).
+ SetPerChainPerTokenConfigAccount(perChainPerTokenConfig).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/SetTokenBilling_test.go b/chains/solana/gobindings/ccip_router/SetTokenBilling_test.go
new file mode 100644
index 000000000..aff9d60f3
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/SetTokenBilling_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_SetTokenBilling(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("SetTokenBilling"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(SetTokenBilling)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(SetTokenBilling)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/TransferAdminRoleTokenAdminRegistry.go b/chains/solana/gobindings/ccip_router/TransferAdminRoleTokenAdminRegistry.go
new file mode 100644
index 000000000..b1c57835d
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/TransferAdminRoleTokenAdminRegistry.go
@@ -0,0 +1,177 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Transfers the admin role of the token admin registry to a new admin.
+//
+// Only the Admin can transfer the Admin Role of the Token Admin Registry, this setups the Pending Admin and then it's their responsibility to accept the role.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for the transfer.
+// * `mint` - The public key of the token mint.
+// * `new_admin` - The public key of the new admin.
+type TransferAdminRoleTokenAdminRegistry struct {
+ Mint *ag_solanago.PublicKey
+ NewAdmin *ag_solanago.PublicKey
+
+ // [0] = [WRITE] tokenAdminRegistry
+ //
+ // [1] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewTransferAdminRoleTokenAdminRegistryInstructionBuilder creates a new `TransferAdminRoleTokenAdminRegistry` instruction builder.
+func NewTransferAdminRoleTokenAdminRegistryInstructionBuilder() *TransferAdminRoleTokenAdminRegistry {
+ nd := &TransferAdminRoleTokenAdminRegistry{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetMint sets the "mint" parameter.
+func (inst *TransferAdminRoleTokenAdminRegistry) SetMint(mint ag_solanago.PublicKey) *TransferAdminRoleTokenAdminRegistry {
+ inst.Mint = &mint
+ return inst
+}
+
+// SetNewAdmin sets the "newAdmin" parameter.
+func (inst *TransferAdminRoleTokenAdminRegistry) SetNewAdmin(newAdmin ag_solanago.PublicKey) *TransferAdminRoleTokenAdminRegistry {
+ inst.NewAdmin = &newAdmin
+ return inst
+}
+
+// SetTokenAdminRegistryAccount sets the "tokenAdminRegistry" account.
+func (inst *TransferAdminRoleTokenAdminRegistry) SetTokenAdminRegistryAccount(tokenAdminRegistry ag_solanago.PublicKey) *TransferAdminRoleTokenAdminRegistry {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(tokenAdminRegistry).WRITE()
+ return inst
+}
+
+// GetTokenAdminRegistryAccount gets the "tokenAdminRegistry" account.
+func (inst *TransferAdminRoleTokenAdminRegistry) GetTokenAdminRegistryAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *TransferAdminRoleTokenAdminRegistry) SetAuthorityAccount(authority ag_solanago.PublicKey) *TransferAdminRoleTokenAdminRegistry {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *TransferAdminRoleTokenAdminRegistry) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst TransferAdminRoleTokenAdminRegistry) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_TransferAdminRoleTokenAdminRegistry,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst TransferAdminRoleTokenAdminRegistry) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *TransferAdminRoleTokenAdminRegistry) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Mint == nil {
+ return errors.New("Mint parameter is not set")
+ }
+ if inst.NewAdmin == nil {
+ return errors.New("NewAdmin parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.TokenAdminRegistry is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *TransferAdminRoleTokenAdminRegistry) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("TransferAdminRoleTokenAdminRegistry")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" Mint", *inst.Mint))
+ paramsBranch.Child(ag_format.Param("NewAdmin", *inst.NewAdmin))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("tokenAdminRegistry", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj TransferAdminRoleTokenAdminRegistry) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Serialize `NewAdmin` param:
+ err = encoder.Encode(obj.NewAdmin)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *TransferAdminRoleTokenAdminRegistry) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Deserialize `NewAdmin`:
+ err = decoder.Decode(&obj.NewAdmin)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewTransferAdminRoleTokenAdminRegistryInstruction declares a new TransferAdminRoleTokenAdminRegistry instruction with the provided parameters and accounts.
+func NewTransferAdminRoleTokenAdminRegistryInstruction(
+ // Parameters:
+ mint ag_solanago.PublicKey,
+ newAdmin ag_solanago.PublicKey,
+ // Accounts:
+ tokenAdminRegistry ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *TransferAdminRoleTokenAdminRegistry {
+ return NewTransferAdminRoleTokenAdminRegistryInstructionBuilder().
+ SetMint(mint).
+ SetNewAdmin(newAdmin).
+ SetTokenAdminRegistryAccount(tokenAdminRegistry).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/TransferAdminRoleTokenAdminRegistry_test.go b/chains/solana/gobindings/ccip_router/TransferAdminRoleTokenAdminRegistry_test.go
new file mode 100644
index 000000000..e52c1a998
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/TransferAdminRoleTokenAdminRegistry_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_TransferAdminRoleTokenAdminRegistry(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("TransferAdminRoleTokenAdminRegistry"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(TransferAdminRoleTokenAdminRegistry)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(TransferAdminRoleTokenAdminRegistry)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/TransferOwnership.go b/chains/solana/gobindings/ccip_router/TransferOwnership.go
new file mode 100644
index 000000000..d16158b6f
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/TransferOwnership.go
@@ -0,0 +1,153 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Transfers the ownership of the router to a new proposed owner.
+//
+// # Shared func signature with other programs
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for the transfer.
+// * `proposed_owner` - The public key of the new proposed owner.
+type TransferOwnership struct {
+ ProposedOwner *ag_solanago.PublicKey
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewTransferOwnershipInstructionBuilder creates a new `TransferOwnership` instruction builder.
+func NewTransferOwnershipInstructionBuilder() *TransferOwnership {
+ nd := &TransferOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetProposedOwner sets the "proposedOwner" parameter.
+func (inst *TransferOwnership) SetProposedOwner(proposedOwner ag_solanago.PublicKey) *TransferOwnership {
+ inst.ProposedOwner = &proposedOwner
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *TransferOwnership) SetConfigAccount(config ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *TransferOwnership) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *TransferOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *TransferOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst TransferOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_TransferOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst TransferOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *TransferOwnership) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ProposedOwner == nil {
+ return errors.New("ProposedOwner parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *TransferOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("TransferOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("ProposedOwner", *inst.ProposedOwner))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj TransferOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *TransferOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewTransferOwnershipInstruction declares a new TransferOwnership instruction with the provided parameters and accounts.
+func NewTransferOwnershipInstruction(
+ // Parameters:
+ proposedOwner ag_solanago.PublicKey,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *TransferOwnership {
+ return NewTransferOwnershipInstructionBuilder().
+ SetProposedOwner(proposedOwner).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/TransferOwnership_test.go b/chains/solana/gobindings/ccip_router/TransferOwnership_test.go
new file mode 100644
index 000000000..38ea4ea34
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/TransferOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_TransferOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("TransferOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(TransferOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(TransferOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateBillingTokenConfig.go b/chains/solana/gobindings/ccip_router/UpdateBillingTokenConfig.go
new file mode 100644
index 000000000..2df1db5af
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateBillingTokenConfig.go
@@ -0,0 +1,171 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Updates the billing token configuration.
+// Only CCIP Admin can update a billing token configuration.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for updating the billing token configuration.
+// * `config` - The new billing token configuration.
+type UpdateBillingTokenConfig struct {
+ Config *BillingTokenConfig
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] billingTokenConfig
+ //
+ // [2] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewUpdateBillingTokenConfigInstructionBuilder creates a new `UpdateBillingTokenConfig` instruction builder.
+func NewUpdateBillingTokenConfigInstructionBuilder() *UpdateBillingTokenConfig {
+ nd := &UpdateBillingTokenConfig{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetConfig sets the "config" parameter.
+func (inst *UpdateBillingTokenConfig) SetConfig(config BillingTokenConfig) *UpdateBillingTokenConfig {
+ inst.Config = &config
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *UpdateBillingTokenConfig) SetConfigAccount(config ag_solanago.PublicKey) *UpdateBillingTokenConfig {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *UpdateBillingTokenConfig) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetBillingTokenConfigAccount sets the "billingTokenConfig" account.
+func (inst *UpdateBillingTokenConfig) SetBillingTokenConfigAccount(billingTokenConfig ag_solanago.PublicKey) *UpdateBillingTokenConfig {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(billingTokenConfig).WRITE()
+ return inst
+}
+
+// GetBillingTokenConfigAccount gets the "billingTokenConfig" account.
+func (inst *UpdateBillingTokenConfig) GetBillingTokenConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *UpdateBillingTokenConfig) SetAuthorityAccount(authority ag_solanago.PublicKey) *UpdateBillingTokenConfig {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *UpdateBillingTokenConfig) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst UpdateBillingTokenConfig) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_UpdateBillingTokenConfig,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst UpdateBillingTokenConfig) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *UpdateBillingTokenConfig) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Config == nil {
+ return errors.New("Config parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.BillingTokenConfig is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *UpdateBillingTokenConfig) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("UpdateBillingTokenConfig")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Config", *inst.Config))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("billingTokenConfig", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj UpdateBillingTokenConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Config` param:
+ err = encoder.Encode(obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *UpdateBillingTokenConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Config`:
+ err = decoder.Decode(&obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewUpdateBillingTokenConfigInstruction declares a new UpdateBillingTokenConfig instruction with the provided parameters and accounts.
+func NewUpdateBillingTokenConfigInstruction(
+ // Parameters:
+ config BillingTokenConfig,
+ // Accounts:
+ configAccount ag_solanago.PublicKey,
+ billingTokenConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *UpdateBillingTokenConfig {
+ return NewUpdateBillingTokenConfigInstructionBuilder().
+ SetConfig(config).
+ SetConfigAccount(configAccount).
+ SetBillingTokenConfigAccount(billingTokenConfig).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateBillingTokenConfig_test.go b/chains/solana/gobindings/ccip_router/UpdateBillingTokenConfig_test.go
new file mode 100644
index 000000000..28d80a7a4
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateBillingTokenConfig_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_UpdateBillingTokenConfig(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("UpdateBillingTokenConfig"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(UpdateBillingTokenConfig)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(UpdateBillingTokenConfig)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateDefaultAllowOutOfOrderExecution.go b/chains/solana/gobindings/ccip_router/UpdateDefaultAllowOutOfOrderExecution.go
new file mode 100644
index 000000000..bfc2d3844
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateDefaultAllowOutOfOrderExecution.go
@@ -0,0 +1,171 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Updates the default setting for allowing out-of-order execution for other destination chains.
+// The Admin is the only one able to update this config.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for updating the configuration.
+// * `new_allow_out_of_order_execution` - The new setting for allowing out-of-order execution.
+type UpdateDefaultAllowOutOfOrderExecution struct {
+ NewAllowOutOfOrderExecution *bool
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ //
+ // [2] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewUpdateDefaultAllowOutOfOrderExecutionInstructionBuilder creates a new `UpdateDefaultAllowOutOfOrderExecution` instruction builder.
+func NewUpdateDefaultAllowOutOfOrderExecutionInstructionBuilder() *UpdateDefaultAllowOutOfOrderExecution {
+ nd := &UpdateDefaultAllowOutOfOrderExecution{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetNewAllowOutOfOrderExecution sets the "newAllowOutOfOrderExecution" parameter.
+func (inst *UpdateDefaultAllowOutOfOrderExecution) SetNewAllowOutOfOrderExecution(newAllowOutOfOrderExecution bool) *UpdateDefaultAllowOutOfOrderExecution {
+ inst.NewAllowOutOfOrderExecution = &newAllowOutOfOrderExecution
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *UpdateDefaultAllowOutOfOrderExecution) SetConfigAccount(config ag_solanago.PublicKey) *UpdateDefaultAllowOutOfOrderExecution {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *UpdateDefaultAllowOutOfOrderExecution) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *UpdateDefaultAllowOutOfOrderExecution) SetAuthorityAccount(authority ag_solanago.PublicKey) *UpdateDefaultAllowOutOfOrderExecution {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *UpdateDefaultAllowOutOfOrderExecution) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *UpdateDefaultAllowOutOfOrderExecution) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *UpdateDefaultAllowOutOfOrderExecution {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *UpdateDefaultAllowOutOfOrderExecution) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst UpdateDefaultAllowOutOfOrderExecution) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_UpdateDefaultAllowOutOfOrderExecution,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst UpdateDefaultAllowOutOfOrderExecution) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *UpdateDefaultAllowOutOfOrderExecution) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.NewAllowOutOfOrderExecution == nil {
+ return errors.New("NewAllowOutOfOrderExecution parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *UpdateDefaultAllowOutOfOrderExecution) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("UpdateDefaultAllowOutOfOrderExecution")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("NewAllowOutOfOrderExecution", *inst.NewAllowOutOfOrderExecution))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj UpdateDefaultAllowOutOfOrderExecution) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `NewAllowOutOfOrderExecution` param:
+ err = encoder.Encode(obj.NewAllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *UpdateDefaultAllowOutOfOrderExecution) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `NewAllowOutOfOrderExecution`:
+ err = decoder.Decode(&obj.NewAllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewUpdateDefaultAllowOutOfOrderExecutionInstruction declares a new UpdateDefaultAllowOutOfOrderExecution instruction with the provided parameters and accounts.
+func NewUpdateDefaultAllowOutOfOrderExecutionInstruction(
+ // Parameters:
+ newAllowOutOfOrderExecution bool,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *UpdateDefaultAllowOutOfOrderExecution {
+ return NewUpdateDefaultAllowOutOfOrderExecutionInstructionBuilder().
+ SetNewAllowOutOfOrderExecution(newAllowOutOfOrderExecution).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateDefaultAllowOutOfOrderExecution_test.go b/chains/solana/gobindings/ccip_router/UpdateDefaultAllowOutOfOrderExecution_test.go
new file mode 100644
index 000000000..70a067474
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateDefaultAllowOutOfOrderExecution_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_UpdateDefaultAllowOutOfOrderExecution(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("UpdateDefaultAllowOutOfOrderExecution"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(UpdateDefaultAllowOutOfOrderExecution)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(UpdateDefaultAllowOutOfOrderExecution)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateDefaultGasLimit.go b/chains/solana/gobindings/ccip_router/UpdateDefaultGasLimit.go
new file mode 100644
index 000000000..5975d6313
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateDefaultGasLimit.go
@@ -0,0 +1,173 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Updates the default gas limit in the router configuration.
+//
+// This change affects the default value for gas limit on every other destination chain.
+// The Admin is the only one able to update the default gas limit.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for updating the configuration.
+// * `new_gas_limit` - The new default gas limit.
+type UpdateDefaultGasLimit struct {
+ NewGasLimit *ag_binary.Uint128
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ //
+ // [2] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewUpdateDefaultGasLimitInstructionBuilder creates a new `UpdateDefaultGasLimit` instruction builder.
+func NewUpdateDefaultGasLimitInstructionBuilder() *UpdateDefaultGasLimit {
+ nd := &UpdateDefaultGasLimit{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetNewGasLimit sets the "newGasLimit" parameter.
+func (inst *UpdateDefaultGasLimit) SetNewGasLimit(newGasLimit ag_binary.Uint128) *UpdateDefaultGasLimit {
+ inst.NewGasLimit = &newGasLimit
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *UpdateDefaultGasLimit) SetConfigAccount(config ag_solanago.PublicKey) *UpdateDefaultGasLimit {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *UpdateDefaultGasLimit) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *UpdateDefaultGasLimit) SetAuthorityAccount(authority ag_solanago.PublicKey) *UpdateDefaultGasLimit {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *UpdateDefaultGasLimit) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *UpdateDefaultGasLimit) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *UpdateDefaultGasLimit {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *UpdateDefaultGasLimit) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst UpdateDefaultGasLimit) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_UpdateDefaultGasLimit,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst UpdateDefaultGasLimit) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *UpdateDefaultGasLimit) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.NewGasLimit == nil {
+ return errors.New("NewGasLimit parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *UpdateDefaultGasLimit) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("UpdateDefaultGasLimit")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("NewGasLimit", *inst.NewGasLimit))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj UpdateDefaultGasLimit) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `NewGasLimit` param:
+ err = encoder.Encode(obj.NewGasLimit)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *UpdateDefaultGasLimit) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `NewGasLimit`:
+ err = decoder.Decode(&obj.NewGasLimit)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewUpdateDefaultGasLimitInstruction declares a new UpdateDefaultGasLimit instruction with the provided parameters and accounts.
+func NewUpdateDefaultGasLimitInstruction(
+ // Parameters:
+ newGasLimit ag_binary.Uint128,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *UpdateDefaultGasLimit {
+ return NewUpdateDefaultGasLimitInstructionBuilder().
+ SetNewGasLimit(newGasLimit).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateDefaultGasLimit_test.go b/chains/solana/gobindings/ccip_router/UpdateDefaultGasLimit_test.go
new file mode 100644
index 000000000..f323c69e6
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateDefaultGasLimit_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_UpdateDefaultGasLimit(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("UpdateDefaultGasLimit"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(UpdateDefaultGasLimit)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(UpdateDefaultGasLimit)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateDestChainConfig.go b/chains/solana/gobindings/ccip_router/UpdateDestChainConfig.go
new file mode 100644
index 000000000..86654a836
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateDestChainConfig.go
@@ -0,0 +1,196 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Updates the configuration of the destination chain selector.
+//
+// The Admin is the only one able to update the destination chain config.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for updating the chain selector.
+// * `dest_chain_selector` - The destination chain selector to be updated.
+// * `dest_chain_config` - The new configuration for the destination chain.
+type UpdateDestChainConfig struct {
+ DestChainSelector *uint64
+ DestChainConfig *DestChainConfig
+
+ // [0] = [WRITE] chainState
+ //
+ // [1] = [] config
+ //
+ // [2] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewUpdateDestChainConfigInstructionBuilder creates a new `UpdateDestChainConfig` instruction builder.
+func NewUpdateDestChainConfigInstructionBuilder() *UpdateDestChainConfig {
+ nd := &UpdateDestChainConfig{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetDestChainSelector sets the "destChainSelector" parameter.
+func (inst *UpdateDestChainConfig) SetDestChainSelector(destChainSelector uint64) *UpdateDestChainConfig {
+ inst.DestChainSelector = &destChainSelector
+ return inst
+}
+
+// SetDestChainConfig sets the "destChainConfig" parameter.
+func (inst *UpdateDestChainConfig) SetDestChainConfig(destChainConfig DestChainConfig) *UpdateDestChainConfig {
+ inst.DestChainConfig = &destChainConfig
+ return inst
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *UpdateDestChainConfig) SetChainStateAccount(chainState ag_solanago.PublicKey) *UpdateDestChainConfig {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(chainState).WRITE()
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *UpdateDestChainConfig) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *UpdateDestChainConfig) SetConfigAccount(config ag_solanago.PublicKey) *UpdateDestChainConfig {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *UpdateDestChainConfig) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *UpdateDestChainConfig) SetAuthorityAccount(authority ag_solanago.PublicKey) *UpdateDestChainConfig {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *UpdateDestChainConfig) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst UpdateDestChainConfig) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_UpdateDestChainConfig,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst UpdateDestChainConfig) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *UpdateDestChainConfig) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.DestChainSelector == nil {
+ return errors.New("DestChainSelector parameter is not set")
+ }
+ if inst.DestChainConfig == nil {
+ return errors.New("DestChainConfig parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *UpdateDestChainConfig) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("UpdateDestChainConfig")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("DestChainSelector", *inst.DestChainSelector))
+ paramsBranch.Child(ag_format.Param(" DestChainConfig", *inst.DestChainConfig))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("chainState", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj UpdateDestChainConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `DestChainSelector` param:
+ err = encoder.Encode(obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestChainConfig` param:
+ err = encoder.Encode(obj.DestChainConfig)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *UpdateDestChainConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `DestChainSelector`:
+ err = decoder.Decode(&obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestChainConfig`:
+ err = decoder.Decode(&obj.DestChainConfig)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewUpdateDestChainConfigInstruction declares a new UpdateDestChainConfig instruction with the provided parameters and accounts.
+func NewUpdateDestChainConfigInstruction(
+ // Parameters:
+ destChainSelector uint64,
+ destChainConfig DestChainConfig,
+ // Accounts:
+ chainState ag_solanago.PublicKey,
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *UpdateDestChainConfig {
+ return NewUpdateDestChainConfigInstructionBuilder().
+ SetDestChainSelector(destChainSelector).
+ SetDestChainConfig(destChainConfig).
+ SetChainStateAccount(chainState).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateDestChainConfig_test.go b/chains/solana/gobindings/ccip_router/UpdateDestChainConfig_test.go
new file mode 100644
index 000000000..7057c39c2
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateDestChainConfig_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_UpdateDestChainConfig(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("UpdateDestChainConfig"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(UpdateDestChainConfig)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(UpdateDestChainConfig)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateEnableManualExecutionAfter.go b/chains/solana/gobindings/ccip_router/UpdateEnableManualExecutionAfter.go
new file mode 100644
index 000000000..0927487cd
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateEnableManualExecutionAfter.go
@@ -0,0 +1,173 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Updates the minimum amount of time required between a message being committed and when it can be manually executed.
+//
+// This is part of the OffRamp Configuration for Solana.
+// The Admin is the only one able to update this config.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for updating the configuration.
+// * `new_enable_manual_execution_after` - The new minimum amount of time required.
+type UpdateEnableManualExecutionAfter struct {
+ NewEnableManualExecutionAfter *int64
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ //
+ // [2] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewUpdateEnableManualExecutionAfterInstructionBuilder creates a new `UpdateEnableManualExecutionAfter` instruction builder.
+func NewUpdateEnableManualExecutionAfterInstructionBuilder() *UpdateEnableManualExecutionAfter {
+ nd := &UpdateEnableManualExecutionAfter{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetNewEnableManualExecutionAfter sets the "newEnableManualExecutionAfter" parameter.
+func (inst *UpdateEnableManualExecutionAfter) SetNewEnableManualExecutionAfter(newEnableManualExecutionAfter int64) *UpdateEnableManualExecutionAfter {
+ inst.NewEnableManualExecutionAfter = &newEnableManualExecutionAfter
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *UpdateEnableManualExecutionAfter) SetConfigAccount(config ag_solanago.PublicKey) *UpdateEnableManualExecutionAfter {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *UpdateEnableManualExecutionAfter) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *UpdateEnableManualExecutionAfter) SetAuthorityAccount(authority ag_solanago.PublicKey) *UpdateEnableManualExecutionAfter {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *UpdateEnableManualExecutionAfter) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *UpdateEnableManualExecutionAfter) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *UpdateEnableManualExecutionAfter {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *UpdateEnableManualExecutionAfter) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst UpdateEnableManualExecutionAfter) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_UpdateEnableManualExecutionAfter,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst UpdateEnableManualExecutionAfter) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *UpdateEnableManualExecutionAfter) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.NewEnableManualExecutionAfter == nil {
+ return errors.New("NewEnableManualExecutionAfter parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *UpdateEnableManualExecutionAfter) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("UpdateEnableManualExecutionAfter")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("NewEnableManualExecutionAfter", *inst.NewEnableManualExecutionAfter))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj UpdateEnableManualExecutionAfter) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `NewEnableManualExecutionAfter` param:
+ err = encoder.Encode(obj.NewEnableManualExecutionAfter)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *UpdateEnableManualExecutionAfter) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `NewEnableManualExecutionAfter`:
+ err = decoder.Decode(&obj.NewEnableManualExecutionAfter)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewUpdateEnableManualExecutionAfterInstruction declares a new UpdateEnableManualExecutionAfter instruction with the provided parameters and accounts.
+func NewUpdateEnableManualExecutionAfterInstruction(
+ // Parameters:
+ newEnableManualExecutionAfter int64,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *UpdateEnableManualExecutionAfter {
+ return NewUpdateEnableManualExecutionAfterInstructionBuilder().
+ SetNewEnableManualExecutionAfter(newEnableManualExecutionAfter).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateEnableManualExecutionAfter_test.go b/chains/solana/gobindings/ccip_router/UpdateEnableManualExecutionAfter_test.go
new file mode 100644
index 000000000..cc37505f5
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateEnableManualExecutionAfter_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_UpdateEnableManualExecutionAfter(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("UpdateEnableManualExecutionAfter"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(UpdateEnableManualExecutionAfter)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(UpdateEnableManualExecutionAfter)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateSolanaChainSelector.go b/chains/solana/gobindings/ccip_router/UpdateSolanaChainSelector.go
new file mode 100644
index 000000000..bb28533bd
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateSolanaChainSelector.go
@@ -0,0 +1,172 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Updates the Solana chain selector in the router configuration.
+//
+// This method should only be used if there was an error with the initial configuration or if the solana chain selector changes.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for updating the configuration.
+// * `new_chain_selector` - The new chain selector for Solana.
+type UpdateSolanaChainSelector struct {
+ NewChainSelector *uint64
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ //
+ // [2] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewUpdateSolanaChainSelectorInstructionBuilder creates a new `UpdateSolanaChainSelector` instruction builder.
+func NewUpdateSolanaChainSelectorInstructionBuilder() *UpdateSolanaChainSelector {
+ nd := &UpdateSolanaChainSelector{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetNewChainSelector sets the "newChainSelector" parameter.
+func (inst *UpdateSolanaChainSelector) SetNewChainSelector(newChainSelector uint64) *UpdateSolanaChainSelector {
+ inst.NewChainSelector = &newChainSelector
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *UpdateSolanaChainSelector) SetConfigAccount(config ag_solanago.PublicKey) *UpdateSolanaChainSelector {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *UpdateSolanaChainSelector) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *UpdateSolanaChainSelector) SetAuthorityAccount(authority ag_solanago.PublicKey) *UpdateSolanaChainSelector {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *UpdateSolanaChainSelector) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *UpdateSolanaChainSelector) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *UpdateSolanaChainSelector {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *UpdateSolanaChainSelector) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst UpdateSolanaChainSelector) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_UpdateSolanaChainSelector,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst UpdateSolanaChainSelector) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *UpdateSolanaChainSelector) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.NewChainSelector == nil {
+ return errors.New("NewChainSelector parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *UpdateSolanaChainSelector) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("UpdateSolanaChainSelector")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("NewChainSelector", *inst.NewChainSelector))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj UpdateSolanaChainSelector) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `NewChainSelector` param:
+ err = encoder.Encode(obj.NewChainSelector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *UpdateSolanaChainSelector) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `NewChainSelector`:
+ err = decoder.Decode(&obj.NewChainSelector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewUpdateSolanaChainSelectorInstruction declares a new UpdateSolanaChainSelector instruction with the provided parameters and accounts.
+func NewUpdateSolanaChainSelectorInstruction(
+ // Parameters:
+ newChainSelector uint64,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *UpdateSolanaChainSelector {
+ return NewUpdateSolanaChainSelectorInstructionBuilder().
+ SetNewChainSelector(newChainSelector).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateSolanaChainSelector_test.go b/chains/solana/gobindings/ccip_router/UpdateSolanaChainSelector_test.go
new file mode 100644
index 000000000..00901a4a9
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateSolanaChainSelector_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_UpdateSolanaChainSelector(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("UpdateSolanaChainSelector"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(UpdateSolanaChainSelector)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(UpdateSolanaChainSelector)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateSourceChainConfig.go b/chains/solana/gobindings/ccip_router/UpdateSourceChainConfig.go
new file mode 100644
index 000000000..ba0557772
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateSourceChainConfig.go
@@ -0,0 +1,196 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Updates the configuration of the source chain selector.
+//
+// The Admin is the only one able to update the source chain config.
+//
+// # Arguments
+//
+// * `ctx` - The context containing the accounts required for updating the chain selector.
+// * `source_chain_selector` - The source chain selector to be updated.
+// * `source_chain_config` - The new configuration for the source chain.
+type UpdateSourceChainConfig struct {
+ SourceChainSelector *uint64
+ SourceChainConfig *SourceChainConfig
+
+ // [0] = [WRITE] chainState
+ //
+ // [1] = [] config
+ //
+ // [2] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewUpdateSourceChainConfigInstructionBuilder creates a new `UpdateSourceChainConfig` instruction builder.
+func NewUpdateSourceChainConfigInstructionBuilder() *UpdateSourceChainConfig {
+ nd := &UpdateSourceChainConfig{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetSourceChainSelector sets the "sourceChainSelector" parameter.
+func (inst *UpdateSourceChainConfig) SetSourceChainSelector(sourceChainSelector uint64) *UpdateSourceChainConfig {
+ inst.SourceChainSelector = &sourceChainSelector
+ return inst
+}
+
+// SetSourceChainConfig sets the "sourceChainConfig" parameter.
+func (inst *UpdateSourceChainConfig) SetSourceChainConfig(sourceChainConfig SourceChainConfig) *UpdateSourceChainConfig {
+ inst.SourceChainConfig = &sourceChainConfig
+ return inst
+}
+
+// SetChainStateAccount sets the "chainState" account.
+func (inst *UpdateSourceChainConfig) SetChainStateAccount(chainState ag_solanago.PublicKey) *UpdateSourceChainConfig {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(chainState).WRITE()
+ return inst
+}
+
+// GetChainStateAccount gets the "chainState" account.
+func (inst *UpdateSourceChainConfig) GetChainStateAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *UpdateSourceChainConfig) SetConfigAccount(config ag_solanago.PublicKey) *UpdateSourceChainConfig {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *UpdateSourceChainConfig) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *UpdateSourceChainConfig) SetAuthorityAccount(authority ag_solanago.PublicKey) *UpdateSourceChainConfig {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *UpdateSourceChainConfig) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst UpdateSourceChainConfig) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_UpdateSourceChainConfig,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst UpdateSourceChainConfig) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *UpdateSourceChainConfig) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.SourceChainSelector == nil {
+ return errors.New("SourceChainSelector parameter is not set")
+ }
+ if inst.SourceChainConfig == nil {
+ return errors.New("SourceChainConfig parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.ChainState is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *UpdateSourceChainConfig) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("UpdateSourceChainConfig")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("SourceChainSelector", *inst.SourceChainSelector))
+ paramsBranch.Child(ag_format.Param(" SourceChainConfig", *inst.SourceChainConfig))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("chainState", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj UpdateSourceChainConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `SourceChainSelector` param:
+ err = encoder.Encode(obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourceChainConfig` param:
+ err = encoder.Encode(obj.SourceChainConfig)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *UpdateSourceChainConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `SourceChainSelector`:
+ err = decoder.Decode(&obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourceChainConfig`:
+ err = decoder.Decode(&obj.SourceChainConfig)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewUpdateSourceChainConfigInstruction declares a new UpdateSourceChainConfig instruction with the provided parameters and accounts.
+func NewUpdateSourceChainConfigInstruction(
+ // Parameters:
+ sourceChainSelector uint64,
+ sourceChainConfig SourceChainConfig,
+ // Accounts:
+ chainState ag_solanago.PublicKey,
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *UpdateSourceChainConfig {
+ return NewUpdateSourceChainConfigInstructionBuilder().
+ SetSourceChainSelector(sourceChainSelector).
+ SetSourceChainConfig(sourceChainConfig).
+ SetChainStateAccount(chainState).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/ccip_router/UpdateSourceChainConfig_test.go b/chains/solana/gobindings/ccip_router/UpdateSourceChainConfig_test.go
new file mode 100644
index 000000000..59289a8b1
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/UpdateSourceChainConfig_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_UpdateSourceChainConfig(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("UpdateSourceChainConfig"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(UpdateSourceChainConfig)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(UpdateSourceChainConfig)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/ccip_router/accounts.go b/chains/solana/gobindings/ccip_router/accounts.go
new file mode 100644
index 000000000..f4d4baa84
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/accounts.go
@@ -0,0 +1,619 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type Config struct {
+ Version uint8
+ DefaultAllowOutOfOrderExecution uint8
+ Padding0 [6]uint8
+ SolanaChainSelector uint64
+ DefaultGasLimit ag_binary.Uint128
+ Padding1 [8]uint8
+ Owner ag_solanago.PublicKey
+ ProposedOwner ag_solanago.PublicKey
+ EnableManualExecutionAfter int64
+ Padding2 [8]uint8
+ Ocr3 [2]Ocr3Config
+ PaddingBeforeBilling [8]uint8
+ LatestPriceSequenceNumber uint64
+}
+
+var ConfigDiscriminator = [8]byte{155, 12, 170, 224, 30, 250, 204, 130}
+
+func (obj Config) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ConfigDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Version` param:
+ err = encoder.Encode(obj.Version)
+ if err != nil {
+ return err
+ }
+ // Serialize `DefaultAllowOutOfOrderExecution` param:
+ err = encoder.Encode(obj.DefaultAllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ // Serialize `Padding0` param:
+ err = encoder.Encode(obj.Padding0)
+ if err != nil {
+ return err
+ }
+ // Serialize `SolanaChainSelector` param:
+ err = encoder.Encode(obj.SolanaChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `DefaultGasLimit` param:
+ err = encoder.Encode(obj.DefaultGasLimit)
+ if err != nil {
+ return err
+ }
+ // Serialize `Padding1` param:
+ err = encoder.Encode(obj.Padding1)
+ if err != nil {
+ return err
+ }
+ // Serialize `Owner` param:
+ err = encoder.Encode(obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Serialize `EnableManualExecutionAfter` param:
+ err = encoder.Encode(obj.EnableManualExecutionAfter)
+ if err != nil {
+ return err
+ }
+ // Serialize `Padding2` param:
+ err = encoder.Encode(obj.Padding2)
+ if err != nil {
+ return err
+ }
+ // Serialize `Ocr3` param:
+ err = encoder.Encode(obj.Ocr3)
+ if err != nil {
+ return err
+ }
+ // Serialize `PaddingBeforeBilling` param:
+ err = encoder.Encode(obj.PaddingBeforeBilling)
+ if err != nil {
+ return err
+ }
+ // Serialize `LatestPriceSequenceNumber` param:
+ err = encoder.Encode(obj.LatestPriceSequenceNumber)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Config) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ConfigDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[155 12 170 224 30 250 204 130]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Version`:
+ err = decoder.Decode(&obj.Version)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DefaultAllowOutOfOrderExecution`:
+ err = decoder.Decode(&obj.DefaultAllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Padding0`:
+ err = decoder.Decode(&obj.Padding0)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SolanaChainSelector`:
+ err = decoder.Decode(&obj.SolanaChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DefaultGasLimit`:
+ err = decoder.Decode(&obj.DefaultGasLimit)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Padding1`:
+ err = decoder.Decode(&obj.Padding1)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Owner`:
+ err = decoder.Decode(&obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `EnableManualExecutionAfter`:
+ err = decoder.Decode(&obj.EnableManualExecutionAfter)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Padding2`:
+ err = decoder.Decode(&obj.Padding2)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Ocr3`:
+ err = decoder.Decode(&obj.Ocr3)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PaddingBeforeBilling`:
+ err = decoder.Decode(&obj.PaddingBeforeBilling)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LatestPriceSequenceNumber`:
+ err = decoder.Decode(&obj.LatestPriceSequenceNumber)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ChainState struct {
+ Version uint8
+ SourceChain SourceChain
+ DestChain DestChain
+}
+
+var ChainStateDiscriminator = [8]byte{130, 46, 94, 156, 79, 53, 170, 50}
+
+func (obj ChainState) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ChainStateDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Version` param:
+ err = encoder.Encode(obj.Version)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourceChain` param:
+ err = encoder.Encode(obj.SourceChain)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestChain` param:
+ err = encoder.Encode(obj.DestChain)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ChainState) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ChainStateDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[130 46 94 156 79 53 170 50]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Version`:
+ err = decoder.Decode(&obj.Version)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourceChain`:
+ err = decoder.Decode(&obj.SourceChain)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestChain`:
+ err = decoder.Decode(&obj.DestChain)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type Nonce struct {
+ Version uint8
+ Counter uint64
+}
+
+var NonceDiscriminator = [8]byte{143, 197, 147, 95, 106, 165, 50, 43}
+
+func (obj Nonce) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(NonceDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Version` param:
+ err = encoder.Encode(obj.Version)
+ if err != nil {
+ return err
+ }
+ // Serialize `Counter` param:
+ err = encoder.Encode(obj.Counter)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Nonce) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(NonceDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[143 197 147 95 106 165 50 43]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Version`:
+ err = decoder.Decode(&obj.Version)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Counter`:
+ err = decoder.Decode(&obj.Counter)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ExternalExecutionConfig struct{}
+
+var ExternalExecutionConfigDiscriminator = [8]byte{159, 157, 150, 212, 168, 103, 117, 39}
+
+func (obj ExternalExecutionConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ExternalExecutionConfigDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ExternalExecutionConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ExternalExecutionConfigDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[159 157 150 212 168 103 117 39]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ return nil
+}
+
+type CommitReport struct {
+ Version uint8
+ Timestamp int64
+ MinMsgNr uint64
+ MaxMsgNr uint64
+ ExecutionStates ag_binary.Uint128
+}
+
+var CommitReportDiscriminator = [8]byte{46, 231, 247, 231, 174, 68, 34, 26}
+
+func (obj CommitReport) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(CommitReportDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Version` param:
+ err = encoder.Encode(obj.Version)
+ if err != nil {
+ return err
+ }
+ // Serialize `Timestamp` param:
+ err = encoder.Encode(obj.Timestamp)
+ if err != nil {
+ return err
+ }
+ // Serialize `MinMsgNr` param:
+ err = encoder.Encode(obj.MinMsgNr)
+ if err != nil {
+ return err
+ }
+ // Serialize `MaxMsgNr` param:
+ err = encoder.Encode(obj.MaxMsgNr)
+ if err != nil {
+ return err
+ }
+ // Serialize `ExecutionStates` param:
+ err = encoder.Encode(obj.ExecutionStates)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *CommitReport) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(CommitReportDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[46 231 247 231 174 68 34 26]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Version`:
+ err = decoder.Decode(&obj.Version)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Timestamp`:
+ err = decoder.Decode(&obj.Timestamp)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MinMsgNr`:
+ err = decoder.Decode(&obj.MinMsgNr)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MaxMsgNr`:
+ err = decoder.Decode(&obj.MaxMsgNr)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ExecutionStates`:
+ err = decoder.Decode(&obj.ExecutionStates)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type PerChainPerTokenConfig struct {
+ Version uint8
+ ChainSelector uint64
+ Mint ag_solanago.PublicKey
+ Billing TokenBilling
+}
+
+var PerChainPerTokenConfigDiscriminator = [8]byte{183, 88, 20, 99, 246, 46, 51, 230}
+
+func (obj PerChainPerTokenConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(PerChainPerTokenConfigDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Version` param:
+ err = encoder.Encode(obj.Version)
+ if err != nil {
+ return err
+ }
+ // Serialize `ChainSelector` param:
+ err = encoder.Encode(obj.ChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Serialize `Billing` param:
+ err = encoder.Encode(obj.Billing)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *PerChainPerTokenConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(PerChainPerTokenConfigDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[183 88 20 99 246 46 51 230]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Version`:
+ err = decoder.Decode(&obj.Version)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ChainSelector`:
+ err = decoder.Decode(&obj.ChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Billing`:
+ err = decoder.Decode(&obj.Billing)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type BillingTokenConfigWrapper struct {
+ Version uint8
+ Config BillingTokenConfig
+}
+
+var BillingTokenConfigWrapperDiscriminator = [8]byte{63, 178, 72, 57, 171, 66, 44, 151}
+
+func (obj BillingTokenConfigWrapper) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(BillingTokenConfigWrapperDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Version` param:
+ err = encoder.Encode(obj.Version)
+ if err != nil {
+ return err
+ }
+ // Serialize `Config` param:
+ err = encoder.Encode(obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *BillingTokenConfigWrapper) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(BillingTokenConfigWrapperDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[63 178 72 57 171 66 44 151]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Version`:
+ err = decoder.Decode(&obj.Version)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Config`:
+ err = decoder.Decode(&obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type TokenAdminRegistry struct {
+ Version uint8
+ Administrator ag_solanago.PublicKey
+ PendingAdministrator ag_solanago.PublicKey
+ LookupTable ag_solanago.PublicKey
+}
+
+var TokenAdminRegistryDiscriminator = [8]byte{70, 92, 207, 200, 76, 17, 57, 114}
+
+func (obj TokenAdminRegistry) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(TokenAdminRegistryDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Version` param:
+ err = encoder.Encode(obj.Version)
+ if err != nil {
+ return err
+ }
+ // Serialize `Administrator` param:
+ err = encoder.Encode(obj.Administrator)
+ if err != nil {
+ return err
+ }
+ // Serialize `PendingAdministrator` param:
+ err = encoder.Encode(obj.PendingAdministrator)
+ if err != nil {
+ return err
+ }
+ // Serialize `LookupTable` param:
+ err = encoder.Encode(obj.LookupTable)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *TokenAdminRegistry) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(TokenAdminRegistryDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[70 92 207 200 76 17 57 114]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Version`:
+ err = decoder.Decode(&obj.Version)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Administrator`:
+ err = decoder.Decode(&obj.Administrator)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PendingAdministrator`:
+ err = decoder.Decode(&obj.PendingAdministrator)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LookupTable`:
+ err = decoder.Decode(&obj.LookupTable)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/ccip_router/instructions.go b/chains/solana/gobindings/ccip_router/instructions.go
new file mode 100644
index 000000000..4f792d6f9
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/instructions.go
@@ -0,0 +1,570 @@
+// The `ccip_router` module contains the implementation of the Cross-Chain Interoperability Protocol (CCIP) Router.
+//
+// This is the Collapsed Router Program for CCIP.
+// As it's upgradable persisting the same program id, there is no need to have an indirection of a Proxy Program.
+// This Router handles both the OnRamp and OffRamp flow of the CCIP Messages.
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ "fmt"
+ ag_spew "github.com/davecgh/go-spew/spew"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_text "github.com/gagliardetto/solana-go/text"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+var ProgramID ag_solanago.PublicKey
+
+func SetProgramID(pubkey ag_solanago.PublicKey) {
+ ProgramID = pubkey
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+}
+
+const ProgramName = "CcipRouter"
+
+func init() {
+ if !ProgramID.IsZero() {
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+ }
+}
+
+var (
+ // Initializes the CCIP Router.
+ //
+ // The initialization of the Router is responsibility of Admin, nothing more than calling this method should be done first.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for initialization.
+ // * `solana_chain_selector` - The chain selector for Solana.
+ // * `default_gas_limit` - The default gas limit for other destination chains.
+ // * `default_allow_out_of_order_execution` - Whether out-of-order execution is allowed by default for other destination chains.
+ // * `enable_execution_after` - The minimum amount of time required between a message has been committed and can be manually executed.
+ Instruction_Initialize = ag_binary.TypeID([8]byte{175, 175, 109, 31, 13, 152, 155, 237})
+
+ // Transfers the ownership of the router to a new proposed owner.
+ //
+ // Shared func signature with other programs
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for the transfer.
+ // * `proposed_owner` - The public key of the new proposed owner.
+ Instruction_TransferOwnership = ag_binary.TypeID([8]byte{65, 177, 215, 73, 53, 45, 99, 47})
+
+ // Accepts the ownership of the router by the proposed owner.
+ //
+ // Shared func signature with other programs
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for accepting ownership.
+ // The new owner must be a signer of the transaction.
+ Instruction_AcceptOwnership = ag_binary.TypeID([8]byte{172, 23, 43, 13, 238, 213, 85, 150})
+
+ // Adds a new chain selector to the router.
+ //
+ // The Admin needs to add any new chain supported (this means both OnRamp and OffRamp).
+ // When adding a new chain, the Admin needs to specify if it's enabled or not.
+ // They may enable only source, or only destination, or neither, or both.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for adding the chain selector.
+ // * `new_chain_selector` - The new chain selector to be added.
+ // * `source_chain_config` - The configuration for the chain as source.
+ // * `dest_chain_config` - The configuration for the chain as destination.
+ Instruction_AddChainSelector = ag_binary.TypeID([8]byte{28, 60, 171, 0, 195, 113, 56, 7})
+
+ // Disables the source chain selector.
+ //
+ // The Admin is the only one able to disable the chain selector as source. This method is thought of as an emergency kill-switch.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for disabling the chain selector.
+ // * `source_chain_selector` - The source chain selector to be disabled.
+ Instruction_DisableSourceChainSelector = ag_binary.TypeID([8]byte{58, 101, 54, 252, 248, 31, 226, 121})
+
+ // Disables the destination chain selector.
+ //
+ // The Admin is the only one able to disable the chain selector as destination. This method is thought of as an emergency kill-switch.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for disabling the chain selector.
+ // * `dest_chain_selector` - The destination chain selector to be disabled.
+ Instruction_DisableDestChainSelector = ag_binary.TypeID([8]byte{214, 71, 132, 65, 177, 59, 170, 72})
+
+ // Updates the configuration of the source chain selector.
+ //
+ // The Admin is the only one able to update the source chain config.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for updating the chain selector.
+ // * `source_chain_selector` - The source chain selector to be updated.
+ // * `source_chain_config` - The new configuration for the source chain.
+ Instruction_UpdateSourceChainConfig = ag_binary.TypeID([8]byte{52, 85, 37, 124, 209, 140, 181, 104})
+
+ // Updates the configuration of the destination chain selector.
+ //
+ // The Admin is the only one able to update the destination chain config.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for updating the chain selector.
+ // * `dest_chain_selector` - The destination chain selector to be updated.
+ // * `dest_chain_config` - The new configuration for the destination chain.
+ Instruction_UpdateDestChainConfig = ag_binary.TypeID([8]byte{215, 122, 81, 22, 190, 58, 219, 13})
+
+ // Updates the Solana chain selector in the router configuration.
+ //
+ // This method should only be used if there was an error with the initial configuration or if the solana chain selector changes.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for updating the configuration.
+ // * `new_chain_selector` - The new chain selector for Solana.
+ Instruction_UpdateSolanaChainSelector = ag_binary.TypeID([8]byte{128, 198, 143, 222, 43, 55, 119, 106})
+
+ // Updates the default gas limit in the router configuration.
+ //
+ // This change affects the default value for gas limit on every other destination chain.
+ // The Admin is the only one able to update the default gas limit.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for updating the configuration.
+ // * `new_gas_limit` - The new default gas limit.
+ Instruction_UpdateDefaultGasLimit = ag_binary.TypeID([8]byte{201, 34, 231, 229, 247, 252, 77, 210})
+
+ // Updates the default setting for allowing out-of-order execution for other destination chains.
+ // The Admin is the only one able to update this config.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for updating the configuration.
+ // * `new_allow_out_of_order_execution` - The new setting for allowing out-of-order execution.
+ Instruction_UpdateDefaultAllowOutOfOrderExecution = ag_binary.TypeID([8]byte{44, 54, 136, 71, 177, 17, 18, 241})
+
+ // Updates the minimum amount of time required between a message being committed and when it can be manually executed.
+ //
+ // This is part of the OffRamp Configuration for Solana.
+ // The Admin is the only one able to update this config.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for updating the configuration.
+ // * `new_enable_manual_execution_after` - The new minimum amount of time required.
+ Instruction_UpdateEnableManualExecutionAfter = ag_binary.TypeID([8]byte{157, 236, 73, 92, 84, 197, 152, 105})
+
+ // Registers the Token Admin Registry via the CCIP Admin
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for registration.
+ // * `mint` - The public key of the token mint.
+ // * `token_admin_registry_admin` - The public key of the token admin registry admin.
+ Instruction_RegisterTokenAdminRegistryViaGetCcipAdmin = ag_binary.TypeID([8]byte{46, 246, 21, 58, 175, 69, 40, 202})
+
+ // Registers the Token Admin Registry via the token owner.
+ //
+ // The Authority of the Mint Token can claim the registry of the token.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for registration.
+ Instruction_RegisterTokenAdminRegistryViaOwner = ag_binary.TypeID([8]byte{85, 191, 10, 113, 134, 138, 144, 16})
+
+ // Sets the pool lookup table for a given token mint.
+ //
+ // The administrator of the token admin registry can set the pool lookup table for a given token mint.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for setting the pool.
+ // * `mint` - The public key of the token mint.
+ // * `pool_lookup_table` - The public key of the pool lookup table, this address will be used for validations when interacting with the pool.
+ Instruction_SetPool = ag_binary.TypeID([8]byte{119, 30, 14, 180, 115, 225, 167, 238})
+
+ // Transfers the admin role of the token admin registry to a new admin.
+ //
+ // Only the Admin can transfer the Admin Role of the Token Admin Registry, this setups the Pending Admin and then it's their responsibility to accept the role.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for the transfer.
+ // * `mint` - The public key of the token mint.
+ // * `new_admin` - The public key of the new admin.
+ Instruction_TransferAdminRoleTokenAdminRegistry = ag_binary.TypeID([8]byte{178, 98, 203, 181, 203, 107, 106, 14})
+
+ // Accepts the admin role of the token admin registry.
+ //
+ // The Pending Admin must call this function to accept the admin role of the Token Admin Registry.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for accepting the admin role.
+ // * `mint` - The public key of the token mint.
+ Instruction_AcceptAdminRoleTokenAdminRegistry = ag_binary.TypeID([8]byte{106, 240, 16, 173, 137, 213, 163, 246})
+
+ // Sets the token billing configuration.
+ //
+ // Only CCIP Admin can set the token billing configuration.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for setting the token billing configuration.
+ // * `_chain_selector` - The chain selector.
+ // * `_mint` - The public key of the token mint.
+ // * `cfg` - The token billing configuration.
+ Instruction_SetTokenBilling = ag_binary.TypeID([8]byte{225, 230, 37, 71, 131, 209, 54, 230})
+
+ // Sets the OCR configuration.
+ // Only CCIP Admin can set the OCR configuration.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for setting the OCR configuration.
+ // * `plugin_type` - The type of OCR plugin [0: Commit, 1: Execution].
+ // * `config_info` - The OCR configuration information.
+ // * `signers` - The list of signers.
+ // * `transmitters` - The list of transmitters.
+ Instruction_SetOcrConfig = ag_binary.TypeID([8]byte{4, 131, 107, 110, 250, 158, 244, 200})
+
+ // Adds a billing token configuration.
+ // Only CCIP Admin can add a billing token configuration.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for adding the billing token configuration.
+ // * `config` - The billing token configuration to be added.
+ Instruction_AddBillingTokenConfig = ag_binary.TypeID([8]byte{63, 156, 254, 216, 227, 53, 0, 69})
+
+ // Updates the billing token configuration.
+ // Only CCIP Admin can update a billing token configuration.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for updating the billing token configuration.
+ // * `config` - The new billing token configuration.
+ Instruction_UpdateBillingTokenConfig = ag_binary.TypeID([8]byte{140, 184, 124, 146, 204, 62, 244, 79})
+
+ // Removes the billing token configuration.
+ // Only CCIP Admin can remove a billing token configuration.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for removing the billing token configuration.
+ Instruction_RemoveBillingTokenConfig = ag_binary.TypeID([8]byte{0, 194, 92, 161, 29, 8, 10, 91})
+
+ // Calculates the fee for sending a message to the destination chain.
+ //
+ // # Arguments
+ //
+ // * `_ctx` - The context containing the accounts required for the fee calculation.
+ // * `dest_chain_selector` - The chain selector for the destination chain.
+ // * `message` - The message to be sent.
+ //
+ // # Returns
+ //
+ // The fee amount in u64.
+ Instruction_GetFee = ag_binary.TypeID([8]byte{115, 195, 235, 161, 25, 219, 60, 29})
+
+ // ON RAMP FLOW
+ // Sends a message to the destination chain.
+ //
+ // Request a message to be sent to the destination chain.
+ // The method name needs to be ccip_send with Anchor encoding.
+ // This function is called by the CCIP Sender Contract (or final user) to send a message to the CCIP Router.
+ // The message will be sent to the receiver on the destination chain selector.
+ // This message emits the event CCIPSendRequested with all the necessary data to be retrieved by the OffChain Code
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for sending the message.
+ // * `dest_chain_selector` - The chain selector for the destination chain.
+ // * `message` - The message to be sent. The size limit of data is 256 bytes.
+ Instruction_CcipSend = ag_binary.TypeID([8]byte{108, 216, 134, 191, 249, 234, 33, 84})
+
+ // OFF RAMP FLOW
+ // Commits a report to the router.
+ //
+ // The method name needs to be commit with Anchor encoding.
+ //
+ // This function is called by the OffChain when committing one Report to the Solana Router.
+ // In this Flow only one report is sent, the Commit Report. This is different as EVM does,
+ // this is because here all the chain state is stored in one account per Merkle Tree Root.
+ // So, to avoid having to send a dynamic size array of accounts, in this message only one Commit Report Account is sent.
+ // This message validates the signatures of the report and stores the Merkle Root in the Commit Report Account.
+ // The Report must contain an interval of messages, and the min of them must be the next sequence number expected.
+ // The max size of the interval is 64.
+ // This message emits two events: CommitReportAccepted and Transmitted.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for the commit.
+ // * `report_context_byte_words` - consists of:
+ // * report_context_byte_words[0]: ConfigDigest
+ // * report_context_byte_words[1]: 24 byte padding, 8 byte sequence number
+ // * report_context_byte_words[2]: ExtraHash
+ // * `report` - The commit input report, single merkle root with RMN signatures and price updates
+ // * `signatures` - The list of signatures. v0.29.0 - anchor idl does not build with ocr3base::SIGNATURE_LENGTH
+ Instruction_Commit = ag_binary.TypeID([8]byte{223, 140, 142, 165, 229, 208, 156, 74})
+
+ // OFF RAMP FLOW
+ // Executes a message on the destination chain.
+ //
+ // The method name needs to be execute with Anchor encoding.
+ //
+ // This function is called by the OffChain when executing one Report to the Solana Router.
+ // In this Flow only one message is sent, the Execution Report. This is different as EVM does,
+ // this is because there is no try/catch mechanism to allow batch execution.
+ // This message validates that the Merkle Tree Proof of the given message is correct and is stored in the Commit Report Account.
+ // The message must be untouched to be executed.
+ // This message emits the event ExecutionStateChanged with the new state of the message.
+ // Finally, executes the CPI instruction to the receiver program in the ccip_receive message.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for the execute.
+ // * `execution_report` - the execution report containing only one message and proofs
+ // * `report_context_byte_words` - report_context after execution_report to match context for manually execute (proper decoding order)
+ // * consists of:
+ // * report_context_byte_words[0]: ConfigDigest
+ // * report_context_byte_words[1]: 24 byte padding, 8 byte sequence number
+ // * report_context_byte_words[2]: ExtraHash
+ Instruction_Execute = ag_binary.TypeID([8]byte{130, 221, 242, 154, 13, 193, 189, 29})
+
+ // Manually executes a report to the router.
+ //
+ // When a message is not being executed, then the user can trigger the execution manually.
+ // No verification over the transmitter, but the message needs to be in some commit report.
+ // It validates that the required time has passed since the commit and then executes the report.
+ //
+ // # Arguments
+ //
+ // * `ctx` - The context containing the accounts required for the execution.
+ // * `execution_report` - The execution report containing the message and proofs.
+ Instruction_ManuallyExecute = ag_binary.TypeID([8]byte{238, 219, 224, 11, 226, 248, 47, 192})
+)
+
+// InstructionIDToName returns the name of the instruction given its ID.
+func InstructionIDToName(id ag_binary.TypeID) string {
+ switch id {
+ case Instruction_Initialize:
+ return "Initialize"
+ case Instruction_TransferOwnership:
+ return "TransferOwnership"
+ case Instruction_AcceptOwnership:
+ return "AcceptOwnership"
+ case Instruction_AddChainSelector:
+ return "AddChainSelector"
+ case Instruction_DisableSourceChainSelector:
+ return "DisableSourceChainSelector"
+ case Instruction_DisableDestChainSelector:
+ return "DisableDestChainSelector"
+ case Instruction_UpdateSourceChainConfig:
+ return "UpdateSourceChainConfig"
+ case Instruction_UpdateDestChainConfig:
+ return "UpdateDestChainConfig"
+ case Instruction_UpdateSolanaChainSelector:
+ return "UpdateSolanaChainSelector"
+ case Instruction_UpdateDefaultGasLimit:
+ return "UpdateDefaultGasLimit"
+ case Instruction_UpdateDefaultAllowOutOfOrderExecution:
+ return "UpdateDefaultAllowOutOfOrderExecution"
+ case Instruction_UpdateEnableManualExecutionAfter:
+ return "UpdateEnableManualExecutionAfter"
+ case Instruction_RegisterTokenAdminRegistryViaGetCcipAdmin:
+ return "RegisterTokenAdminRegistryViaGetCcipAdmin"
+ case Instruction_RegisterTokenAdminRegistryViaOwner:
+ return "RegisterTokenAdminRegistryViaOwner"
+ case Instruction_SetPool:
+ return "SetPool"
+ case Instruction_TransferAdminRoleTokenAdminRegistry:
+ return "TransferAdminRoleTokenAdminRegistry"
+ case Instruction_AcceptAdminRoleTokenAdminRegistry:
+ return "AcceptAdminRoleTokenAdminRegistry"
+ case Instruction_SetTokenBilling:
+ return "SetTokenBilling"
+ case Instruction_SetOcrConfig:
+ return "SetOcrConfig"
+ case Instruction_AddBillingTokenConfig:
+ return "AddBillingTokenConfig"
+ case Instruction_UpdateBillingTokenConfig:
+ return "UpdateBillingTokenConfig"
+ case Instruction_RemoveBillingTokenConfig:
+ return "RemoveBillingTokenConfig"
+ case Instruction_GetFee:
+ return "GetFee"
+ case Instruction_CcipSend:
+ return "CcipSend"
+ case Instruction_Commit:
+ return "Commit"
+ case Instruction_Execute:
+ return "Execute"
+ case Instruction_ManuallyExecute:
+ return "ManuallyExecute"
+ default:
+ return ""
+ }
+}
+
+type Instruction struct {
+ ag_binary.BaseVariant
+}
+
+func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) {
+ if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok {
+ enToTree.EncodeToTree(parent)
+ } else {
+ parent.Child(ag_spew.Sdump(inst))
+ }
+}
+
+var InstructionImplDef = ag_binary.NewVariantDefinition(
+ ag_binary.AnchorTypeIDEncoding,
+ []ag_binary.VariantType{
+ {
+ "initialize", (*Initialize)(nil),
+ },
+ {
+ "transfer_ownership", (*TransferOwnership)(nil),
+ },
+ {
+ "accept_ownership", (*AcceptOwnership)(nil),
+ },
+ {
+ "add_chain_selector", (*AddChainSelector)(nil),
+ },
+ {
+ "disable_source_chain_selector", (*DisableSourceChainSelector)(nil),
+ },
+ {
+ "disable_dest_chain_selector", (*DisableDestChainSelector)(nil),
+ },
+ {
+ "update_source_chain_config", (*UpdateSourceChainConfig)(nil),
+ },
+ {
+ "update_dest_chain_config", (*UpdateDestChainConfig)(nil),
+ },
+ {
+ "update_solana_chain_selector", (*UpdateSolanaChainSelector)(nil),
+ },
+ {
+ "update_default_gas_limit", (*UpdateDefaultGasLimit)(nil),
+ },
+ {
+ "update_default_allow_out_of_order_execution", (*UpdateDefaultAllowOutOfOrderExecution)(nil),
+ },
+ {
+ "update_enable_manual_execution_after", (*UpdateEnableManualExecutionAfter)(nil),
+ },
+ {
+ "register_token_admin_registry_via_get_ccip_admin", (*RegisterTokenAdminRegistryViaGetCcipAdmin)(nil),
+ },
+ {
+ "register_token_admin_registry_via_owner", (*RegisterTokenAdminRegistryViaOwner)(nil),
+ },
+ {
+ "set_pool", (*SetPool)(nil),
+ },
+ {
+ "transfer_admin_role_token_admin_registry", (*TransferAdminRoleTokenAdminRegistry)(nil),
+ },
+ {
+ "accept_admin_role_token_admin_registry", (*AcceptAdminRoleTokenAdminRegistry)(nil),
+ },
+ {
+ "set_token_billing", (*SetTokenBilling)(nil),
+ },
+ {
+ "set_ocr_config", (*SetOcrConfig)(nil),
+ },
+ {
+ "add_billing_token_config", (*AddBillingTokenConfig)(nil),
+ },
+ {
+ "update_billing_token_config", (*UpdateBillingTokenConfig)(nil),
+ },
+ {
+ "remove_billing_token_config", (*RemoveBillingTokenConfig)(nil),
+ },
+ {
+ "get_fee", (*GetFee)(nil),
+ },
+ {
+ "ccip_send", (*CcipSend)(nil),
+ },
+ {
+ "commit", (*Commit)(nil),
+ },
+ {
+ "execute", (*Execute)(nil),
+ },
+ {
+ "manually_execute", (*ManuallyExecute)(nil),
+ },
+ },
+)
+
+func (inst *Instruction) ProgramID() ag_solanago.PublicKey {
+ return ProgramID
+}
+
+func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) {
+ return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts()
+}
+
+func (inst *Instruction) Data() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil {
+ return nil, fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return buf.Bytes(), nil
+}
+
+func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
+ return encoder.Encode(inst.Impl, option)
+}
+
+func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
+ return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
+}
+
+func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
+ err := encoder.WriteBytes(inst.TypeID.Bytes(), false)
+ if err != nil {
+ return fmt.Errorf("unable to write variant type: %w", err)
+ }
+ return encoder.Encode(inst.Impl)
+}
+
+func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) {
+ inst, err := DecodeInstruction(accounts, data)
+ if err != nil {
+ return nil, err
+ }
+ return inst, nil
+}
+
+func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) {
+ inst := new(Instruction)
+ if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil {
+ return nil, fmt.Errorf("unable to decode instruction: %w", err)
+ }
+ if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok {
+ err := v.SetAccounts(accounts)
+ if err != nil {
+ return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
+ }
+ }
+ return inst, nil
+}
diff --git a/chains/solana/gobindings/ccip_router/testing_utils.go b/chains/solana/gobindings/ccip_router/testing_utils.go
new file mode 100644
index 000000000..aaecdf49b
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/testing_utils.go
@@ -0,0 +1,20 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ "bytes"
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+func encodeT(data interface{}, buf *bytes.Buffer) error {
+ if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil {
+ return fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return nil
+}
+
+func decodeT(dst interface{}, data []byte) error {
+ return ag_binary.NewBorshDecoder(data).Decode(dst)
+}
diff --git a/chains/solana/gobindings/ccip_router/types.go b/chains/solana/gobindings/ccip_router/types.go
new file mode 100644
index 000000000..f10f1277b
--- /dev/null
+++ b/chains/solana/gobindings/ccip_router/types.go
@@ -0,0 +1,2074 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package ccip_router
+
+import (
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type CommitInput struct {
+ PriceUpdates PriceUpdates
+ MerkleRoot MerkleRoot
+}
+
+func (obj CommitInput) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `PriceUpdates` param:
+ err = encoder.Encode(obj.PriceUpdates)
+ if err != nil {
+ return err
+ }
+ // Serialize `MerkleRoot` param:
+ err = encoder.Encode(obj.MerkleRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *CommitInput) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `PriceUpdates`:
+ err = decoder.Decode(&obj.PriceUpdates)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MerkleRoot`:
+ err = decoder.Decode(&obj.MerkleRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type PriceUpdates struct {
+ TokenPriceUpdates []TokenPriceUpdate
+ GasPriceUpdates []GasPriceUpdate
+}
+
+func (obj PriceUpdates) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `TokenPriceUpdates` param:
+ err = encoder.Encode(obj.TokenPriceUpdates)
+ if err != nil {
+ return err
+ }
+ // Serialize `GasPriceUpdates` param:
+ err = encoder.Encode(obj.GasPriceUpdates)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *PriceUpdates) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `TokenPriceUpdates`:
+ err = decoder.Decode(&obj.TokenPriceUpdates)
+ if err != nil {
+ return err
+ }
+ // Deserialize `GasPriceUpdates`:
+ err = decoder.Decode(&obj.GasPriceUpdates)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type TokenPriceUpdate struct {
+ SourceToken ag_solanago.PublicKey
+ UsdPerToken [28]uint8
+}
+
+func (obj TokenPriceUpdate) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `SourceToken` param:
+ err = encoder.Encode(obj.SourceToken)
+ if err != nil {
+ return err
+ }
+ // Serialize `UsdPerToken` param:
+ err = encoder.Encode(obj.UsdPerToken)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *TokenPriceUpdate) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `SourceToken`:
+ err = decoder.Decode(&obj.SourceToken)
+ if err != nil {
+ return err
+ }
+ // Deserialize `UsdPerToken`:
+ err = decoder.Decode(&obj.UsdPerToken)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type GasPriceUpdate struct {
+ DestChainSelector uint64
+ UsdPerUnitGas [28]uint8
+}
+
+func (obj GasPriceUpdate) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `DestChainSelector` param:
+ err = encoder.Encode(obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `UsdPerUnitGas` param:
+ err = encoder.Encode(obj.UsdPerUnitGas)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *GasPriceUpdate) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `DestChainSelector`:
+ err = decoder.Decode(&obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `UsdPerUnitGas`:
+ err = decoder.Decode(&obj.UsdPerUnitGas)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type MerkleRoot struct {
+ SourceChainSelector uint64
+ OnRampAddress []byte
+ MinSeqNr uint64
+ MaxSeqNr uint64
+ MerkleRoot [32]uint8
+}
+
+func (obj MerkleRoot) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `SourceChainSelector` param:
+ err = encoder.Encode(obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `OnRampAddress` param:
+ err = encoder.Encode(obj.OnRampAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `MinSeqNr` param:
+ err = encoder.Encode(obj.MinSeqNr)
+ if err != nil {
+ return err
+ }
+ // Serialize `MaxSeqNr` param:
+ err = encoder.Encode(obj.MaxSeqNr)
+ if err != nil {
+ return err
+ }
+ // Serialize `MerkleRoot` param:
+ err = encoder.Encode(obj.MerkleRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *MerkleRoot) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `SourceChainSelector`:
+ err = decoder.Decode(&obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OnRampAddress`:
+ err = decoder.Decode(&obj.OnRampAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MinSeqNr`:
+ err = decoder.Decode(&obj.MinSeqNr)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MaxSeqNr`:
+ err = decoder.Decode(&obj.MaxSeqNr)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MerkleRoot`:
+ err = decoder.Decode(&obj.MerkleRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type Solana2AnyMessage struct {
+ Receiver []byte
+ Data []byte
+ TokenAmounts []SolanaTokenAmount
+ FeeToken ag_solanago.PublicKey
+ ExtraArgs ExtraArgsInput
+ TokenIndexes []byte
+}
+
+func (obj Solana2AnyMessage) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Receiver` param:
+ err = encoder.Encode(obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenAmounts` param:
+ err = encoder.Encode(obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ // Serialize `FeeToken` param:
+ err = encoder.Encode(obj.FeeToken)
+ if err != nil {
+ return err
+ }
+ // Serialize `ExtraArgs` param:
+ err = encoder.Encode(obj.ExtraArgs)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenIndexes` param:
+ err = encoder.Encode(obj.TokenIndexes)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Solana2AnyMessage) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Receiver`:
+ err = decoder.Decode(&obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenAmounts`:
+ err = decoder.Decode(&obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ // Deserialize `FeeToken`:
+ err = decoder.Decode(&obj.FeeToken)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ExtraArgs`:
+ err = decoder.Decode(&obj.ExtraArgs)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenIndexes`:
+ err = decoder.Decode(&obj.TokenIndexes)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type SolanaTokenAmount struct {
+ Token ag_solanago.PublicKey
+ Amount uint64
+}
+
+func (obj SolanaTokenAmount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Token` param:
+ err = encoder.Encode(obj.Token)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *SolanaTokenAmount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Token`:
+ err = decoder.Decode(&obj.Token)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ExtraArgsInput struct {
+ GasLimit *ag_binary.Uint128 `bin:"optional"`
+ AllowOutOfOrderExecution *bool `bin:"optional"`
+}
+
+func (obj ExtraArgsInput) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `GasLimit` param (optional):
+ {
+ if obj.GasLimit == nil {
+ err = encoder.WriteBool(false)
+ if err != nil {
+ return err
+ }
+ } else {
+ err = encoder.WriteBool(true)
+ if err != nil {
+ return err
+ }
+ err = encoder.Encode(obj.GasLimit)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ // Serialize `AllowOutOfOrderExecution` param (optional):
+ {
+ if obj.AllowOutOfOrderExecution == nil {
+ err = encoder.WriteBool(false)
+ if err != nil {
+ return err
+ }
+ } else {
+ err = encoder.WriteBool(true)
+ if err != nil {
+ return err
+ }
+ err = encoder.Encode(obj.AllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (obj *ExtraArgsInput) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `GasLimit` (optional):
+ {
+ ok, err := decoder.ReadBool()
+ if err != nil {
+ return err
+ }
+ if ok {
+ err = decoder.Decode(&obj.GasLimit)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ // Deserialize `AllowOutOfOrderExecution` (optional):
+ {
+ ok, err := decoder.ReadBool()
+ if err != nil {
+ return err
+ }
+ if ok {
+ err = decoder.Decode(&obj.AllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+type Any2SolanaMessage struct {
+ MessageId [32]uint8
+ SourceChainSelector uint64
+ Sender []byte
+ Data []byte
+ TokenAmounts []SolanaTokenAmount
+}
+
+func (obj Any2SolanaMessage) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MessageId` param:
+ err = encoder.Encode(obj.MessageId)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourceChainSelector` param:
+ err = encoder.Encode(obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Sender` param:
+ err = encoder.Encode(obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenAmounts` param:
+ err = encoder.Encode(obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Any2SolanaMessage) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MessageId`:
+ err = decoder.Decode(&obj.MessageId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourceChainSelector`:
+ err = decoder.Decode(&obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Sender`:
+ err = decoder.Decode(&obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenAmounts`:
+ err = decoder.Decode(&obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type RampMessageHeader struct {
+ MessageId [32]uint8
+ SourceChainSelector uint64
+ DestChainSelector uint64
+ SequenceNumber uint64
+ Nonce uint64
+}
+
+func (obj RampMessageHeader) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MessageId` param:
+ err = encoder.Encode(obj.MessageId)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourceChainSelector` param:
+ err = encoder.Encode(obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestChainSelector` param:
+ err = encoder.Encode(obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `SequenceNumber` param:
+ err = encoder.Encode(obj.SequenceNumber)
+ if err != nil {
+ return err
+ }
+ // Serialize `Nonce` param:
+ err = encoder.Encode(obj.Nonce)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *RampMessageHeader) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MessageId`:
+ err = decoder.Decode(&obj.MessageId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourceChainSelector`:
+ err = decoder.Decode(&obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestChainSelector`:
+ err = decoder.Decode(&obj.DestChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SequenceNumber`:
+ err = decoder.Decode(&obj.SequenceNumber)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Nonce`:
+ err = decoder.Decode(&obj.Nonce)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ExecutionReportSingleChain struct {
+ SourceChainSelector uint64
+ Message Any2SolanaRampMessage
+ OffchainTokenData [][]byte
+ Root [32]uint8
+ Proofs [][32]uint8
+ TokenIndexes []byte
+}
+
+func (obj ExecutionReportSingleChain) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `SourceChainSelector` param:
+ err = encoder.Encode(obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Message` param:
+ err = encoder.Encode(obj.Message)
+ if err != nil {
+ return err
+ }
+ // Serialize `OffchainTokenData` param:
+ err = encoder.Encode(obj.OffchainTokenData)
+ if err != nil {
+ return err
+ }
+ // Serialize `Root` param:
+ err = encoder.Encode(obj.Root)
+ if err != nil {
+ return err
+ }
+ // Serialize `Proofs` param:
+ err = encoder.Encode(obj.Proofs)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenIndexes` param:
+ err = encoder.Encode(obj.TokenIndexes)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ExecutionReportSingleChain) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `SourceChainSelector`:
+ err = decoder.Decode(&obj.SourceChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Message`:
+ err = decoder.Decode(&obj.Message)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OffchainTokenData`:
+ err = decoder.Decode(&obj.OffchainTokenData)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Root`:
+ err = decoder.Decode(&obj.Root)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Proofs`:
+ err = decoder.Decode(&obj.Proofs)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenIndexes`:
+ err = decoder.Decode(&obj.TokenIndexes)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type SolanaAccountMeta struct {
+ Pubkey ag_solanago.PublicKey
+ IsWritable bool
+}
+
+func (obj SolanaAccountMeta) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Pubkey` param:
+ err = encoder.Encode(obj.Pubkey)
+ if err != nil {
+ return err
+ }
+ // Serialize `IsWritable` param:
+ err = encoder.Encode(obj.IsWritable)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *SolanaAccountMeta) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Pubkey`:
+ err = decoder.Decode(&obj.Pubkey)
+ if err != nil {
+ return err
+ }
+ // Deserialize `IsWritable`:
+ err = decoder.Decode(&obj.IsWritable)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type SolanaExtraArgs struct {
+ ComputeUnits uint32
+ Accounts []SolanaAccountMeta
+}
+
+func (obj SolanaExtraArgs) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ComputeUnits` param:
+ err = encoder.Encode(obj.ComputeUnits)
+ if err != nil {
+ return err
+ }
+ // Serialize `Accounts` param:
+ err = encoder.Encode(obj.Accounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *SolanaExtraArgs) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ComputeUnits`:
+ err = decoder.Decode(&obj.ComputeUnits)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Accounts`:
+ err = decoder.Decode(&obj.Accounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type EvmExtraArgs struct {
+ GasLimit ag_binary.Uint128
+ AllowOutOfOrderExecution bool
+}
+
+func (obj EvmExtraArgs) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `GasLimit` param:
+ err = encoder.Encode(obj.GasLimit)
+ if err != nil {
+ return err
+ }
+ // Serialize `AllowOutOfOrderExecution` param:
+ err = encoder.Encode(obj.AllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *EvmExtraArgs) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `GasLimit`:
+ err = decoder.Decode(&obj.GasLimit)
+ if err != nil {
+ return err
+ }
+ // Deserialize `AllowOutOfOrderExecution`:
+ err = decoder.Decode(&obj.AllowOutOfOrderExecution)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type Any2SolanaRampMessage struct {
+ Header RampMessageHeader
+ Sender []byte
+ Data []byte
+ Receiver ag_solanago.PublicKey
+ TokenAmounts []Any2SolanaTokenTransfer
+ ExtraArgs SolanaExtraArgs
+}
+
+func (obj Any2SolanaRampMessage) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Header` param:
+ err = encoder.Encode(obj.Header)
+ if err != nil {
+ return err
+ }
+ // Serialize `Sender` param:
+ err = encoder.Encode(obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ // Serialize `Receiver` param:
+ err = encoder.Encode(obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenAmounts` param:
+ err = encoder.Encode(obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ // Serialize `ExtraArgs` param:
+ err = encoder.Encode(obj.ExtraArgs)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Any2SolanaRampMessage) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Header`:
+ err = decoder.Decode(&obj.Header)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Sender`:
+ err = decoder.Decode(&obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Receiver`:
+ err = decoder.Decode(&obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenAmounts`:
+ err = decoder.Decode(&obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ExtraArgs`:
+ err = decoder.Decode(&obj.ExtraArgs)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type Solana2AnyRampMessage struct {
+ Header RampMessageHeader
+ Sender ag_solanago.PublicKey
+ Data []byte
+ Receiver []byte
+ ExtraArgs EvmExtraArgs
+ FeeToken ag_solanago.PublicKey
+ TokenAmounts []Solana2AnyTokenTransfer
+}
+
+func (obj Solana2AnyRampMessage) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Header` param:
+ err = encoder.Encode(obj.Header)
+ if err != nil {
+ return err
+ }
+ // Serialize `Sender` param:
+ err = encoder.Encode(obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ // Serialize `Receiver` param:
+ err = encoder.Encode(obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Serialize `ExtraArgs` param:
+ err = encoder.Encode(obj.ExtraArgs)
+ if err != nil {
+ return err
+ }
+ // Serialize `FeeToken` param:
+ err = encoder.Encode(obj.FeeToken)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenAmounts` param:
+ err = encoder.Encode(obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Solana2AnyRampMessage) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Header`:
+ err = decoder.Decode(&obj.Header)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Sender`:
+ err = decoder.Decode(&obj.Sender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Receiver`:
+ err = decoder.Decode(&obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ExtraArgs`:
+ err = decoder.Decode(&obj.ExtraArgs)
+ if err != nil {
+ return err
+ }
+ // Deserialize `FeeToken`:
+ err = decoder.Decode(&obj.FeeToken)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenAmounts`:
+ err = decoder.Decode(&obj.TokenAmounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type Solana2AnyTokenTransfer struct {
+ SourcePoolAddress ag_solanago.PublicKey
+ DestTokenAddress []byte
+ ExtraData []byte
+ Amount [32]uint8
+ DestExecData []byte
+}
+
+func (obj Solana2AnyTokenTransfer) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `SourcePoolAddress` param:
+ err = encoder.Encode(obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestTokenAddress` param:
+ err = encoder.Encode(obj.DestTokenAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `ExtraData` param:
+ err = encoder.Encode(obj.ExtraData)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestExecData` param:
+ err = encoder.Encode(obj.DestExecData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Solana2AnyTokenTransfer) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `SourcePoolAddress`:
+ err = decoder.Decode(&obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestTokenAddress`:
+ err = decoder.Decode(&obj.DestTokenAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ExtraData`:
+ err = decoder.Decode(&obj.ExtraData)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestExecData`:
+ err = decoder.Decode(&obj.DestExecData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type Any2SolanaTokenTransfer struct {
+ SourcePoolAddress []byte
+ DestTokenAddress ag_solanago.PublicKey
+ DestGasAmount uint32
+ ExtraData []byte
+ Amount [32]uint8
+}
+
+func (obj Any2SolanaTokenTransfer) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `SourcePoolAddress` param:
+ err = encoder.Encode(obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestTokenAddress` param:
+ err = encoder.Encode(obj.DestTokenAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestGasAmount` param:
+ err = encoder.Encode(obj.DestGasAmount)
+ if err != nil {
+ return err
+ }
+ // Serialize `ExtraData` param:
+ err = encoder.Encode(obj.ExtraData)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Any2SolanaTokenTransfer) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `SourcePoolAddress`:
+ err = decoder.Decode(&obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestTokenAddress`:
+ err = decoder.Decode(&obj.DestTokenAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestGasAmount`:
+ err = decoder.Decode(&obj.DestGasAmount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ExtraData`:
+ err = decoder.Decode(&obj.ExtraData)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type LockOrBurnInV1 struct {
+ Receiver []byte
+ RemoteChainSelector uint64
+ OriginalSender ag_solanago.PublicKey
+ Amount uint64
+ LocalToken ag_solanago.PublicKey
+}
+
+func (obj LockOrBurnInV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Receiver` param:
+ err = encoder.Encode(obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Serialize `RemoteChainSelector` param:
+ err = encoder.Encode(obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `OriginalSender` param:
+ err = encoder.Encode(obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Serialize `LocalToken` param:
+ err = encoder.Encode(obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *LockOrBurnInV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Receiver`:
+ err = decoder.Decode(&obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Deserialize `RemoteChainSelector`:
+ err = decoder.Decode(&obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OriginalSender`:
+ err = decoder.Decode(&obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LocalToken`:
+ err = decoder.Decode(&obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ReleaseOrMintInV1 struct {
+ OriginalSender []byte
+ RemoteChainSelector uint64
+ Receiver ag_solanago.PublicKey
+ Amount [32]uint8
+ LocalToken ag_solanago.PublicKey
+
+ // @dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the
+ // expected pool address for the given remoteChainSelector.
+ SourcePoolAddress []byte
+ SourcePoolData []byte
+
+ // @dev WARNING: offchainTokenData is untrusted data.
+ OffchainTokenData []byte
+}
+
+func (obj ReleaseOrMintInV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `OriginalSender` param:
+ err = encoder.Encode(obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Serialize `RemoteChainSelector` param:
+ err = encoder.Encode(obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Receiver` param:
+ err = encoder.Encode(obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Serialize `LocalToken` param:
+ err = encoder.Encode(obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourcePoolAddress` param:
+ err = encoder.Encode(obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourcePoolData` param:
+ err = encoder.Encode(obj.SourcePoolData)
+ if err != nil {
+ return err
+ }
+ // Serialize `OffchainTokenData` param:
+ err = encoder.Encode(obj.OffchainTokenData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ReleaseOrMintInV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `OriginalSender`:
+ err = decoder.Decode(&obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `RemoteChainSelector`:
+ err = decoder.Decode(&obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Receiver`:
+ err = decoder.Decode(&obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LocalToken`:
+ err = decoder.Decode(&obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourcePoolAddress`:
+ err = decoder.Decode(&obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourcePoolData`:
+ err = decoder.Decode(&obj.SourcePoolData)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OffchainTokenData`:
+ err = decoder.Decode(&obj.OffchainTokenData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type LockOrBurnOutV1 struct {
+ DestTokenAddress []byte
+ DestPoolData []byte
+}
+
+func (obj LockOrBurnOutV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `DestTokenAddress` param:
+ err = encoder.Encode(obj.DestTokenAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestPoolData` param:
+ err = encoder.Encode(obj.DestPoolData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *LockOrBurnOutV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `DestTokenAddress`:
+ err = decoder.Decode(&obj.DestTokenAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestPoolData`:
+ err = decoder.Decode(&obj.DestPoolData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ReleaseOrMintOutV1 struct {
+ DestinationAmount uint64
+}
+
+func (obj ReleaseOrMintOutV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `DestinationAmount` param:
+ err = encoder.Encode(obj.DestinationAmount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ReleaseOrMintOutV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `DestinationAmount`:
+ err = decoder.Decode(&obj.DestinationAmount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ReportContext struct {
+ ByteWords [3][32]uint8
+}
+
+func (obj ReportContext) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ByteWords` param:
+ err = encoder.Encode(obj.ByteWords)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ReportContext) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ByteWords`:
+ err = decoder.Decode(&obj.ByteWords)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type Ocr3Config struct {
+ PluginType uint8
+ ConfigInfo Ocr3ConfigInfo
+ Signers [16][20]uint8
+ Transmitters [16][32]uint8
+}
+
+func (obj Ocr3Config) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `PluginType` param:
+ err = encoder.Encode(obj.PluginType)
+ if err != nil {
+ return err
+ }
+ // Serialize `ConfigInfo` param:
+ err = encoder.Encode(obj.ConfigInfo)
+ if err != nil {
+ return err
+ }
+ // Serialize `Signers` param:
+ err = encoder.Encode(obj.Signers)
+ if err != nil {
+ return err
+ }
+ // Serialize `Transmitters` param:
+ err = encoder.Encode(obj.Transmitters)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Ocr3Config) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `PluginType`:
+ err = decoder.Decode(&obj.PluginType)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ConfigInfo`:
+ err = decoder.Decode(&obj.ConfigInfo)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Signers`:
+ err = decoder.Decode(&obj.Signers)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Transmitters`:
+ err = decoder.Decode(&obj.Transmitters)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type Ocr3ConfigInfo struct {
+ ConfigDigest [32]uint8
+ F uint8
+ N uint8
+ IsSignatureVerificationEnabled uint8
+}
+
+func (obj Ocr3ConfigInfo) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ConfigDigest` param:
+ err = encoder.Encode(obj.ConfigDigest)
+ if err != nil {
+ return err
+ }
+ // Serialize `F` param:
+ err = encoder.Encode(obj.F)
+ if err != nil {
+ return err
+ }
+ // Serialize `N` param:
+ err = encoder.Encode(obj.N)
+ if err != nil {
+ return err
+ }
+ // Serialize `IsSignatureVerificationEnabled` param:
+ err = encoder.Encode(obj.IsSignatureVerificationEnabled)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Ocr3ConfigInfo) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ConfigDigest`:
+ err = decoder.Decode(&obj.ConfigDigest)
+ if err != nil {
+ return err
+ }
+ // Deserialize `F`:
+ err = decoder.Decode(&obj.F)
+ if err != nil {
+ return err
+ }
+ // Deserialize `N`:
+ err = decoder.Decode(&obj.N)
+ if err != nil {
+ return err
+ }
+ // Deserialize `IsSignatureVerificationEnabled`:
+ err = decoder.Decode(&obj.IsSignatureVerificationEnabled)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type SourceChainConfig struct {
+ IsEnabled bool
+ OnRamp []byte
+}
+
+func (obj SourceChainConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `IsEnabled` param:
+ err = encoder.Encode(obj.IsEnabled)
+ if err != nil {
+ return err
+ }
+ // Serialize `OnRamp` param:
+ err = encoder.Encode(obj.OnRamp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *SourceChainConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `IsEnabled`:
+ err = decoder.Decode(&obj.IsEnabled)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OnRamp`:
+ err = decoder.Decode(&obj.OnRamp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type SourceChainState struct {
+ MinSeqNr uint64
+}
+
+func (obj SourceChainState) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MinSeqNr` param:
+ err = encoder.Encode(obj.MinSeqNr)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *SourceChainState) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MinSeqNr`:
+ err = decoder.Decode(&obj.MinSeqNr)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type SourceChain struct {
+ State SourceChainState
+ Config SourceChainConfig
+}
+
+func (obj SourceChain) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `State` param:
+ err = encoder.Encode(obj.State)
+ if err != nil {
+ return err
+ }
+ // Serialize `Config` param:
+ err = encoder.Encode(obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *SourceChain) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `State`:
+ err = decoder.Decode(&obj.State)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Config`:
+ err = decoder.Decode(&obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type DestChainState struct {
+ SequenceNumber uint64
+ UsdPerUnitGas TimestampedPackedU224
+}
+
+func (obj DestChainState) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `SequenceNumber` param:
+ err = encoder.Encode(obj.SequenceNumber)
+ if err != nil {
+ return err
+ }
+ // Serialize `UsdPerUnitGas` param:
+ err = encoder.Encode(obj.UsdPerUnitGas)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *DestChainState) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `SequenceNumber`:
+ err = decoder.Decode(&obj.SequenceNumber)
+ if err != nil {
+ return err
+ }
+ // Deserialize `UsdPerUnitGas`:
+ err = decoder.Decode(&obj.UsdPerUnitGas)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type DestChainConfig struct {
+ IsEnabled bool
+ MaxNumberOfTokensPerMsg uint16
+ MaxDataBytes uint32
+ MaxPerMsgGasLimit uint32
+ DestGasOverhead uint32
+ DestGasPerPayloadByte uint16
+ DestDataAvailabilityOverheadGas uint32
+ DestGasPerDataAvailabilityByte uint16
+ DestDataAvailabilityMultiplierBps uint16
+ DefaultTokenFeeUsdcents uint16
+ DefaultTokenDestGasOverhead uint32
+ DefaultTxGasLimit uint32
+ GasMultiplierWeiPerEth uint64
+ NetworkFeeUsdcents uint32
+ GasPriceStalenessThreshold uint32
+ EnforceOutOfOrder bool
+ ChainFamilySelector [4]uint8
+}
+
+func (obj DestChainConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `IsEnabled` param:
+ err = encoder.Encode(obj.IsEnabled)
+ if err != nil {
+ return err
+ }
+ // Serialize `MaxNumberOfTokensPerMsg` param:
+ err = encoder.Encode(obj.MaxNumberOfTokensPerMsg)
+ if err != nil {
+ return err
+ }
+ // Serialize `MaxDataBytes` param:
+ err = encoder.Encode(obj.MaxDataBytes)
+ if err != nil {
+ return err
+ }
+ // Serialize `MaxPerMsgGasLimit` param:
+ err = encoder.Encode(obj.MaxPerMsgGasLimit)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestGasOverhead` param:
+ err = encoder.Encode(obj.DestGasOverhead)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestGasPerPayloadByte` param:
+ err = encoder.Encode(obj.DestGasPerPayloadByte)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestDataAvailabilityOverheadGas` param:
+ err = encoder.Encode(obj.DestDataAvailabilityOverheadGas)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestGasPerDataAvailabilityByte` param:
+ err = encoder.Encode(obj.DestGasPerDataAvailabilityByte)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestDataAvailabilityMultiplierBps` param:
+ err = encoder.Encode(obj.DestDataAvailabilityMultiplierBps)
+ if err != nil {
+ return err
+ }
+ // Serialize `DefaultTokenFeeUsdcents` param:
+ err = encoder.Encode(obj.DefaultTokenFeeUsdcents)
+ if err != nil {
+ return err
+ }
+ // Serialize `DefaultTokenDestGasOverhead` param:
+ err = encoder.Encode(obj.DefaultTokenDestGasOverhead)
+ if err != nil {
+ return err
+ }
+ // Serialize `DefaultTxGasLimit` param:
+ err = encoder.Encode(obj.DefaultTxGasLimit)
+ if err != nil {
+ return err
+ }
+ // Serialize `GasMultiplierWeiPerEth` param:
+ err = encoder.Encode(obj.GasMultiplierWeiPerEth)
+ if err != nil {
+ return err
+ }
+ // Serialize `NetworkFeeUsdcents` param:
+ err = encoder.Encode(obj.NetworkFeeUsdcents)
+ if err != nil {
+ return err
+ }
+ // Serialize `GasPriceStalenessThreshold` param:
+ err = encoder.Encode(obj.GasPriceStalenessThreshold)
+ if err != nil {
+ return err
+ }
+ // Serialize `EnforceOutOfOrder` param:
+ err = encoder.Encode(obj.EnforceOutOfOrder)
+ if err != nil {
+ return err
+ }
+ // Serialize `ChainFamilySelector` param:
+ err = encoder.Encode(obj.ChainFamilySelector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *DestChainConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `IsEnabled`:
+ err = decoder.Decode(&obj.IsEnabled)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MaxNumberOfTokensPerMsg`:
+ err = decoder.Decode(&obj.MaxNumberOfTokensPerMsg)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MaxDataBytes`:
+ err = decoder.Decode(&obj.MaxDataBytes)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MaxPerMsgGasLimit`:
+ err = decoder.Decode(&obj.MaxPerMsgGasLimit)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestGasOverhead`:
+ err = decoder.Decode(&obj.DestGasOverhead)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestGasPerPayloadByte`:
+ err = decoder.Decode(&obj.DestGasPerPayloadByte)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestDataAvailabilityOverheadGas`:
+ err = decoder.Decode(&obj.DestDataAvailabilityOverheadGas)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestGasPerDataAvailabilityByte`:
+ err = decoder.Decode(&obj.DestGasPerDataAvailabilityByte)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestDataAvailabilityMultiplierBps`:
+ err = decoder.Decode(&obj.DestDataAvailabilityMultiplierBps)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DefaultTokenFeeUsdcents`:
+ err = decoder.Decode(&obj.DefaultTokenFeeUsdcents)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DefaultTokenDestGasOverhead`:
+ err = decoder.Decode(&obj.DefaultTokenDestGasOverhead)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DefaultTxGasLimit`:
+ err = decoder.Decode(&obj.DefaultTxGasLimit)
+ if err != nil {
+ return err
+ }
+ // Deserialize `GasMultiplierWeiPerEth`:
+ err = decoder.Decode(&obj.GasMultiplierWeiPerEth)
+ if err != nil {
+ return err
+ }
+ // Deserialize `NetworkFeeUsdcents`:
+ err = decoder.Decode(&obj.NetworkFeeUsdcents)
+ if err != nil {
+ return err
+ }
+ // Deserialize `GasPriceStalenessThreshold`:
+ err = decoder.Decode(&obj.GasPriceStalenessThreshold)
+ if err != nil {
+ return err
+ }
+ // Deserialize `EnforceOutOfOrder`:
+ err = decoder.Decode(&obj.EnforceOutOfOrder)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ChainFamilySelector`:
+ err = decoder.Decode(&obj.ChainFamilySelector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type DestChain struct {
+ State DestChainState
+ Config DestChainConfig
+}
+
+func (obj DestChain) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `State` param:
+ err = encoder.Encode(obj.State)
+ if err != nil {
+ return err
+ }
+ // Serialize `Config` param:
+ err = encoder.Encode(obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *DestChain) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `State`:
+ err = decoder.Decode(&obj.State)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Config`:
+ err = decoder.Decode(&obj.Config)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type TokenBilling struct {
+ MinFeeUsdcents uint32
+ MaxFeeUsdcents uint32
+ DeciBps uint16
+ DestGasOverhead uint32
+ DestBytesOverhead uint32
+ IsEnabled bool
+}
+
+func (obj TokenBilling) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MinFeeUsdcents` param:
+ err = encoder.Encode(obj.MinFeeUsdcents)
+ if err != nil {
+ return err
+ }
+ // Serialize `MaxFeeUsdcents` param:
+ err = encoder.Encode(obj.MaxFeeUsdcents)
+ if err != nil {
+ return err
+ }
+ // Serialize `DeciBps` param:
+ err = encoder.Encode(obj.DeciBps)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestGasOverhead` param:
+ err = encoder.Encode(obj.DestGasOverhead)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestBytesOverhead` param:
+ err = encoder.Encode(obj.DestBytesOverhead)
+ if err != nil {
+ return err
+ }
+ // Serialize `IsEnabled` param:
+ err = encoder.Encode(obj.IsEnabled)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *TokenBilling) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MinFeeUsdcents`:
+ err = decoder.Decode(&obj.MinFeeUsdcents)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MaxFeeUsdcents`:
+ err = decoder.Decode(&obj.MaxFeeUsdcents)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DeciBps`:
+ err = decoder.Decode(&obj.DeciBps)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestGasOverhead`:
+ err = decoder.Decode(&obj.DestGasOverhead)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestBytesOverhead`:
+ err = decoder.Decode(&obj.DestBytesOverhead)
+ if err != nil {
+ return err
+ }
+ // Deserialize `IsEnabled`:
+ err = decoder.Decode(&obj.IsEnabled)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type RateLimitTokenBucket struct {
+ Tokens ag_binary.Uint128
+ LastUpdated uint32
+ IsEnabled bool
+ Capacity ag_binary.Uint128
+ Rate ag_binary.Uint128
+}
+
+func (obj RateLimitTokenBucket) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Tokens` param:
+ err = encoder.Encode(obj.Tokens)
+ if err != nil {
+ return err
+ }
+ // Serialize `LastUpdated` param:
+ err = encoder.Encode(obj.LastUpdated)
+ if err != nil {
+ return err
+ }
+ // Serialize `IsEnabled` param:
+ err = encoder.Encode(obj.IsEnabled)
+ if err != nil {
+ return err
+ }
+ // Serialize `Capacity` param:
+ err = encoder.Encode(obj.Capacity)
+ if err != nil {
+ return err
+ }
+ // Serialize `Rate` param:
+ err = encoder.Encode(obj.Rate)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *RateLimitTokenBucket) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Tokens`:
+ err = decoder.Decode(&obj.Tokens)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LastUpdated`:
+ err = decoder.Decode(&obj.LastUpdated)
+ if err != nil {
+ return err
+ }
+ // Deserialize `IsEnabled`:
+ err = decoder.Decode(&obj.IsEnabled)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Capacity`:
+ err = decoder.Decode(&obj.Capacity)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Rate`:
+ err = decoder.Decode(&obj.Rate)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type BillingTokenConfig struct {
+ Enabled bool
+ Mint ag_solanago.PublicKey
+ UsdPerToken TimestampedPackedU224
+ PremiumMultiplierWeiPerEth uint64
+}
+
+func (obj BillingTokenConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Enabled` param:
+ err = encoder.Encode(obj.Enabled)
+ if err != nil {
+ return err
+ }
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Serialize `UsdPerToken` param:
+ err = encoder.Encode(obj.UsdPerToken)
+ if err != nil {
+ return err
+ }
+ // Serialize `PremiumMultiplierWeiPerEth` param:
+ err = encoder.Encode(obj.PremiumMultiplierWeiPerEth)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *BillingTokenConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Enabled`:
+ err = decoder.Decode(&obj.Enabled)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Deserialize `UsdPerToken`:
+ err = decoder.Decode(&obj.UsdPerToken)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PremiumMultiplierWeiPerEth`:
+ err = decoder.Decode(&obj.PremiumMultiplierWeiPerEth)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type TimestampedPackedU224 struct {
+ Value [28]uint8
+ Timestamp int64
+}
+
+func (obj TimestampedPackedU224) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Value` param:
+ err = encoder.Encode(obj.Value)
+ if err != nil {
+ return err
+ }
+ // Serialize `Timestamp` param:
+ err = encoder.Encode(obj.Timestamp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *TimestampedPackedU224) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Value`:
+ err = decoder.Decode(&obj.Value)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Timestamp`:
+ err = decoder.Decode(&obj.Timestamp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type OcrPluginType ag_binary.BorshEnum
+
+const (
+ Commit_OcrPluginType OcrPluginType = iota
+ Execution_OcrPluginType
+)
+
+func (value OcrPluginType) String() string {
+ switch value {
+ case Commit_OcrPluginType:
+ return "Commit"
+ case Execution_OcrPluginType:
+ return "Execution"
+ default:
+ return ""
+ }
+}
+
+type MerkleError ag_binary.BorshEnum
+
+const (
+ InvalidProof_MerkleError MerkleError = iota
+)
+
+func (value MerkleError) String() string {
+ switch value {
+ case InvalidProof_MerkleError:
+ return "InvalidProof"
+ default:
+ return ""
+ }
+}
+
+type MessageExecutionState ag_binary.BorshEnum
+
+const (
+ Untouched_MessageExecutionState MessageExecutionState = iota
+ InProgress_MessageExecutionState
+ Success_MessageExecutionState
+ Failure_MessageExecutionState
+)
+
+func (value MessageExecutionState) String() string {
+ switch value {
+ case Untouched_MessageExecutionState:
+ return "Untouched"
+ case InProgress_MessageExecutionState:
+ return "InProgress"
+ case Success_MessageExecutionState:
+ return "Success"
+ case Failure_MessageExecutionState:
+ return "Failure"
+ default:
+ return ""
+ }
+}
+
+type CcipRouterError ag_binary.BorshEnum
+
+const (
+ InvalidSequenceInterval_CcipRouterError CcipRouterError = iota
+ RootNotCommitted_CcipRouterError
+ ExistingMerkleRoot_CcipRouterError
+ Unauthorized_CcipRouterError
+ InvalidInputs_CcipRouterError
+ UnsupportedSourceChainSelector_CcipRouterError
+ UnsupportedDestinationChainSelector_CcipRouterError
+ InvalidProof_CcipRouterError
+ InvalidMessage_CcipRouterError
+ ReachedMaxSequenceNumber_CcipRouterError
+ ManualExecutionNotAllowed_CcipRouterError
+ InvalidInputsTokenIndices_CcipRouterError
+ InvalidInputsPoolAccounts_CcipRouterError
+ InvalidInputsTokenAccounts_CcipRouterError
+ InvalidInputsConfigAccounts_CcipRouterError
+ InvalidInputsTokenAdminRegistryAccounts_CcipRouterError
+ InvalidInputsLookupTableAccounts_CcipRouterError
+ InvalidInputsTokenAmount_CcipRouterError
+ OfframpReleaseMintBalanceMismatch_CcipRouterError
+ OfframpInvalidDataLength_CcipRouterError
+ StaleCommitReport_CcipRouterError
+ DestinationChainDisabled_CcipRouterError
+ FeeTokenDisabled_CcipRouterError
+)
+
+func (value CcipRouterError) String() string {
+ switch value {
+ case InvalidSequenceInterval_CcipRouterError:
+ return "InvalidSequenceInterval"
+ case RootNotCommitted_CcipRouterError:
+ return "RootNotCommitted"
+ case ExistingMerkleRoot_CcipRouterError:
+ return "ExistingMerkleRoot"
+ case Unauthorized_CcipRouterError:
+ return "Unauthorized"
+ case InvalidInputs_CcipRouterError:
+ return "InvalidInputs"
+ case UnsupportedSourceChainSelector_CcipRouterError:
+ return "UnsupportedSourceChainSelector"
+ case UnsupportedDestinationChainSelector_CcipRouterError:
+ return "UnsupportedDestinationChainSelector"
+ case InvalidProof_CcipRouterError:
+ return "InvalidProof"
+ case InvalidMessage_CcipRouterError:
+ return "InvalidMessage"
+ case ReachedMaxSequenceNumber_CcipRouterError:
+ return "ReachedMaxSequenceNumber"
+ case ManualExecutionNotAllowed_CcipRouterError:
+ return "ManualExecutionNotAllowed"
+ case InvalidInputsTokenIndices_CcipRouterError:
+ return "InvalidInputsTokenIndices"
+ case InvalidInputsPoolAccounts_CcipRouterError:
+ return "InvalidInputsPoolAccounts"
+ case InvalidInputsTokenAccounts_CcipRouterError:
+ return "InvalidInputsTokenAccounts"
+ case InvalidInputsConfigAccounts_CcipRouterError:
+ return "InvalidInputsConfigAccounts"
+ case InvalidInputsTokenAdminRegistryAccounts_CcipRouterError:
+ return "InvalidInputsTokenAdminRegistryAccounts"
+ case InvalidInputsLookupTableAccounts_CcipRouterError:
+ return "InvalidInputsLookupTableAccounts"
+ case InvalidInputsTokenAmount_CcipRouterError:
+ return "InvalidInputsTokenAmount"
+ case OfframpReleaseMintBalanceMismatch_CcipRouterError:
+ return "OfframpReleaseMintBalanceMismatch"
+ case OfframpInvalidDataLength_CcipRouterError:
+ return "OfframpInvalidDataLength"
+ case StaleCommitReport_CcipRouterError:
+ return "StaleCommitReport"
+ case DestinationChainDisabled_CcipRouterError:
+ return "DestinationChainDisabled"
+ case FeeTokenDisabled_CcipRouterError:
+ return "FeeTokenDisabled"
+ default:
+ return ""
+ }
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/AccountMut.go b/chains/solana/gobindings/external_program_cpi_stub/AccountMut.go
new file mode 100644
index 000000000..ae2b2e8a7
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/AccountMut.go
@@ -0,0 +1,136 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AccountMut is the `accountMut` instruction.
+type AccountMut struct {
+
+ // [0] = [WRITE] u8Value
+ //
+ // [1] = [SIGNER] stubCaller
+ //
+ // [2] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAccountMutInstructionBuilder creates a new `AccountMut` instruction builder.
+func NewAccountMutInstructionBuilder() *AccountMut {
+ nd := &AccountMut{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetU8ValueAccount sets the "u8Value" account.
+func (inst *AccountMut) SetU8ValueAccount(u8Value ag_solanago.PublicKey) *AccountMut {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(u8Value).WRITE()
+ return inst
+}
+
+// GetU8ValueAccount gets the "u8Value" account.
+func (inst *AccountMut) GetU8ValueAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetStubCallerAccount sets the "stubCaller" account.
+func (inst *AccountMut) SetStubCallerAccount(stubCaller ag_solanago.PublicKey) *AccountMut {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(stubCaller).SIGNER()
+ return inst
+}
+
+// GetStubCallerAccount gets the "stubCaller" account.
+func (inst *AccountMut) GetStubCallerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *AccountMut) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *AccountMut {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *AccountMut) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst AccountMut) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AccountMut,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AccountMut) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AccountMut) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.U8Value is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.StubCaller is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AccountMut) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AccountMut")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" u8Value", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" stubCaller", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj AccountMut) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *AccountMut) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewAccountMutInstruction declares a new AccountMut instruction with the provided parameters and accounts.
+func NewAccountMutInstruction(
+ // Accounts:
+ u8Value ag_solanago.PublicKey,
+ stubCaller ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *AccountMut {
+ return NewAccountMutInstructionBuilder().
+ SetU8ValueAccount(u8Value).
+ SetStubCallerAccount(stubCaller).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/AccountMut_test.go b/chains/solana/gobindings/external_program_cpi_stub/AccountMut_test.go
new file mode 100644
index 000000000..cdb9efae6
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/AccountMut_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AccountMut(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AccountMut"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AccountMut)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AccountMut)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/AccountRead.go b/chains/solana/gobindings/external_program_cpi_stub/AccountRead.go
new file mode 100644
index 000000000..e11cbf568
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/AccountRead.go
@@ -0,0 +1,98 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AccountRead is the `accountRead` instruction.
+type AccountRead struct {
+
+ // [0] = [] u8Value
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAccountReadInstructionBuilder creates a new `AccountRead` instruction builder.
+func NewAccountReadInstructionBuilder() *AccountRead {
+ nd := &AccountRead{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1),
+ }
+ return nd
+}
+
+// SetU8ValueAccount sets the "u8Value" account.
+func (inst *AccountRead) SetU8ValueAccount(u8Value ag_solanago.PublicKey) *AccountRead {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(u8Value)
+ return inst
+}
+
+// GetU8ValueAccount gets the "u8Value" account.
+func (inst *AccountRead) GetU8ValueAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+func (inst AccountRead) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AccountRead,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AccountRead) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AccountRead) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.U8Value is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AccountRead) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AccountRead")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=1]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("u8Value", inst.AccountMetaSlice[0]))
+ })
+ })
+ })
+}
+
+func (obj AccountRead) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *AccountRead) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewAccountReadInstruction declares a new AccountRead instruction with the provided parameters and accounts.
+func NewAccountReadInstruction(
+ // Accounts:
+ u8Value ag_solanago.PublicKey) *AccountRead {
+ return NewAccountReadInstructionBuilder().
+ SetU8ValueAccount(u8Value)
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/AccountRead_test.go b/chains/solana/gobindings/external_program_cpi_stub/AccountRead_test.go
new file mode 100644
index 000000000..2110c796c
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/AccountRead_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AccountRead(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AccountRead"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AccountRead)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AccountRead)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/Empty.go b/chains/solana/gobindings/external_program_cpi_stub/Empty.go
new file mode 100644
index 000000000..2e886ebc2
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/Empty.go
@@ -0,0 +1,76 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Empty is the `empty` instruction.
+type Empty struct {
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewEmptyInstructionBuilder creates a new `Empty` instruction builder.
+func NewEmptyInstructionBuilder() *Empty {
+ nd := &Empty{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 0),
+ }
+ return nd
+}
+
+func (inst Empty) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Empty,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Empty) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Empty) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ }
+ return nil
+}
+
+func (inst *Empty) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Empty")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=0]").ParentFunc(func(accountsBranch ag_treeout.Branches) {})
+ })
+ })
+}
+
+func (obj Empty) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *Empty) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewEmptyInstruction declares a new Empty instruction with the provided parameters and accounts.
+func NewEmptyInstruction() *Empty {
+ return NewEmptyInstructionBuilder()
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/Empty_test.go b/chains/solana/gobindings/external_program_cpi_stub/Empty_test.go
new file mode 100644
index 000000000..2cd605b80
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/Empty_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Empty(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Empty"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Empty)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Empty)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/Initialize.go b/chains/solana/gobindings/external_program_cpi_stub/Initialize.go
new file mode 100644
index 000000000..38c3ec18b
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/Initialize.go
@@ -0,0 +1,136 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Initialize is the `initialize` instruction.
+type Initialize struct {
+
+ // [0] = [WRITE] u8Value
+ //
+ // [1] = [WRITE, SIGNER] stubCaller
+ //
+ // [2] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitializeInstructionBuilder creates a new `Initialize` instruction builder.
+func NewInitializeInstructionBuilder() *Initialize {
+ nd := &Initialize{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetU8ValueAccount sets the "u8Value" account.
+func (inst *Initialize) SetU8ValueAccount(u8Value ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(u8Value).WRITE()
+ return inst
+}
+
+// GetU8ValueAccount gets the "u8Value" account.
+func (inst *Initialize) GetU8ValueAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetStubCallerAccount sets the "stubCaller" account.
+func (inst *Initialize) SetStubCallerAccount(stubCaller ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(stubCaller).WRITE().SIGNER()
+ return inst
+}
+
+// GetStubCallerAccount gets the "stubCaller" account.
+func (inst *Initialize) GetStubCallerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *Initialize) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *Initialize) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst Initialize) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Initialize,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Initialize) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Initialize) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.U8Value is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.StubCaller is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Initialize) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Initialize")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" u8Value", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" stubCaller", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj Initialize) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewInitializeInstruction declares a new Initialize instruction with the provided parameters and accounts.
+func NewInitializeInstruction(
+ // Accounts:
+ u8Value ag_solanago.PublicKey,
+ stubCaller ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *Initialize {
+ return NewInitializeInstructionBuilder().
+ SetU8ValueAccount(u8Value).
+ SetStubCallerAccount(stubCaller).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/Initialize_test.go b/chains/solana/gobindings/external_program_cpi_stub/Initialize_test.go
new file mode 100644
index 000000000..2bf46b8a8
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/Initialize_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Initialize(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Initialize"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Initialize)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Initialize)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/StructInstructionData.go b/chains/solana/gobindings/external_program_cpi_stub/StructInstructionData.go
new file mode 100644
index 000000000..d630ee566
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/StructInstructionData.go
@@ -0,0 +1,107 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// StructInstructionData is the `structInstructionData` instruction.
+type StructInstructionData struct {
+ Data *Value
+
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewStructInstructionDataInstructionBuilder creates a new `StructInstructionData` instruction builder.
+func NewStructInstructionDataInstructionBuilder() *StructInstructionData {
+ nd := &StructInstructionData{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 0),
+ }
+ return nd
+}
+
+// SetData sets the "data" parameter.
+func (inst *StructInstructionData) SetData(data Value) *StructInstructionData {
+ inst.Data = &data
+ return inst
+}
+
+func (inst StructInstructionData) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_StructInstructionData,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst StructInstructionData) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *StructInstructionData) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Data == nil {
+ return errors.New("Data parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ }
+ return nil
+}
+
+func (inst *StructInstructionData) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("StructInstructionData")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Data", *inst.Data))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=0]").ParentFunc(func(accountsBranch ag_treeout.Branches) {})
+ })
+ })
+}
+
+func (obj StructInstructionData) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *StructInstructionData) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewStructInstructionDataInstruction declares a new StructInstructionData instruction with the provided parameters and accounts.
+func NewStructInstructionDataInstruction(
+ // Parameters:
+ data Value) *StructInstructionData {
+ return NewStructInstructionDataInstructionBuilder().
+ SetData(data)
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/StructInstructionData_test.go b/chains/solana/gobindings/external_program_cpi_stub/StructInstructionData_test.go
new file mode 100644
index 000000000..1d58083f6
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/StructInstructionData_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_StructInstructionData(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("StructInstructionData"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(StructInstructionData)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(StructInstructionData)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/U8InstructionData.go b/chains/solana/gobindings/external_program_cpi_stub/U8InstructionData.go
new file mode 100644
index 000000000..c48accedf
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/U8InstructionData.go
@@ -0,0 +1,107 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// U8InstructionData is the `u8InstructionData` instruction.
+type U8InstructionData struct {
+ Data *uint8
+
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewU8InstructionDataInstructionBuilder creates a new `U8InstructionData` instruction builder.
+func NewU8InstructionDataInstructionBuilder() *U8InstructionData {
+ nd := &U8InstructionData{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 0),
+ }
+ return nd
+}
+
+// SetData sets the "data" parameter.
+func (inst *U8InstructionData) SetData(data uint8) *U8InstructionData {
+ inst.Data = &data
+ return inst
+}
+
+func (inst U8InstructionData) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_U8InstructionData,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst U8InstructionData) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *U8InstructionData) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Data == nil {
+ return errors.New("Data parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ }
+ return nil
+}
+
+func (inst *U8InstructionData) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("U8InstructionData")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Data", *inst.Data))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=0]").ParentFunc(func(accountsBranch ag_treeout.Branches) {})
+ })
+ })
+}
+
+func (obj U8InstructionData) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *U8InstructionData) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewU8InstructionDataInstruction declares a new U8InstructionData instruction with the provided parameters and accounts.
+func NewU8InstructionDataInstruction(
+ // Parameters:
+ data uint8) *U8InstructionData {
+ return NewU8InstructionDataInstructionBuilder().
+ SetData(data)
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/U8InstructionData_test.go b/chains/solana/gobindings/external_program_cpi_stub/U8InstructionData_test.go
new file mode 100644
index 000000000..aee65b776
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/U8InstructionData_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_U8InstructionData(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("U8InstructionData"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(U8InstructionData)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(U8InstructionData)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/accounts.go b/chains/solana/gobindings/external_program_cpi_stub/accounts.go
new file mode 100644
index 000000000..90ab0ba6e
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/accounts.go
@@ -0,0 +1,50 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+type Value struct {
+ Value uint8
+}
+
+var ValueDiscriminator = [8]byte{135, 158, 244, 117, 72, 203, 24, 194}
+
+func (obj Value) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ValueDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Value` param:
+ err = encoder.Encode(obj.Value)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Value) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ValueDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[135 158 244 117 72 203 24 194]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Value`:
+ err = decoder.Decode(&obj.Value)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/instructions.go b/chains/solana/gobindings/external_program_cpi_stub/instructions.go
new file mode 100644
index 000000000..46fcc83bc
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/instructions.go
@@ -0,0 +1,152 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "bytes"
+ "fmt"
+ ag_spew "github.com/davecgh/go-spew/spew"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_text "github.com/gagliardetto/solana-go/text"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+var ProgramID ag_solanago.PublicKey
+
+func SetProgramID(pubkey ag_solanago.PublicKey) {
+ ProgramID = pubkey
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+}
+
+const ProgramName = "ExternalProgramCpiStub"
+
+func init() {
+ if !ProgramID.IsZero() {
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+ }
+}
+
+var (
+ Instruction_Initialize = ag_binary.TypeID([8]byte{175, 175, 109, 31, 13, 152, 155, 237})
+
+ Instruction_Empty = ag_binary.TypeID([8]byte{214, 44, 4, 247, 12, 41, 217, 110})
+
+ Instruction_U8InstructionData = ag_binary.TypeID([8]byte{17, 175, 156, 253, 91, 173, 26, 228})
+
+ Instruction_StructInstructionData = ag_binary.TypeID([8]byte{132, 84, 80, 47, 117, 198, 94, 67})
+
+ Instruction_AccountRead = ag_binary.TypeID([8]byte{79, 53, 80, 124, 182, 81, 206, 85})
+
+ Instruction_AccountMut = ag_binary.TypeID([8]byte{12, 2, 137, 19, 22, 235, 144, 70})
+)
+
+// InstructionIDToName returns the name of the instruction given its ID.
+func InstructionIDToName(id ag_binary.TypeID) string {
+ switch id {
+ case Instruction_Initialize:
+ return "Initialize"
+ case Instruction_Empty:
+ return "Empty"
+ case Instruction_U8InstructionData:
+ return "U8InstructionData"
+ case Instruction_StructInstructionData:
+ return "StructInstructionData"
+ case Instruction_AccountRead:
+ return "AccountRead"
+ case Instruction_AccountMut:
+ return "AccountMut"
+ default:
+ return ""
+ }
+}
+
+type Instruction struct {
+ ag_binary.BaseVariant
+}
+
+func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) {
+ if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok {
+ enToTree.EncodeToTree(parent)
+ } else {
+ parent.Child(ag_spew.Sdump(inst))
+ }
+}
+
+var InstructionImplDef = ag_binary.NewVariantDefinition(
+ ag_binary.AnchorTypeIDEncoding,
+ []ag_binary.VariantType{
+ {
+ "initialize", (*Initialize)(nil),
+ },
+ {
+ "empty", (*Empty)(nil),
+ },
+ {
+ "u8_instruction_data", (*U8InstructionData)(nil),
+ },
+ {
+ "struct_instruction_data", (*StructInstructionData)(nil),
+ },
+ {
+ "account_read", (*AccountRead)(nil),
+ },
+ {
+ "account_mut", (*AccountMut)(nil),
+ },
+ },
+)
+
+func (inst *Instruction) ProgramID() ag_solanago.PublicKey {
+ return ProgramID
+}
+
+func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) {
+ return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts()
+}
+
+func (inst *Instruction) Data() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil {
+ return nil, fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return buf.Bytes(), nil
+}
+
+func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
+ return encoder.Encode(inst.Impl, option)
+}
+
+func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
+ return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
+}
+
+func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
+ err := encoder.WriteBytes(inst.TypeID.Bytes(), false)
+ if err != nil {
+ return fmt.Errorf("unable to write variant type: %w", err)
+ }
+ return encoder.Encode(inst.Impl)
+}
+
+func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) {
+ inst, err := DecodeInstruction(accounts, data)
+ if err != nil {
+ return nil, err
+ }
+ return inst, nil
+}
+
+func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) {
+ inst := new(Instruction)
+ if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil {
+ return nil, fmt.Errorf("unable to decode instruction: %w", err)
+ }
+ if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok {
+ err := v.SetAccounts(accounts)
+ if err != nil {
+ return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
+ }
+ }
+ return inst, nil
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/testing_utils.go b/chains/solana/gobindings/external_program_cpi_stub/testing_utils.go
new file mode 100644
index 000000000..3ee8a3103
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/testing_utils.go
@@ -0,0 +1,20 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
+
+import (
+ "bytes"
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+func encodeT(data interface{}, buf *bytes.Buffer) error {
+ if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil {
+ return fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return nil
+}
+
+func decodeT(dst interface{}, data []byte) error {
+ return ag_binary.NewBorshDecoder(data).Decode(dst)
+}
diff --git a/chains/solana/gobindings/external_program_cpi_stub/types.go b/chains/solana/gobindings/external_program_cpi_stub/types.go
new file mode 100644
index 000000000..a07a35546
--- /dev/null
+++ b/chains/solana/gobindings/external_program_cpi_stub/types.go
@@ -0,0 +1,3 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package external_program_cpi_stub
diff --git a/chains/solana/gobindings/mcm/AcceptOwnership.go b/chains/solana/gobindings/mcm/AcceptOwnership.go
new file mode 100644
index 000000000..295421634
--- /dev/null
+++ b/chains/solana/gobindings/mcm/AcceptOwnership.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AcceptOwnership is the `acceptOwnership` instruction.
+type AcceptOwnership struct {
+ MultisigName *[32]uint8
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAcceptOwnershipInstructionBuilder creates a new `AcceptOwnership` instruction builder.
+func NewAcceptOwnershipInstructionBuilder() *AcceptOwnership {
+ nd := &AcceptOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *AcceptOwnership) SetMultisigName(multisigName [32]uint8) *AcceptOwnership {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *AcceptOwnership) SetConfigAccount(config ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *AcceptOwnership) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AcceptOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AcceptOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst AcceptOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AcceptOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AcceptOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AcceptOwnership) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AcceptOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AcceptOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj AcceptOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *AcceptOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewAcceptOwnershipInstruction declares a new AcceptOwnership instruction with the provided parameters and accounts.
+func NewAcceptOwnershipInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *AcceptOwnership {
+ return NewAcceptOwnershipInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/mcm/AcceptOwnership_test.go b/chains/solana/gobindings/mcm/AcceptOwnership_test.go
new file mode 100644
index 000000000..88b190119
--- /dev/null
+++ b/chains/solana/gobindings/mcm/AcceptOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AcceptOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AcceptOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AcceptOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AcceptOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/AppendSignatures.go b/chains/solana/gobindings/mcm/AppendSignatures.go
new file mode 100644
index 000000000..923f5e3bc
--- /dev/null
+++ b/chains/solana/gobindings/mcm/AppendSignatures.go
@@ -0,0 +1,215 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AppendSignatures is the `appendSignatures` instruction.
+type AppendSignatures struct {
+ MultisigName *[32]uint8
+ Root *[32]uint8
+ ValidUntil *uint32
+ SignaturesBatch *[]Signature
+
+ // [0] = [WRITE] signatures
+ //
+ // [1] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAppendSignaturesInstructionBuilder creates a new `AppendSignatures` instruction builder.
+func NewAppendSignaturesInstructionBuilder() *AppendSignatures {
+ nd := &AppendSignatures{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *AppendSignatures) SetMultisigName(multisigName [32]uint8) *AppendSignatures {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetRoot sets the "root" parameter.
+func (inst *AppendSignatures) SetRoot(root [32]uint8) *AppendSignatures {
+ inst.Root = &root
+ return inst
+}
+
+// SetValidUntil sets the "validUntil" parameter.
+func (inst *AppendSignatures) SetValidUntil(validUntil uint32) *AppendSignatures {
+ inst.ValidUntil = &validUntil
+ return inst
+}
+
+// SetSignaturesBatch sets the "signaturesBatch" parameter.
+func (inst *AppendSignatures) SetSignaturesBatch(signaturesBatch []Signature) *AppendSignatures {
+ inst.SignaturesBatch = &signaturesBatch
+ return inst
+}
+
+// SetSignaturesAccount sets the "signatures" account.
+func (inst *AppendSignatures) SetSignaturesAccount(signatures ag_solanago.PublicKey) *AppendSignatures {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(signatures).WRITE()
+ return inst
+}
+
+// GetSignaturesAccount gets the "signatures" account.
+func (inst *AppendSignatures) GetSignaturesAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AppendSignatures) SetAuthorityAccount(authority ag_solanago.PublicKey) *AppendSignatures {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AppendSignatures) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst AppendSignatures) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AppendSignatures,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AppendSignatures) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AppendSignatures) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.Root == nil {
+ return errors.New("Root parameter is not set")
+ }
+ if inst.ValidUntil == nil {
+ return errors.New("ValidUntil parameter is not set")
+ }
+ if inst.SignaturesBatch == nil {
+ return errors.New("SignaturesBatch parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Signatures is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AppendSignatures) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AppendSignatures")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=4]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param(" Root", *inst.Root))
+ paramsBranch.Child(ag_format.Param(" ValidUntil", *inst.ValidUntil))
+ paramsBranch.Child(ag_format.Param("SignaturesBatch", *inst.SignaturesBatch))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("signatures", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj AppendSignatures) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `Root` param:
+ err = encoder.Encode(obj.Root)
+ if err != nil {
+ return err
+ }
+ // Serialize `ValidUntil` param:
+ err = encoder.Encode(obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ // Serialize `SignaturesBatch` param:
+ err = encoder.Encode(obj.SignaturesBatch)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *AppendSignatures) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Root`:
+ err = decoder.Decode(&obj.Root)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ValidUntil`:
+ err = decoder.Decode(&obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SignaturesBatch`:
+ err = decoder.Decode(&obj.SignaturesBatch)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewAppendSignaturesInstruction declares a new AppendSignatures instruction with the provided parameters and accounts.
+func NewAppendSignaturesInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ root [32]uint8,
+ validUntil uint32,
+ signaturesBatch []Signature,
+ // Accounts:
+ signatures ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *AppendSignatures {
+ return NewAppendSignaturesInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetRoot(root).
+ SetValidUntil(validUntil).
+ SetSignaturesBatch(signaturesBatch).
+ SetSignaturesAccount(signatures).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/mcm/AppendSignatures_test.go b/chains/solana/gobindings/mcm/AppendSignatures_test.go
new file mode 100644
index 000000000..ac2cd848b
--- /dev/null
+++ b/chains/solana/gobindings/mcm/AppendSignatures_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AppendSignatures(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AppendSignatures"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AppendSignatures)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AppendSignatures)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/AppendSigners.go b/chains/solana/gobindings/mcm/AppendSigners.go
new file mode 100644
index 000000000..0bb023156
--- /dev/null
+++ b/chains/solana/gobindings/mcm/AppendSigners.go
@@ -0,0 +1,188 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AppendSigners is the `appendSigners` instruction.
+type AppendSigners struct {
+ MultisigName *[32]uint8
+ SignersBatch *[][20]uint8
+
+ // [0] = [] multisigConfig
+ //
+ // [1] = [WRITE] configSigners
+ //
+ // [2] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAppendSignersInstructionBuilder creates a new `AppendSigners` instruction builder.
+func NewAppendSignersInstructionBuilder() *AppendSigners {
+ nd := &AppendSigners{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *AppendSigners) SetMultisigName(multisigName [32]uint8) *AppendSigners {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetSignersBatch sets the "signersBatch" parameter.
+func (inst *AppendSigners) SetSignersBatch(signersBatch [][20]uint8) *AppendSigners {
+ inst.SignersBatch = &signersBatch
+ return inst
+}
+
+// SetMultisigConfigAccount sets the "multisigConfig" account.
+func (inst *AppendSigners) SetMultisigConfigAccount(multisigConfig ag_solanago.PublicKey) *AppendSigners {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(multisigConfig)
+ return inst
+}
+
+// GetMultisigConfigAccount gets the "multisigConfig" account.
+func (inst *AppendSigners) GetMultisigConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigSignersAccount sets the "configSigners" account.
+func (inst *AppendSigners) SetConfigSignersAccount(configSigners ag_solanago.PublicKey) *AppendSigners {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(configSigners).WRITE()
+ return inst
+}
+
+// GetConfigSignersAccount gets the "configSigners" account.
+func (inst *AppendSigners) GetConfigSignersAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AppendSigners) SetAuthorityAccount(authority ag_solanago.PublicKey) *AppendSigners {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AppendSigners) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst AppendSigners) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AppendSigners,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AppendSigners) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AppendSigners) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.SignersBatch == nil {
+ return errors.New("SignersBatch parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.MultisigConfig is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ConfigSigners is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AppendSigners) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AppendSigners")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param("SignersBatch", *inst.SignersBatch))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("multisigConfig", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" configSigners", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj AppendSigners) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `SignersBatch` param:
+ err = encoder.Encode(obj.SignersBatch)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *AppendSigners) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SignersBatch`:
+ err = decoder.Decode(&obj.SignersBatch)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewAppendSignersInstruction declares a new AppendSigners instruction with the provided parameters and accounts.
+func NewAppendSignersInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ signersBatch [][20]uint8,
+ // Accounts:
+ multisigConfig ag_solanago.PublicKey,
+ configSigners ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *AppendSigners {
+ return NewAppendSignersInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetSignersBatch(signersBatch).
+ SetMultisigConfigAccount(multisigConfig).
+ SetConfigSignersAccount(configSigners).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/mcm/AppendSigners_test.go b/chains/solana/gobindings/mcm/AppendSigners_test.go
new file mode 100644
index 000000000..a7e98c5b2
--- /dev/null
+++ b/chains/solana/gobindings/mcm/AppendSigners_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AppendSigners(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AppendSigners"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AppendSigners)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AppendSigners)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/ClearSignatures.go b/chains/solana/gobindings/mcm/ClearSignatures.go
new file mode 100644
index 000000000..b511c24cf
--- /dev/null
+++ b/chains/solana/gobindings/mcm/ClearSignatures.go
@@ -0,0 +1,192 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// ClearSignatures is the `clearSignatures` instruction.
+type ClearSignatures struct {
+ MultisigName *[32]uint8
+ Root *[32]uint8
+ ValidUntil *uint32
+
+ // [0] = [WRITE] signatures
+ //
+ // [1] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewClearSignaturesInstructionBuilder creates a new `ClearSignatures` instruction builder.
+func NewClearSignaturesInstructionBuilder() *ClearSignatures {
+ nd := &ClearSignatures{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *ClearSignatures) SetMultisigName(multisigName [32]uint8) *ClearSignatures {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetRoot sets the "root" parameter.
+func (inst *ClearSignatures) SetRoot(root [32]uint8) *ClearSignatures {
+ inst.Root = &root
+ return inst
+}
+
+// SetValidUntil sets the "validUntil" parameter.
+func (inst *ClearSignatures) SetValidUntil(validUntil uint32) *ClearSignatures {
+ inst.ValidUntil = &validUntil
+ return inst
+}
+
+// SetSignaturesAccount sets the "signatures" account.
+func (inst *ClearSignatures) SetSignaturesAccount(signatures ag_solanago.PublicKey) *ClearSignatures {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(signatures).WRITE()
+ return inst
+}
+
+// GetSignaturesAccount gets the "signatures" account.
+func (inst *ClearSignatures) GetSignaturesAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *ClearSignatures) SetAuthorityAccount(authority ag_solanago.PublicKey) *ClearSignatures {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *ClearSignatures) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst ClearSignatures) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_ClearSignatures,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst ClearSignatures) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *ClearSignatures) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.Root == nil {
+ return errors.New("Root parameter is not set")
+ }
+ if inst.ValidUntil == nil {
+ return errors.New("ValidUntil parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Signatures is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *ClearSignatures) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("ClearSignatures")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=3]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param(" Root", *inst.Root))
+ paramsBranch.Child(ag_format.Param(" ValidUntil", *inst.ValidUntil))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("signatures", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj ClearSignatures) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `Root` param:
+ err = encoder.Encode(obj.Root)
+ if err != nil {
+ return err
+ }
+ // Serialize `ValidUntil` param:
+ err = encoder.Encode(obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *ClearSignatures) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Root`:
+ err = decoder.Decode(&obj.Root)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ValidUntil`:
+ err = decoder.Decode(&obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewClearSignaturesInstruction declares a new ClearSignatures instruction with the provided parameters and accounts.
+func NewClearSignaturesInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ root [32]uint8,
+ validUntil uint32,
+ // Accounts:
+ signatures ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *ClearSignatures {
+ return NewClearSignaturesInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetRoot(root).
+ SetValidUntil(validUntil).
+ SetSignaturesAccount(signatures).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/mcm/ClearSignatures_test.go b/chains/solana/gobindings/mcm/ClearSignatures_test.go
new file mode 100644
index 000000000..5f4a25846
--- /dev/null
+++ b/chains/solana/gobindings/mcm/ClearSignatures_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_ClearSignatures(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("ClearSignatures"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(ClearSignatures)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(ClearSignatures)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/ClearSigners.go b/chains/solana/gobindings/mcm/ClearSigners.go
new file mode 100644
index 000000000..7d20a37e3
--- /dev/null
+++ b/chains/solana/gobindings/mcm/ClearSigners.go
@@ -0,0 +1,165 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// ClearSigners is the `clearSigners` instruction.
+type ClearSigners struct {
+ MultisigName *[32]uint8
+
+ // [0] = [] multisigConfig
+ //
+ // [1] = [WRITE] configSigners
+ //
+ // [2] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewClearSignersInstructionBuilder creates a new `ClearSigners` instruction builder.
+func NewClearSignersInstructionBuilder() *ClearSigners {
+ nd := &ClearSigners{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *ClearSigners) SetMultisigName(multisigName [32]uint8) *ClearSigners {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetMultisigConfigAccount sets the "multisigConfig" account.
+func (inst *ClearSigners) SetMultisigConfigAccount(multisigConfig ag_solanago.PublicKey) *ClearSigners {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(multisigConfig)
+ return inst
+}
+
+// GetMultisigConfigAccount gets the "multisigConfig" account.
+func (inst *ClearSigners) GetMultisigConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigSignersAccount sets the "configSigners" account.
+func (inst *ClearSigners) SetConfigSignersAccount(configSigners ag_solanago.PublicKey) *ClearSigners {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(configSigners).WRITE()
+ return inst
+}
+
+// GetConfigSignersAccount gets the "configSigners" account.
+func (inst *ClearSigners) GetConfigSignersAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *ClearSigners) SetAuthorityAccount(authority ag_solanago.PublicKey) *ClearSigners {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *ClearSigners) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst ClearSigners) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_ClearSigners,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst ClearSigners) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *ClearSigners) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.MultisigConfig is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ConfigSigners is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *ClearSigners) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("ClearSigners")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("multisigConfig", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" configSigners", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj ClearSigners) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *ClearSigners) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewClearSignersInstruction declares a new ClearSigners instruction with the provided parameters and accounts.
+func NewClearSignersInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ // Accounts:
+ multisigConfig ag_solanago.PublicKey,
+ configSigners ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *ClearSigners {
+ return NewClearSignersInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetMultisigConfigAccount(multisigConfig).
+ SetConfigSignersAccount(configSigners).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/mcm/ClearSigners_test.go b/chains/solana/gobindings/mcm/ClearSigners_test.go
new file mode 100644
index 000000000..4a5d97573
--- /dev/null
+++ b/chains/solana/gobindings/mcm/ClearSigners_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_ClearSigners(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("ClearSigners"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(ClearSigners)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(ClearSigners)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/Execute.go b/chains/solana/gobindings/mcm/Execute.go
new file mode 100644
index 000000000..cf265ad60
--- /dev/null
+++ b/chains/solana/gobindings/mcm/Execute.go
@@ -0,0 +1,314 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Execute is the `execute` instruction.
+type Execute struct {
+ MultisigName *[32]uint8
+ ChainId *uint64
+ Nonce *uint64
+ Data *[]byte
+ Proof *[][32]uint8
+
+ // [0] = [WRITE] multisigConfig
+ //
+ // [1] = [] rootMetadata
+ //
+ // [2] = [WRITE] expiringRootAndOpCount
+ //
+ // [3] = [] to
+ //
+ // [4] = [] multisigSigner
+ //
+ // [5] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewExecuteInstructionBuilder creates a new `Execute` instruction builder.
+func NewExecuteInstructionBuilder() *Execute {
+ nd := &Execute{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 6),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *Execute) SetMultisigName(multisigName [32]uint8) *Execute {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetChainId sets the "chainId" parameter.
+func (inst *Execute) SetChainId(chainId uint64) *Execute {
+ inst.ChainId = &chainId
+ return inst
+}
+
+// SetNonce sets the "nonce" parameter.
+func (inst *Execute) SetNonce(nonce uint64) *Execute {
+ inst.Nonce = &nonce
+ return inst
+}
+
+// SetData sets the "data" parameter.
+func (inst *Execute) SetData(data []byte) *Execute {
+ inst.Data = &data
+ return inst
+}
+
+// SetProof sets the "proof" parameter.
+func (inst *Execute) SetProof(proof [][32]uint8) *Execute {
+ inst.Proof = &proof
+ return inst
+}
+
+// SetMultisigConfigAccount sets the "multisigConfig" account.
+func (inst *Execute) SetMultisigConfigAccount(multisigConfig ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(multisigConfig).WRITE()
+ return inst
+}
+
+// GetMultisigConfigAccount gets the "multisigConfig" account.
+func (inst *Execute) GetMultisigConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetRootMetadataAccount sets the "rootMetadata" account.
+func (inst *Execute) SetRootMetadataAccount(rootMetadata ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(rootMetadata)
+ return inst
+}
+
+// GetRootMetadataAccount gets the "rootMetadata" account.
+func (inst *Execute) GetRootMetadataAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetExpiringRootAndOpCountAccount sets the "expiringRootAndOpCount" account.
+func (inst *Execute) SetExpiringRootAndOpCountAccount(expiringRootAndOpCount ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(expiringRootAndOpCount).WRITE()
+ return inst
+}
+
+// GetExpiringRootAndOpCountAccount gets the "expiringRootAndOpCount" account.
+func (inst *Execute) GetExpiringRootAndOpCountAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetToAccount sets the "to" account.
+func (inst *Execute) SetToAccount(to ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(to)
+ return inst
+}
+
+// GetToAccount gets the "to" account.
+func (inst *Execute) GetToAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetMultisigSignerAccount sets the "multisigSigner" account.
+func (inst *Execute) SetMultisigSignerAccount(multisigSigner ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(multisigSigner)
+ return inst
+}
+
+// GetMultisigSignerAccount gets the "multisigSigner" account.
+func (inst *Execute) GetMultisigSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *Execute) SetAuthorityAccount(authority ag_solanago.PublicKey) *Execute {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *Execute) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+func (inst Execute) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Execute,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Execute) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Execute) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.ChainId == nil {
+ return errors.New("ChainId parameter is not set")
+ }
+ if inst.Nonce == nil {
+ return errors.New("Nonce parameter is not set")
+ }
+ if inst.Data == nil {
+ return errors.New("Data parameter is not set")
+ }
+ if inst.Proof == nil {
+ return errors.New("Proof parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.MultisigConfig is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.RootMetadata is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.ExpiringRootAndOpCount is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.To is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.MultisigSigner is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Execute) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Execute")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=5]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param(" ChainId", *inst.ChainId))
+ paramsBranch.Child(ag_format.Param(" Nonce", *inst.Nonce))
+ paramsBranch.Child(ag_format.Param(" Data", *inst.Data))
+ paramsBranch.Child(ag_format.Param(" Proof", *inst.Proof))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=6]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" multisigConfig", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" rootMetadata", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("expiringRootAndOpCount", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" to", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" multisigSigner", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[5]))
+ })
+ })
+ })
+}
+
+func (obj Execute) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `ChainId` param:
+ err = encoder.Encode(obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Serialize `Nonce` param:
+ err = encoder.Encode(obj.Nonce)
+ if err != nil {
+ return err
+ }
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ // Serialize `Proof` param:
+ err = encoder.Encode(obj.Proof)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *Execute) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ChainId`:
+ err = decoder.Decode(&obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Nonce`:
+ err = decoder.Decode(&obj.Nonce)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Proof`:
+ err = decoder.Decode(&obj.Proof)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewExecuteInstruction declares a new Execute instruction with the provided parameters and accounts.
+func NewExecuteInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ chainId uint64,
+ nonce uint64,
+ data []byte,
+ proof [][32]uint8,
+ // Accounts:
+ multisigConfig ag_solanago.PublicKey,
+ rootMetadata ag_solanago.PublicKey,
+ expiringRootAndOpCount ag_solanago.PublicKey,
+ to ag_solanago.PublicKey,
+ multisigSigner ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *Execute {
+ return NewExecuteInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetChainId(chainId).
+ SetNonce(nonce).
+ SetData(data).
+ SetProof(proof).
+ SetMultisigConfigAccount(multisigConfig).
+ SetRootMetadataAccount(rootMetadata).
+ SetExpiringRootAndOpCountAccount(expiringRootAndOpCount).
+ SetToAccount(to).
+ SetMultisigSignerAccount(multisigSigner).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/mcm/Execute_test.go b/chains/solana/gobindings/mcm/Execute_test.go
new file mode 100644
index 000000000..e4cc1637e
--- /dev/null
+++ b/chains/solana/gobindings/mcm/Execute_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Execute(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Execute"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Execute)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Execute)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/FinalizeSignatures.go b/chains/solana/gobindings/mcm/FinalizeSignatures.go
new file mode 100644
index 000000000..9e090b282
--- /dev/null
+++ b/chains/solana/gobindings/mcm/FinalizeSignatures.go
@@ -0,0 +1,192 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// FinalizeSignatures is the `finalizeSignatures` instruction.
+type FinalizeSignatures struct {
+ MultisigName *[32]uint8
+ Root *[32]uint8
+ ValidUntil *uint32
+
+ // [0] = [WRITE] signatures
+ //
+ // [1] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewFinalizeSignaturesInstructionBuilder creates a new `FinalizeSignatures` instruction builder.
+func NewFinalizeSignaturesInstructionBuilder() *FinalizeSignatures {
+ nd := &FinalizeSignatures{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *FinalizeSignatures) SetMultisigName(multisigName [32]uint8) *FinalizeSignatures {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetRoot sets the "root" parameter.
+func (inst *FinalizeSignatures) SetRoot(root [32]uint8) *FinalizeSignatures {
+ inst.Root = &root
+ return inst
+}
+
+// SetValidUntil sets the "validUntil" parameter.
+func (inst *FinalizeSignatures) SetValidUntil(validUntil uint32) *FinalizeSignatures {
+ inst.ValidUntil = &validUntil
+ return inst
+}
+
+// SetSignaturesAccount sets the "signatures" account.
+func (inst *FinalizeSignatures) SetSignaturesAccount(signatures ag_solanago.PublicKey) *FinalizeSignatures {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(signatures).WRITE()
+ return inst
+}
+
+// GetSignaturesAccount gets the "signatures" account.
+func (inst *FinalizeSignatures) GetSignaturesAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *FinalizeSignatures) SetAuthorityAccount(authority ag_solanago.PublicKey) *FinalizeSignatures {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *FinalizeSignatures) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst FinalizeSignatures) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_FinalizeSignatures,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst FinalizeSignatures) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *FinalizeSignatures) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.Root == nil {
+ return errors.New("Root parameter is not set")
+ }
+ if inst.ValidUntil == nil {
+ return errors.New("ValidUntil parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Signatures is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *FinalizeSignatures) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("FinalizeSignatures")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=3]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param(" Root", *inst.Root))
+ paramsBranch.Child(ag_format.Param(" ValidUntil", *inst.ValidUntil))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("signatures", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj FinalizeSignatures) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `Root` param:
+ err = encoder.Encode(obj.Root)
+ if err != nil {
+ return err
+ }
+ // Serialize `ValidUntil` param:
+ err = encoder.Encode(obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *FinalizeSignatures) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Root`:
+ err = decoder.Decode(&obj.Root)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ValidUntil`:
+ err = decoder.Decode(&obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewFinalizeSignaturesInstruction declares a new FinalizeSignatures instruction with the provided parameters and accounts.
+func NewFinalizeSignaturesInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ root [32]uint8,
+ validUntil uint32,
+ // Accounts:
+ signatures ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *FinalizeSignatures {
+ return NewFinalizeSignaturesInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetRoot(root).
+ SetValidUntil(validUntil).
+ SetSignaturesAccount(signatures).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/mcm/FinalizeSignatures_test.go b/chains/solana/gobindings/mcm/FinalizeSignatures_test.go
new file mode 100644
index 000000000..ed59a5d20
--- /dev/null
+++ b/chains/solana/gobindings/mcm/FinalizeSignatures_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_FinalizeSignatures(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("FinalizeSignatures"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(FinalizeSignatures)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(FinalizeSignatures)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/FinalizeSigners.go b/chains/solana/gobindings/mcm/FinalizeSigners.go
new file mode 100644
index 000000000..c6439a697
--- /dev/null
+++ b/chains/solana/gobindings/mcm/FinalizeSigners.go
@@ -0,0 +1,165 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// FinalizeSigners is the `finalizeSigners` instruction.
+type FinalizeSigners struct {
+ MultisigName *[32]uint8
+
+ // [0] = [] multisigConfig
+ //
+ // [1] = [WRITE] configSigners
+ //
+ // [2] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewFinalizeSignersInstructionBuilder creates a new `FinalizeSigners` instruction builder.
+func NewFinalizeSignersInstructionBuilder() *FinalizeSigners {
+ nd := &FinalizeSigners{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *FinalizeSigners) SetMultisigName(multisigName [32]uint8) *FinalizeSigners {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetMultisigConfigAccount sets the "multisigConfig" account.
+func (inst *FinalizeSigners) SetMultisigConfigAccount(multisigConfig ag_solanago.PublicKey) *FinalizeSigners {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(multisigConfig)
+ return inst
+}
+
+// GetMultisigConfigAccount gets the "multisigConfig" account.
+func (inst *FinalizeSigners) GetMultisigConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigSignersAccount sets the "configSigners" account.
+func (inst *FinalizeSigners) SetConfigSignersAccount(configSigners ag_solanago.PublicKey) *FinalizeSigners {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(configSigners).WRITE()
+ return inst
+}
+
+// GetConfigSignersAccount gets the "configSigners" account.
+func (inst *FinalizeSigners) GetConfigSignersAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *FinalizeSigners) SetAuthorityAccount(authority ag_solanago.PublicKey) *FinalizeSigners {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *FinalizeSigners) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst FinalizeSigners) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_FinalizeSigners,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst FinalizeSigners) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *FinalizeSigners) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.MultisigConfig is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ConfigSigners is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *FinalizeSigners) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("FinalizeSigners")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("multisigConfig", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" configSigners", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj FinalizeSigners) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *FinalizeSigners) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewFinalizeSignersInstruction declares a new FinalizeSigners instruction with the provided parameters and accounts.
+func NewFinalizeSignersInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ // Accounts:
+ multisigConfig ag_solanago.PublicKey,
+ configSigners ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *FinalizeSigners {
+ return NewFinalizeSignersInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetMultisigConfigAccount(multisigConfig).
+ SetConfigSignersAccount(configSigners).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/mcm/FinalizeSigners_test.go b/chains/solana/gobindings/mcm/FinalizeSigners_test.go
new file mode 100644
index 000000000..73371bf85
--- /dev/null
+++ b/chains/solana/gobindings/mcm/FinalizeSigners_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_FinalizeSigners(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("FinalizeSigners"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(FinalizeSigners)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(FinalizeSigners)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/InitSignatures.go b/chains/solana/gobindings/mcm/InitSignatures.go
new file mode 100644
index 000000000..418ffebb7
--- /dev/null
+++ b/chains/solana/gobindings/mcm/InitSignatures.go
@@ -0,0 +1,234 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// InitSignatures is the `initSignatures` instruction.
+type InitSignatures struct {
+ MultisigName *[32]uint8
+ Root *[32]uint8
+ ValidUntil *uint32
+ TotalSignatures *uint8
+
+ // [0] = [WRITE] signatures
+ //
+ // [1] = [WRITE, SIGNER] authority
+ //
+ // [2] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitSignaturesInstructionBuilder creates a new `InitSignatures` instruction builder.
+func NewInitSignaturesInstructionBuilder() *InitSignatures {
+ nd := &InitSignatures{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *InitSignatures) SetMultisigName(multisigName [32]uint8) *InitSignatures {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetRoot sets the "root" parameter.
+func (inst *InitSignatures) SetRoot(root [32]uint8) *InitSignatures {
+ inst.Root = &root
+ return inst
+}
+
+// SetValidUntil sets the "validUntil" parameter.
+func (inst *InitSignatures) SetValidUntil(validUntil uint32) *InitSignatures {
+ inst.ValidUntil = &validUntil
+ return inst
+}
+
+// SetTotalSignatures sets the "totalSignatures" parameter.
+func (inst *InitSignatures) SetTotalSignatures(totalSignatures uint8) *InitSignatures {
+ inst.TotalSignatures = &totalSignatures
+ return inst
+}
+
+// SetSignaturesAccount sets the "signatures" account.
+func (inst *InitSignatures) SetSignaturesAccount(signatures ag_solanago.PublicKey) *InitSignatures {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(signatures).WRITE()
+ return inst
+}
+
+// GetSignaturesAccount gets the "signatures" account.
+func (inst *InitSignatures) GetSignaturesAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *InitSignatures) SetAuthorityAccount(authority ag_solanago.PublicKey) *InitSignatures {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *InitSignatures) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *InitSignatures) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *InitSignatures {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *InitSignatures) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst InitSignatures) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_InitSignatures,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst InitSignatures) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *InitSignatures) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.Root == nil {
+ return errors.New("Root parameter is not set")
+ }
+ if inst.ValidUntil == nil {
+ return errors.New("ValidUntil parameter is not set")
+ }
+ if inst.TotalSignatures == nil {
+ return errors.New("TotalSignatures parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Signatures is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *InitSignatures) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("InitSignatures")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=4]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param(" Root", *inst.Root))
+ paramsBranch.Child(ag_format.Param(" ValidUntil", *inst.ValidUntil))
+ paramsBranch.Child(ag_format.Param("TotalSignatures", *inst.TotalSignatures))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" signatures", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj InitSignatures) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `Root` param:
+ err = encoder.Encode(obj.Root)
+ if err != nil {
+ return err
+ }
+ // Serialize `ValidUntil` param:
+ err = encoder.Encode(obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ // Serialize `TotalSignatures` param:
+ err = encoder.Encode(obj.TotalSignatures)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *InitSignatures) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Root`:
+ err = decoder.Decode(&obj.Root)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ValidUntil`:
+ err = decoder.Decode(&obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TotalSignatures`:
+ err = decoder.Decode(&obj.TotalSignatures)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewInitSignaturesInstruction declares a new InitSignatures instruction with the provided parameters and accounts.
+func NewInitSignaturesInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ root [32]uint8,
+ validUntil uint32,
+ totalSignatures uint8,
+ // Accounts:
+ signatures ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *InitSignatures {
+ return NewInitSignaturesInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetRoot(root).
+ SetValidUntil(validUntil).
+ SetTotalSignatures(totalSignatures).
+ SetSignaturesAccount(signatures).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/mcm/InitSignatures_test.go b/chains/solana/gobindings/mcm/InitSignatures_test.go
new file mode 100644
index 000000000..daf49388f
--- /dev/null
+++ b/chains/solana/gobindings/mcm/InitSignatures_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_InitSignatures(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("InitSignatures"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(InitSignatures)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(InitSignatures)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/InitSigners.go b/chains/solana/gobindings/mcm/InitSigners.go
new file mode 100644
index 000000000..bbc72af6a
--- /dev/null
+++ b/chains/solana/gobindings/mcm/InitSigners.go
@@ -0,0 +1,207 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// InitSigners is the `initSigners` instruction.
+type InitSigners struct {
+ MultisigName *[32]uint8
+ TotalSigners *uint8
+
+ // [0] = [] multisigConfig
+ //
+ // [1] = [WRITE] configSigners
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitSignersInstructionBuilder creates a new `InitSigners` instruction builder.
+func NewInitSignersInstructionBuilder() *InitSigners {
+ nd := &InitSigners{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *InitSigners) SetMultisigName(multisigName [32]uint8) *InitSigners {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetTotalSigners sets the "totalSigners" parameter.
+func (inst *InitSigners) SetTotalSigners(totalSigners uint8) *InitSigners {
+ inst.TotalSigners = &totalSigners
+ return inst
+}
+
+// SetMultisigConfigAccount sets the "multisigConfig" account.
+func (inst *InitSigners) SetMultisigConfigAccount(multisigConfig ag_solanago.PublicKey) *InitSigners {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(multisigConfig)
+ return inst
+}
+
+// GetMultisigConfigAccount gets the "multisigConfig" account.
+func (inst *InitSigners) GetMultisigConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigSignersAccount sets the "configSigners" account.
+func (inst *InitSigners) SetConfigSignersAccount(configSigners ag_solanago.PublicKey) *InitSigners {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(configSigners).WRITE()
+ return inst
+}
+
+// GetConfigSignersAccount gets the "configSigners" account.
+func (inst *InitSigners) GetConfigSignersAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *InitSigners) SetAuthorityAccount(authority ag_solanago.PublicKey) *InitSigners {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *InitSigners) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *InitSigners) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *InitSigners {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *InitSigners) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst InitSigners) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_InitSigners,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst InitSigners) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *InitSigners) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.TotalSigners == nil {
+ return errors.New("TotalSigners parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.MultisigConfig is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ConfigSigners is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *InitSigners) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("InitSigners")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param("TotalSigners", *inst.TotalSigners))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("multisigConfig", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" configSigners", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj InitSigners) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `TotalSigners` param:
+ err = encoder.Encode(obj.TotalSigners)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *InitSigners) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TotalSigners`:
+ err = decoder.Decode(&obj.TotalSigners)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewInitSignersInstruction declares a new InitSigners instruction with the provided parameters and accounts.
+func NewInitSignersInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ totalSigners uint8,
+ // Accounts:
+ multisigConfig ag_solanago.PublicKey,
+ configSigners ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *InitSigners {
+ return NewInitSignersInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetTotalSigners(totalSigners).
+ SetMultisigConfigAccount(multisigConfig).
+ SetConfigSignersAccount(configSigners).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/mcm/InitSigners_test.go b/chains/solana/gobindings/mcm/InitSigners_test.go
new file mode 100644
index 000000000..0d388b030
--- /dev/null
+++ b/chains/solana/gobindings/mcm/InitSigners_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_InitSigners(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("InitSigners"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(InitSigners)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(InitSigners)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/Initialize.go b/chains/solana/gobindings/mcm/Initialize.go
new file mode 100644
index 000000000..b61c0095f
--- /dev/null
+++ b/chains/solana/gobindings/mcm/Initialize.go
@@ -0,0 +1,265 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// initialize a new multisig configuration, store the chain_id and multisig_name
+// multisig_name is a unique identifier for the multisig configuration(32 bytes, left-padded)
+type Initialize struct {
+ ChainId *uint64
+ MultisigName *[32]uint8
+
+ // [0] = [WRITE] multisigConfig
+ //
+ // [1] = [WRITE, SIGNER] authority
+ //
+ // [2] = [] systemProgram
+ //
+ // [3] = [] program
+ //
+ // [4] = [] programData
+ //
+ // [5] = [WRITE] rootMetadata
+ //
+ // [6] = [WRITE] expiringRootAndOpCount
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitializeInstructionBuilder creates a new `Initialize` instruction builder.
+func NewInitializeInstructionBuilder() *Initialize {
+ nd := &Initialize{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 7),
+ }
+ return nd
+}
+
+// SetChainId sets the "chainId" parameter.
+func (inst *Initialize) SetChainId(chainId uint64) *Initialize {
+ inst.ChainId = &chainId
+ return inst
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *Initialize) SetMultisigName(multisigName [32]uint8) *Initialize {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetMultisigConfigAccount sets the "multisigConfig" account.
+func (inst *Initialize) SetMultisigConfigAccount(multisigConfig ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(multisigConfig).WRITE()
+ return inst
+}
+
+// GetMultisigConfigAccount gets the "multisigConfig" account.
+func (inst *Initialize) GetMultisigConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *Initialize) SetAuthorityAccount(authority ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *Initialize) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *Initialize) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *Initialize) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetProgramAccount sets the "program" account.
+func (inst *Initialize) SetProgramAccount(program ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(program)
+ return inst
+}
+
+// GetProgramAccount gets the "program" account.
+func (inst *Initialize) GetProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetProgramDataAccount sets the "programData" account.
+func (inst *Initialize) SetProgramDataAccount(programData ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(programData)
+ return inst
+}
+
+// GetProgramDataAccount gets the "programData" account.
+func (inst *Initialize) GetProgramDataAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetRootMetadataAccount sets the "rootMetadata" account.
+func (inst *Initialize) SetRootMetadataAccount(rootMetadata ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(rootMetadata).WRITE()
+ return inst
+}
+
+// GetRootMetadataAccount gets the "rootMetadata" account.
+func (inst *Initialize) GetRootMetadataAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetExpiringRootAndOpCountAccount sets the "expiringRootAndOpCount" account.
+func (inst *Initialize) SetExpiringRootAndOpCountAccount(expiringRootAndOpCount ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(expiringRootAndOpCount).WRITE()
+ return inst
+}
+
+// GetExpiringRootAndOpCountAccount gets the "expiringRootAndOpCount" account.
+func (inst *Initialize) GetExpiringRootAndOpCountAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+func (inst Initialize) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Initialize,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Initialize) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Initialize) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ChainId == nil {
+ return errors.New("ChainId parameter is not set")
+ }
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.MultisigConfig is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Program is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.ProgramData is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.RootMetadata is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.ExpiringRootAndOpCount is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Initialize) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Initialize")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" ChainId", *inst.ChainId))
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=7]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" multisigConfig", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" program", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" programData", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" rootMetadata", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta("expiringRootAndOpCount", inst.AccountMetaSlice[6]))
+ })
+ })
+ })
+}
+
+func (obj Initialize) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ChainId` param:
+ err = encoder.Encode(obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ChainId`:
+ err = decoder.Decode(&obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewInitializeInstruction declares a new Initialize instruction with the provided parameters and accounts.
+func NewInitializeInstruction(
+ // Parameters:
+ chainId uint64,
+ multisigName [32]uint8,
+ // Accounts:
+ multisigConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey,
+ program ag_solanago.PublicKey,
+ programData ag_solanago.PublicKey,
+ rootMetadata ag_solanago.PublicKey,
+ expiringRootAndOpCount ag_solanago.PublicKey) *Initialize {
+ return NewInitializeInstructionBuilder().
+ SetChainId(chainId).
+ SetMultisigName(multisigName).
+ SetMultisigConfigAccount(multisigConfig).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram).
+ SetProgramAccount(program).
+ SetProgramDataAccount(programData).
+ SetRootMetadataAccount(rootMetadata).
+ SetExpiringRootAndOpCountAccount(expiringRootAndOpCount)
+}
diff --git a/chains/solana/gobindings/mcm/Initialize_test.go b/chains/solana/gobindings/mcm/Initialize_test.go
new file mode 100644
index 000000000..5a42d4278
--- /dev/null
+++ b/chains/solana/gobindings/mcm/Initialize_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Initialize(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Initialize"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Initialize)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Initialize)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/SetConfig.go b/chains/solana/gobindings/mcm/SetConfig.go
new file mode 100644
index 000000000..722f72da7
--- /dev/null
+++ b/chains/solana/gobindings/mcm/SetConfig.go
@@ -0,0 +1,276 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// SetConfig is the `setConfig` instruction.
+type SetConfig struct {
+ MultisigName *[32]uint8
+ SignerGroups *[]byte
+ GroupQuorums *[32]uint8
+ GroupParents *[32]uint8
+ ClearRoot *bool
+
+ // [0] = [WRITE] multisigConfig
+ //
+ // [1] = [WRITE] configSigners
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewSetConfigInstructionBuilder creates a new `SetConfig` instruction builder.
+func NewSetConfigInstructionBuilder() *SetConfig {
+ nd := &SetConfig{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *SetConfig) SetMultisigName(multisigName [32]uint8) *SetConfig {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetSignerGroups sets the "signerGroups" parameter.
+func (inst *SetConfig) SetSignerGroups(signerGroups []byte) *SetConfig {
+ inst.SignerGroups = &signerGroups
+ return inst
+}
+
+// SetGroupQuorums sets the "groupQuorums" parameter.
+func (inst *SetConfig) SetGroupQuorums(groupQuorums [32]uint8) *SetConfig {
+ inst.GroupQuorums = &groupQuorums
+ return inst
+}
+
+// SetGroupParents sets the "groupParents" parameter.
+func (inst *SetConfig) SetGroupParents(groupParents [32]uint8) *SetConfig {
+ inst.GroupParents = &groupParents
+ return inst
+}
+
+// SetClearRoot sets the "clearRoot" parameter.
+func (inst *SetConfig) SetClearRoot(clearRoot bool) *SetConfig {
+ inst.ClearRoot = &clearRoot
+ return inst
+}
+
+// SetMultisigConfigAccount sets the "multisigConfig" account.
+func (inst *SetConfig) SetMultisigConfigAccount(multisigConfig ag_solanago.PublicKey) *SetConfig {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(multisigConfig).WRITE()
+ return inst
+}
+
+// GetMultisigConfigAccount gets the "multisigConfig" account.
+func (inst *SetConfig) GetMultisigConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigSignersAccount sets the "configSigners" account.
+func (inst *SetConfig) SetConfigSignersAccount(configSigners ag_solanago.PublicKey) *SetConfig {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(configSigners).WRITE()
+ return inst
+}
+
+// GetConfigSignersAccount gets the "configSigners" account.
+func (inst *SetConfig) GetConfigSignersAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *SetConfig) SetAuthorityAccount(authority ag_solanago.PublicKey) *SetConfig {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *SetConfig) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *SetConfig) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *SetConfig {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *SetConfig) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst SetConfig) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_SetConfig,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst SetConfig) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *SetConfig) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.SignerGroups == nil {
+ return errors.New("SignerGroups parameter is not set")
+ }
+ if inst.GroupQuorums == nil {
+ return errors.New("GroupQuorums parameter is not set")
+ }
+ if inst.GroupParents == nil {
+ return errors.New("GroupParents parameter is not set")
+ }
+ if inst.ClearRoot == nil {
+ return errors.New("ClearRoot parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.MultisigConfig is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ConfigSigners is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *SetConfig) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("SetConfig")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=5]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param("SignerGroups", *inst.SignerGroups))
+ paramsBranch.Child(ag_format.Param("GroupQuorums", *inst.GroupQuorums))
+ paramsBranch.Child(ag_format.Param("GroupParents", *inst.GroupParents))
+ paramsBranch.Child(ag_format.Param(" ClearRoot", *inst.ClearRoot))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("multisigConfig", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" configSigners", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj SetConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `SignerGroups` param:
+ err = encoder.Encode(obj.SignerGroups)
+ if err != nil {
+ return err
+ }
+ // Serialize `GroupQuorums` param:
+ err = encoder.Encode(obj.GroupQuorums)
+ if err != nil {
+ return err
+ }
+ // Serialize `GroupParents` param:
+ err = encoder.Encode(obj.GroupParents)
+ if err != nil {
+ return err
+ }
+ // Serialize `ClearRoot` param:
+ err = encoder.Encode(obj.ClearRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *SetConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SignerGroups`:
+ err = decoder.Decode(&obj.SignerGroups)
+ if err != nil {
+ return err
+ }
+ // Deserialize `GroupQuorums`:
+ err = decoder.Decode(&obj.GroupQuorums)
+ if err != nil {
+ return err
+ }
+ // Deserialize `GroupParents`:
+ err = decoder.Decode(&obj.GroupParents)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ClearRoot`:
+ err = decoder.Decode(&obj.ClearRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewSetConfigInstruction declares a new SetConfig instruction with the provided parameters and accounts.
+func NewSetConfigInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ signerGroups []byte,
+ groupQuorums [32]uint8,
+ groupParents [32]uint8,
+ clearRoot bool,
+ // Accounts:
+ multisigConfig ag_solanago.PublicKey,
+ configSigners ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *SetConfig {
+ return NewSetConfigInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetSignerGroups(signerGroups).
+ SetGroupQuorums(groupQuorums).
+ SetGroupParents(groupParents).
+ SetClearRoot(clearRoot).
+ SetMultisigConfigAccount(multisigConfig).
+ SetConfigSignersAccount(configSigners).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/mcm/SetConfig_test.go b/chains/solana/gobindings/mcm/SetConfig_test.go
new file mode 100644
index 000000000..8c01ca713
--- /dev/null
+++ b/chains/solana/gobindings/mcm/SetConfig_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_SetConfig(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("SetConfig"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(SetConfig)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(SetConfig)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/SetRoot.go b/chains/solana/gobindings/mcm/SetRoot.go
new file mode 100644
index 000000000..48c41dc7a
--- /dev/null
+++ b/chains/solana/gobindings/mcm/SetRoot.go
@@ -0,0 +1,333 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// SetRoot is the `setRoot` instruction.
+type SetRoot struct {
+ MultisigName *[32]uint8
+ Root *[32]uint8
+ ValidUntil *uint32
+ Metadata *RootMetadataInput
+ MetadataProof *[][32]uint8
+
+ // [0] = [WRITE] rootSignatures
+ //
+ // [1] = [WRITE] rootMetadata
+ //
+ // [2] = [WRITE] seenSignedHashes
+ //
+ // [3] = [WRITE] expiringRootAndOpCount
+ //
+ // [4] = [] multisigConfig
+ //
+ // [5] = [WRITE, SIGNER] authority
+ //
+ // [6] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewSetRootInstructionBuilder creates a new `SetRoot` instruction builder.
+func NewSetRootInstructionBuilder() *SetRoot {
+ nd := &SetRoot{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 7),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *SetRoot) SetMultisigName(multisigName [32]uint8) *SetRoot {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetRoot sets the "root" parameter.
+func (inst *SetRoot) SetRoot(root [32]uint8) *SetRoot {
+ inst.Root = &root
+ return inst
+}
+
+// SetValidUntil sets the "validUntil" parameter.
+func (inst *SetRoot) SetValidUntil(validUntil uint32) *SetRoot {
+ inst.ValidUntil = &validUntil
+ return inst
+}
+
+// SetMetadata sets the "metadata" parameter.
+func (inst *SetRoot) SetMetadata(metadata RootMetadataInput) *SetRoot {
+ inst.Metadata = &metadata
+ return inst
+}
+
+// SetMetadataProof sets the "metadataProof" parameter.
+func (inst *SetRoot) SetMetadataProof(metadataProof [][32]uint8) *SetRoot {
+ inst.MetadataProof = &metadataProof
+ return inst
+}
+
+// SetRootSignaturesAccount sets the "rootSignatures" account.
+func (inst *SetRoot) SetRootSignaturesAccount(rootSignatures ag_solanago.PublicKey) *SetRoot {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(rootSignatures).WRITE()
+ return inst
+}
+
+// GetRootSignaturesAccount gets the "rootSignatures" account.
+func (inst *SetRoot) GetRootSignaturesAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetRootMetadataAccount sets the "rootMetadata" account.
+func (inst *SetRoot) SetRootMetadataAccount(rootMetadata ag_solanago.PublicKey) *SetRoot {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(rootMetadata).WRITE()
+ return inst
+}
+
+// GetRootMetadataAccount gets the "rootMetadata" account.
+func (inst *SetRoot) GetRootMetadataAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSeenSignedHashesAccount sets the "seenSignedHashes" account.
+func (inst *SetRoot) SetSeenSignedHashesAccount(seenSignedHashes ag_solanago.PublicKey) *SetRoot {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(seenSignedHashes).WRITE()
+ return inst
+}
+
+// GetSeenSignedHashesAccount gets the "seenSignedHashes" account.
+func (inst *SetRoot) GetSeenSignedHashesAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetExpiringRootAndOpCountAccount sets the "expiringRootAndOpCount" account.
+func (inst *SetRoot) SetExpiringRootAndOpCountAccount(expiringRootAndOpCount ag_solanago.PublicKey) *SetRoot {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(expiringRootAndOpCount).WRITE()
+ return inst
+}
+
+// GetExpiringRootAndOpCountAccount gets the "expiringRootAndOpCount" account.
+func (inst *SetRoot) GetExpiringRootAndOpCountAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetMultisigConfigAccount sets the "multisigConfig" account.
+func (inst *SetRoot) SetMultisigConfigAccount(multisigConfig ag_solanago.PublicKey) *SetRoot {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(multisigConfig)
+ return inst
+}
+
+// GetMultisigConfigAccount gets the "multisigConfig" account.
+func (inst *SetRoot) GetMultisigConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *SetRoot) SetAuthorityAccount(authority ag_solanago.PublicKey) *SetRoot {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *SetRoot) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *SetRoot) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *SetRoot {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *SetRoot) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+func (inst SetRoot) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_SetRoot,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst SetRoot) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *SetRoot) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.Root == nil {
+ return errors.New("Root parameter is not set")
+ }
+ if inst.ValidUntil == nil {
+ return errors.New("ValidUntil parameter is not set")
+ }
+ if inst.Metadata == nil {
+ return errors.New("Metadata parameter is not set")
+ }
+ if inst.MetadataProof == nil {
+ return errors.New("MetadataProof parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.RootSignatures is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.RootMetadata is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SeenSignedHashes is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.ExpiringRootAndOpCount is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.MultisigConfig is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *SetRoot) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("SetRoot")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=5]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param(" Root", *inst.Root))
+ paramsBranch.Child(ag_format.Param(" ValidUntil", *inst.ValidUntil))
+ paramsBranch.Child(ag_format.Param(" Metadata", *inst.Metadata))
+ paramsBranch.Child(ag_format.Param("MetadataProof", *inst.MetadataProof))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=7]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" rootSignatures", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" rootMetadata", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" seenSignedHashes", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("expiringRootAndOpCount", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" multisigConfig", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[6]))
+ })
+ })
+ })
+}
+
+func (obj SetRoot) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `Root` param:
+ err = encoder.Encode(obj.Root)
+ if err != nil {
+ return err
+ }
+ // Serialize `ValidUntil` param:
+ err = encoder.Encode(obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ // Serialize `Metadata` param:
+ err = encoder.Encode(obj.Metadata)
+ if err != nil {
+ return err
+ }
+ // Serialize `MetadataProof` param:
+ err = encoder.Encode(obj.MetadataProof)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *SetRoot) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Root`:
+ err = decoder.Decode(&obj.Root)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ValidUntil`:
+ err = decoder.Decode(&obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Metadata`:
+ err = decoder.Decode(&obj.Metadata)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MetadataProof`:
+ err = decoder.Decode(&obj.MetadataProof)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewSetRootInstruction declares a new SetRoot instruction with the provided parameters and accounts.
+func NewSetRootInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ root [32]uint8,
+ validUntil uint32,
+ metadata RootMetadataInput,
+ metadataProof [][32]uint8,
+ // Accounts:
+ rootSignatures ag_solanago.PublicKey,
+ rootMetadata ag_solanago.PublicKey,
+ seenSignedHashes ag_solanago.PublicKey,
+ expiringRootAndOpCount ag_solanago.PublicKey,
+ multisigConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *SetRoot {
+ return NewSetRootInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetRoot(root).
+ SetValidUntil(validUntil).
+ SetMetadata(metadata).
+ SetMetadataProof(metadataProof).
+ SetRootSignaturesAccount(rootSignatures).
+ SetRootMetadataAccount(rootMetadata).
+ SetSeenSignedHashesAccount(seenSignedHashes).
+ SetExpiringRootAndOpCountAccount(expiringRootAndOpCount).
+ SetMultisigConfigAccount(multisigConfig).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/mcm/SetRoot_test.go b/chains/solana/gobindings/mcm/SetRoot_test.go
new file mode 100644
index 000000000..ed23d75c0
--- /dev/null
+++ b/chains/solana/gobindings/mcm/SetRoot_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_SetRoot(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("SetRoot"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(SetRoot)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(SetRoot)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/TransferOwnership.go b/chains/solana/gobindings/mcm/TransferOwnership.go
new file mode 100644
index 000000000..dacffff5f
--- /dev/null
+++ b/chains/solana/gobindings/mcm/TransferOwnership.go
@@ -0,0 +1,169 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// TransferOwnership is the `transferOwnership` instruction.
+type TransferOwnership struct {
+ MultisigName *[32]uint8
+ ProposedOwner *ag_solanago.PublicKey
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewTransferOwnershipInstructionBuilder creates a new `TransferOwnership` instruction builder.
+func NewTransferOwnershipInstructionBuilder() *TransferOwnership {
+ nd := &TransferOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetMultisigName sets the "multisigName" parameter.
+func (inst *TransferOwnership) SetMultisigName(multisigName [32]uint8) *TransferOwnership {
+ inst.MultisigName = &multisigName
+ return inst
+}
+
+// SetProposedOwner sets the "proposedOwner" parameter.
+func (inst *TransferOwnership) SetProposedOwner(proposedOwner ag_solanago.PublicKey) *TransferOwnership {
+ inst.ProposedOwner = &proposedOwner
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *TransferOwnership) SetConfigAccount(config ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *TransferOwnership) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *TransferOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *TransferOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst TransferOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_TransferOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst TransferOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *TransferOwnership) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MultisigName == nil {
+ return errors.New("MultisigName parameter is not set")
+ }
+ if inst.ProposedOwner == nil {
+ return errors.New("ProposedOwner parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *TransferOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("TransferOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" MultisigName", *inst.MultisigName))
+ paramsBranch.Child(ag_format.Param("ProposedOwner", *inst.ProposedOwner))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj TransferOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *TransferOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewTransferOwnershipInstruction declares a new TransferOwnership instruction with the provided parameters and accounts.
+func NewTransferOwnershipInstruction(
+ // Parameters:
+ multisigName [32]uint8,
+ proposedOwner ag_solanago.PublicKey,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *TransferOwnership {
+ return NewTransferOwnershipInstructionBuilder().
+ SetMultisigName(multisigName).
+ SetProposedOwner(proposedOwner).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/mcm/TransferOwnership_test.go b/chains/solana/gobindings/mcm/TransferOwnership_test.go
new file mode 100644
index 000000000..32ed7ad97
--- /dev/null
+++ b/chains/solana/gobindings/mcm/TransferOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_TransferOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("TransferOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(TransferOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(TransferOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/mcm/accounts.go b/chains/solana/gobindings/mcm/accounts.go
new file mode 100644
index 000000000..8c4d06ae0
--- /dev/null
+++ b/chains/solana/gobindings/mcm/accounts.go
@@ -0,0 +1,459 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type ConfigSigners struct {
+ SignerAddresses [][20]uint8
+ TotalSigners uint8
+ IsFinalized bool
+ Bump uint8
+}
+
+var ConfigSignersDiscriminator = [8]byte{147, 137, 80, 98, 50, 225, 190, 163}
+
+func (obj ConfigSigners) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ConfigSignersDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `SignerAddresses` param:
+ err = encoder.Encode(obj.SignerAddresses)
+ if err != nil {
+ return err
+ }
+ // Serialize `TotalSigners` param:
+ err = encoder.Encode(obj.TotalSigners)
+ if err != nil {
+ return err
+ }
+ // Serialize `IsFinalized` param:
+ err = encoder.Encode(obj.IsFinalized)
+ if err != nil {
+ return err
+ }
+ // Serialize `Bump` param:
+ err = encoder.Encode(obj.Bump)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ConfigSigners) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ConfigSignersDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[147 137 80 98 50 225 190 163]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `SignerAddresses`:
+ err = decoder.Decode(&obj.SignerAddresses)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TotalSigners`:
+ err = decoder.Decode(&obj.TotalSigners)
+ if err != nil {
+ return err
+ }
+ // Deserialize `IsFinalized`:
+ err = decoder.Decode(&obj.IsFinalized)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Bump`:
+ err = decoder.Decode(&obj.Bump)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type MultisigConfig struct {
+ ChainId uint64
+ MultisigName [32]uint8
+ Owner ag_solanago.PublicKey
+ ProposedOwner ag_solanago.PublicKey
+ GroupQuorums [32]uint8
+ GroupParents [32]uint8
+ Signers []McmSigner
+}
+
+var MultisigConfigDiscriminator = [8]byte{44, 62, 172, 225, 246, 3, 178, 33}
+
+func (obj MultisigConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(MultisigConfigDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `ChainId` param:
+ err = encoder.Encode(obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Serialize `MultisigName` param:
+ err = encoder.Encode(obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Serialize `Owner` param:
+ err = encoder.Encode(obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Serialize `GroupQuorums` param:
+ err = encoder.Encode(obj.GroupQuorums)
+ if err != nil {
+ return err
+ }
+ // Serialize `GroupParents` param:
+ err = encoder.Encode(obj.GroupParents)
+ if err != nil {
+ return err
+ }
+ // Serialize `Signers` param:
+ err = encoder.Encode(obj.Signers)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *MultisigConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(MultisigConfigDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[44 62 172 225 246 3 178 33]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `ChainId`:
+ err = decoder.Decode(&obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MultisigName`:
+ err = decoder.Decode(&obj.MultisigName)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Owner`:
+ err = decoder.Decode(&obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `GroupQuorums`:
+ err = decoder.Decode(&obj.GroupQuorums)
+ if err != nil {
+ return err
+ }
+ // Deserialize `GroupParents`:
+ err = decoder.Decode(&obj.GroupParents)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Signers`:
+ err = decoder.Decode(&obj.Signers)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type RootSignatures struct {
+ TotalSignatures uint8
+ Signatures []Signature
+ IsFinalized bool
+ Bump uint8
+}
+
+var RootSignaturesDiscriminator = [8]byte{21, 186, 10, 33, 117, 215, 246, 76}
+
+func (obj RootSignatures) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(RootSignaturesDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `TotalSignatures` param:
+ err = encoder.Encode(obj.TotalSignatures)
+ if err != nil {
+ return err
+ }
+ // Serialize `Signatures` param:
+ err = encoder.Encode(obj.Signatures)
+ if err != nil {
+ return err
+ }
+ // Serialize `IsFinalized` param:
+ err = encoder.Encode(obj.IsFinalized)
+ if err != nil {
+ return err
+ }
+ // Serialize `Bump` param:
+ err = encoder.Encode(obj.Bump)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *RootSignatures) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(RootSignaturesDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[21 186 10 33 117 215 246 76]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `TotalSignatures`:
+ err = decoder.Decode(&obj.TotalSignatures)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Signatures`:
+ err = decoder.Decode(&obj.Signatures)
+ if err != nil {
+ return err
+ }
+ // Deserialize `IsFinalized`:
+ err = decoder.Decode(&obj.IsFinalized)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Bump`:
+ err = decoder.Decode(&obj.Bump)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type RootMetadata struct {
+ ChainId uint64
+ Multisig ag_solanago.PublicKey
+ PreOpCount uint64
+ PostOpCount uint64
+ OverridePreviousRoot bool
+}
+
+var RootMetadataDiscriminator = [8]byte{125, 211, 89, 150, 221, 6, 141, 205}
+
+func (obj RootMetadata) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(RootMetadataDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `ChainId` param:
+ err = encoder.Encode(obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Serialize `Multisig` param:
+ err = encoder.Encode(obj.Multisig)
+ if err != nil {
+ return err
+ }
+ // Serialize `PreOpCount` param:
+ err = encoder.Encode(obj.PreOpCount)
+ if err != nil {
+ return err
+ }
+ // Serialize `PostOpCount` param:
+ err = encoder.Encode(obj.PostOpCount)
+ if err != nil {
+ return err
+ }
+ // Serialize `OverridePreviousRoot` param:
+ err = encoder.Encode(obj.OverridePreviousRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *RootMetadata) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(RootMetadataDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[125 211 89 150 221 6 141 205]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `ChainId`:
+ err = decoder.Decode(&obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Multisig`:
+ err = decoder.Decode(&obj.Multisig)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PreOpCount`:
+ err = decoder.Decode(&obj.PreOpCount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PostOpCount`:
+ err = decoder.Decode(&obj.PostOpCount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OverridePreviousRoot`:
+ err = decoder.Decode(&obj.OverridePreviousRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ExpiringRootAndOpCount struct {
+ Root [32]uint8
+ ValidUntil uint32
+ OpCount uint64
+}
+
+var ExpiringRootAndOpCountDiscriminator = [8]byte{196, 176, 71, 210, 134, 228, 202, 75}
+
+func (obj ExpiringRootAndOpCount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ExpiringRootAndOpCountDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Root` param:
+ err = encoder.Encode(obj.Root)
+ if err != nil {
+ return err
+ }
+ // Serialize `ValidUntil` param:
+ err = encoder.Encode(obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ // Serialize `OpCount` param:
+ err = encoder.Encode(obj.OpCount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ExpiringRootAndOpCount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ExpiringRootAndOpCountDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[196 176 71 210 134 228 202 75]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Root`:
+ err = decoder.Decode(&obj.Root)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ValidUntil`:
+ err = decoder.Decode(&obj.ValidUntil)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OpCount`:
+ err = decoder.Decode(&obj.OpCount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type SeenSignedHash struct {
+ Seen bool
+}
+
+var SeenSignedHashDiscriminator = [8]byte{229, 115, 10, 185, 39, 100, 210, 151}
+
+func (obj SeenSignedHash) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(SeenSignedHashDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Seen` param:
+ err = encoder.Encode(obj.Seen)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *SeenSignedHash) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(SeenSignedHashDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[229 115 10 185 39 100 210 151]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Seen`:
+ err = decoder.Decode(&obj.Seen)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/mcm/instructions.go b/chains/solana/gobindings/mcm/instructions.go
new file mode 100644
index 000000000..1c6db2533
--- /dev/null
+++ b/chains/solana/gobindings/mcm/instructions.go
@@ -0,0 +1,212 @@
+// This is mcm program supporting multiple instances of multisig configuration
+// A single deployed program manages multiple multisig states(configurations) identified by multisig_name
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ "fmt"
+ ag_spew "github.com/davecgh/go-spew/spew"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_text "github.com/gagliardetto/solana-go/text"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+var ProgramID ag_solanago.PublicKey
+
+func SetProgramID(pubkey ag_solanago.PublicKey) {
+ ProgramID = pubkey
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+}
+
+const ProgramName = "Mcm"
+
+func init() {
+ if !ProgramID.IsZero() {
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+ }
+}
+
+var (
+ // initialize a new multisig configuration, store the chain_id and multisig_name
+ // multisig_name is a unique identifier for the multisig configuration(32 bytes, left-padded)
+ Instruction_Initialize = ag_binary.TypeID([8]byte{175, 175, 109, 31, 13, 152, 155, 237})
+
+ Instruction_TransferOwnership = ag_binary.TypeID([8]byte{65, 177, 215, 73, 53, 45, 99, 47})
+
+ Instruction_AcceptOwnership = ag_binary.TypeID([8]byte{172, 23, 43, 13, 238, 213, 85, 150})
+
+ Instruction_SetConfig = ag_binary.TypeID([8]byte{108, 158, 154, 175, 212, 98, 52, 66})
+
+ Instruction_SetRoot = ag_binary.TypeID([8]byte{183, 49, 10, 206, 168, 183, 131, 67})
+
+ Instruction_Execute = ag_binary.TypeID([8]byte{130, 221, 242, 154, 13, 193, 189, 29})
+
+ Instruction_InitSigners = ag_binary.TypeID([8]byte{102, 182, 129, 16, 138, 142, 223, 196})
+
+ Instruction_AppendSigners = ag_binary.TypeID([8]byte{238, 209, 251, 39, 41, 241, 146, 25})
+
+ Instruction_ClearSigners = ag_binary.TypeID([8]byte{90, 140, 170, 146, 128, 75, 100, 175})
+
+ Instruction_FinalizeSigners = ag_binary.TypeID([8]byte{49, 254, 154, 226, 137, 199, 120, 63})
+
+ Instruction_InitSignatures = ag_binary.TypeID([8]byte{190, 120, 207, 36, 26, 58, 196, 13})
+
+ Instruction_AppendSignatures = ag_binary.TypeID([8]byte{195, 112, 164, 69, 37, 137, 198, 54})
+
+ Instruction_ClearSignatures = ag_binary.TypeID([8]byte{80, 0, 39, 255, 46, 165, 193, 109})
+
+ Instruction_FinalizeSignatures = ag_binary.TypeID([8]byte{77, 138, 152, 199, 37, 141, 189, 159})
+)
+
+// InstructionIDToName returns the name of the instruction given its ID.
+func InstructionIDToName(id ag_binary.TypeID) string {
+ switch id {
+ case Instruction_Initialize:
+ return "Initialize"
+ case Instruction_TransferOwnership:
+ return "TransferOwnership"
+ case Instruction_AcceptOwnership:
+ return "AcceptOwnership"
+ case Instruction_SetConfig:
+ return "SetConfig"
+ case Instruction_SetRoot:
+ return "SetRoot"
+ case Instruction_Execute:
+ return "Execute"
+ case Instruction_InitSigners:
+ return "InitSigners"
+ case Instruction_AppendSigners:
+ return "AppendSigners"
+ case Instruction_ClearSigners:
+ return "ClearSigners"
+ case Instruction_FinalizeSigners:
+ return "FinalizeSigners"
+ case Instruction_InitSignatures:
+ return "InitSignatures"
+ case Instruction_AppendSignatures:
+ return "AppendSignatures"
+ case Instruction_ClearSignatures:
+ return "ClearSignatures"
+ case Instruction_FinalizeSignatures:
+ return "FinalizeSignatures"
+ default:
+ return ""
+ }
+}
+
+type Instruction struct {
+ ag_binary.BaseVariant
+}
+
+func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) {
+ if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok {
+ enToTree.EncodeToTree(parent)
+ } else {
+ parent.Child(ag_spew.Sdump(inst))
+ }
+}
+
+var InstructionImplDef = ag_binary.NewVariantDefinition(
+ ag_binary.AnchorTypeIDEncoding,
+ []ag_binary.VariantType{
+ {
+ "initialize", (*Initialize)(nil),
+ },
+ {
+ "transfer_ownership", (*TransferOwnership)(nil),
+ },
+ {
+ "accept_ownership", (*AcceptOwnership)(nil),
+ },
+ {
+ "set_config", (*SetConfig)(nil),
+ },
+ {
+ "set_root", (*SetRoot)(nil),
+ },
+ {
+ "execute", (*Execute)(nil),
+ },
+ {
+ "init_signers", (*InitSigners)(nil),
+ },
+ {
+ "append_signers", (*AppendSigners)(nil),
+ },
+ {
+ "clear_signers", (*ClearSigners)(nil),
+ },
+ {
+ "finalize_signers", (*FinalizeSigners)(nil),
+ },
+ {
+ "init_signatures", (*InitSignatures)(nil),
+ },
+ {
+ "append_signatures", (*AppendSignatures)(nil),
+ },
+ {
+ "clear_signatures", (*ClearSignatures)(nil),
+ },
+ {
+ "finalize_signatures", (*FinalizeSignatures)(nil),
+ },
+ },
+)
+
+func (inst *Instruction) ProgramID() ag_solanago.PublicKey {
+ return ProgramID
+}
+
+func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) {
+ return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts()
+}
+
+func (inst *Instruction) Data() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil {
+ return nil, fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return buf.Bytes(), nil
+}
+
+func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
+ return encoder.Encode(inst.Impl, option)
+}
+
+func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
+ return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
+}
+
+func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
+ err := encoder.WriteBytes(inst.TypeID.Bytes(), false)
+ if err != nil {
+ return fmt.Errorf("unable to write variant type: %w", err)
+ }
+ return encoder.Encode(inst.Impl)
+}
+
+func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) {
+ inst, err := DecodeInstruction(accounts, data)
+ if err != nil {
+ return nil, err
+ }
+ return inst, nil
+}
+
+func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) {
+ inst := new(Instruction)
+ if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil {
+ return nil, fmt.Errorf("unable to decode instruction: %w", err)
+ }
+ if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok {
+ err := v.SetAccounts(accounts)
+ if err != nil {
+ return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
+ }
+ }
+ return inst, nil
+}
diff --git a/chains/solana/gobindings/mcm/testing_utils.go b/chains/solana/gobindings/mcm/testing_utils.go
new file mode 100644
index 000000000..c04949ded
--- /dev/null
+++ b/chains/solana/gobindings/mcm/testing_utils.go
@@ -0,0 +1,20 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ "bytes"
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+func encodeT(data interface{}, buf *bytes.Buffer) error {
+ if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil {
+ return fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return nil
+}
+
+func decodeT(dst interface{}, data []byte) error {
+ return ag_binary.NewBorshDecoder(data).Decode(dst)
+}
diff --git a/chains/solana/gobindings/mcm/types.go b/chains/solana/gobindings/mcm/types.go
new file mode 100644
index 000000000..e58247564
--- /dev/null
+++ b/chains/solana/gobindings/mcm/types.go
@@ -0,0 +1,162 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package mcm
+
+import (
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type Signature struct {
+ V uint8
+ R [32]uint8
+ S [32]uint8
+}
+
+func (obj Signature) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `V` param:
+ err = encoder.Encode(obj.V)
+ if err != nil {
+ return err
+ }
+ // Serialize `R` param:
+ err = encoder.Encode(obj.R)
+ if err != nil {
+ return err
+ }
+ // Serialize `S` param:
+ err = encoder.Encode(obj.S)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Signature) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `V`:
+ err = decoder.Decode(&obj.V)
+ if err != nil {
+ return err
+ }
+ // Deserialize `R`:
+ err = decoder.Decode(&obj.R)
+ if err != nil {
+ return err
+ }
+ // Deserialize `S`:
+ err = decoder.Decode(&obj.S)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type McmSigner struct {
+ EvmAddress [20]uint8
+ Index uint8
+ Group uint8
+}
+
+func (obj McmSigner) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `EvmAddress` param:
+ err = encoder.Encode(obj.EvmAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `Index` param:
+ err = encoder.Encode(obj.Index)
+ if err != nil {
+ return err
+ }
+ // Serialize `Group` param:
+ err = encoder.Encode(obj.Group)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *McmSigner) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `EvmAddress`:
+ err = decoder.Decode(&obj.EvmAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Index`:
+ err = decoder.Decode(&obj.Index)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Group`:
+ err = decoder.Decode(&obj.Group)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type RootMetadataInput struct {
+ ChainId uint64
+ Multisig ag_solanago.PublicKey
+ PreOpCount uint64
+ PostOpCount uint64
+ OverridePreviousRoot bool
+}
+
+func (obj RootMetadataInput) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ChainId` param:
+ err = encoder.Encode(obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Serialize `Multisig` param:
+ err = encoder.Encode(obj.Multisig)
+ if err != nil {
+ return err
+ }
+ // Serialize `PreOpCount` param:
+ err = encoder.Encode(obj.PreOpCount)
+ if err != nil {
+ return err
+ }
+ // Serialize `PostOpCount` param:
+ err = encoder.Encode(obj.PostOpCount)
+ if err != nil {
+ return err
+ }
+ // Serialize `OverridePreviousRoot` param:
+ err = encoder.Encode(obj.OverridePreviousRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *RootMetadataInput) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ChainId`:
+ err = decoder.Decode(&obj.ChainId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Multisig`:
+ err = decoder.Decode(&obj.Multisig)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PreOpCount`:
+ err = decoder.Decode(&obj.PreOpCount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PostOpCount`:
+ err = decoder.Decode(&obj.PostOpCount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OverridePreviousRoot`:
+ err = decoder.Decode(&obj.OverridePreviousRoot)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/timelock/AcceptOwnership.go b/chains/solana/gobindings/timelock/AcceptOwnership.go
new file mode 100644
index 000000000..c95e33898
--- /dev/null
+++ b/chains/solana/gobindings/timelock/AcceptOwnership.go
@@ -0,0 +1,117 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AcceptOwnership is the `acceptOwnership` instruction.
+type AcceptOwnership struct {
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAcceptOwnershipInstructionBuilder creates a new `AcceptOwnership` instruction builder.
+func NewAcceptOwnershipInstructionBuilder() *AcceptOwnership {
+ nd := &AcceptOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *AcceptOwnership) SetConfigAccount(config ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *AcceptOwnership) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AcceptOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AcceptOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst AcceptOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AcceptOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AcceptOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AcceptOwnership) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AcceptOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AcceptOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj AcceptOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *AcceptOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewAcceptOwnershipInstruction declares a new AcceptOwnership instruction with the provided parameters and accounts.
+func NewAcceptOwnershipInstruction(
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *AcceptOwnership {
+ return NewAcceptOwnershipInstructionBuilder().
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/AcceptOwnership_test.go b/chains/solana/gobindings/timelock/AcceptOwnership_test.go
new file mode 100644
index 000000000..56599774c
--- /dev/null
+++ b/chains/solana/gobindings/timelock/AcceptOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AcceptOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AcceptOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AcceptOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AcceptOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/AppendInstructions.go b/chains/solana/gobindings/timelock/AppendInstructions.go
new file mode 100644
index 000000000..f92f1c1c4
--- /dev/null
+++ b/chains/solana/gobindings/timelock/AppendInstructions.go
@@ -0,0 +1,188 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AppendInstructions is the `appendInstructions` instruction.
+type AppendInstructions struct {
+ Id *[32]uint8
+ InstructionsBatch *[]InstructionData
+
+ // [0] = [WRITE] operation
+ //
+ // [1] = [WRITE, SIGNER] authority
+ //
+ // [2] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAppendInstructionsInstructionBuilder creates a new `AppendInstructions` instruction builder.
+func NewAppendInstructionsInstructionBuilder() *AppendInstructions {
+ nd := &AppendInstructions{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3),
+ }
+ return nd
+}
+
+// SetId sets the "id" parameter.
+func (inst *AppendInstructions) SetId(id [32]uint8) *AppendInstructions {
+ inst.Id = &id
+ return inst
+}
+
+// SetInstructionsBatch sets the "instructionsBatch" parameter.
+func (inst *AppendInstructions) SetInstructionsBatch(instructionsBatch []InstructionData) *AppendInstructions {
+ inst.InstructionsBatch = &instructionsBatch
+ return inst
+}
+
+// SetOperationAccount sets the "operation" account.
+func (inst *AppendInstructions) SetOperationAccount(operation ag_solanago.PublicKey) *AppendInstructions {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(operation).WRITE()
+ return inst
+}
+
+// GetOperationAccount gets the "operation" account.
+func (inst *AppendInstructions) GetOperationAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AppendInstructions) SetAuthorityAccount(authority ag_solanago.PublicKey) *AppendInstructions {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AppendInstructions) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *AppendInstructions) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *AppendInstructions {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *AppendInstructions) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+func (inst AppendInstructions) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AppendInstructions,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AppendInstructions) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AppendInstructions) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Id == nil {
+ return errors.New("Id parameter is not set")
+ }
+ if inst.InstructionsBatch == nil {
+ return errors.New("InstructionsBatch parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Operation is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AppendInstructions) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AppendInstructions")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" Id", *inst.Id))
+ paramsBranch.Child(ag_format.Param("InstructionsBatch", *inst.InstructionsBatch))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" operation", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2]))
+ })
+ })
+ })
+}
+
+func (obj AppendInstructions) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Id` param:
+ err = encoder.Encode(obj.Id)
+ if err != nil {
+ return err
+ }
+ // Serialize `InstructionsBatch` param:
+ err = encoder.Encode(obj.InstructionsBatch)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *AppendInstructions) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Id`:
+ err = decoder.Decode(&obj.Id)
+ if err != nil {
+ return err
+ }
+ // Deserialize `InstructionsBatch`:
+ err = decoder.Decode(&obj.InstructionsBatch)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewAppendInstructionsInstruction declares a new AppendInstructions instruction with the provided parameters and accounts.
+func NewAppendInstructionsInstruction(
+ // Parameters:
+ id [32]uint8,
+ instructionsBatch []InstructionData,
+ // Accounts:
+ operation ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *AppendInstructions {
+ return NewAppendInstructionsInstructionBuilder().
+ SetId(id).
+ SetInstructionsBatch(instructionsBatch).
+ SetOperationAccount(operation).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/timelock/AppendInstructions_test.go b/chains/solana/gobindings/timelock/AppendInstructions_test.go
new file mode 100644
index 000000000..c2158e4a2
--- /dev/null
+++ b/chains/solana/gobindings/timelock/AppendInstructions_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AppendInstructions(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AppendInstructions"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AppendInstructions)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AppendInstructions)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/BatchAddAccess.go b/chains/solana/gobindings/timelock/BatchAddAccess.go
new file mode 100644
index 000000000..ce331614b
--- /dev/null
+++ b/chains/solana/gobindings/timelock/BatchAddAccess.go
@@ -0,0 +1,184 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// BatchAddAccess is the `batchAddAccess` instruction.
+type BatchAddAccess struct {
+ Role *Role
+
+ // [0] = [] config
+ //
+ // [1] = [] accessControllerProgram
+ //
+ // [2] = [WRITE] roleAccessController
+ //
+ // [3] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewBatchAddAccessInstructionBuilder creates a new `BatchAddAccess` instruction builder.
+func NewBatchAddAccessInstructionBuilder() *BatchAddAccess {
+ nd := &BatchAddAccess{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetRole sets the "role" parameter.
+func (inst *BatchAddAccess) SetRole(role Role) *BatchAddAccess {
+ inst.Role = &role
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *BatchAddAccess) SetConfigAccount(config ag_solanago.PublicKey) *BatchAddAccess {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *BatchAddAccess) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAccessControllerProgramAccount sets the "accessControllerProgram" account.
+func (inst *BatchAddAccess) SetAccessControllerProgramAccount(accessControllerProgram ag_solanago.PublicKey) *BatchAddAccess {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(accessControllerProgram)
+ return inst
+}
+
+// GetAccessControllerProgramAccount gets the "accessControllerProgram" account.
+func (inst *BatchAddAccess) GetAccessControllerProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetRoleAccessControllerAccount sets the "roleAccessController" account.
+func (inst *BatchAddAccess) SetRoleAccessControllerAccount(roleAccessController ag_solanago.PublicKey) *BatchAddAccess {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(roleAccessController).WRITE()
+ return inst
+}
+
+// GetRoleAccessControllerAccount gets the "roleAccessController" account.
+func (inst *BatchAddAccess) GetRoleAccessControllerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *BatchAddAccess) SetAuthorityAccount(authority ag_solanago.PublicKey) *BatchAddAccess {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *BatchAddAccess) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst BatchAddAccess) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_BatchAddAccess,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst BatchAddAccess) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *BatchAddAccess) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Role == nil {
+ return errors.New("Role parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.AccessControllerProgram is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.RoleAccessController is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *BatchAddAccess) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("BatchAddAccess")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Role", *inst.Role))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("accessControllerProgram", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" roleAccessController", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj BatchAddAccess) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Role` param:
+ err = encoder.Encode(obj.Role)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *BatchAddAccess) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Role`:
+ err = decoder.Decode(&obj.Role)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewBatchAddAccessInstruction declares a new BatchAddAccess instruction with the provided parameters and accounts.
+func NewBatchAddAccessInstruction(
+ // Parameters:
+ role Role,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ accessControllerProgram ag_solanago.PublicKey,
+ roleAccessController ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *BatchAddAccess {
+ return NewBatchAddAccessInstructionBuilder().
+ SetRole(role).
+ SetConfigAccount(config).
+ SetAccessControllerProgramAccount(accessControllerProgram).
+ SetRoleAccessControllerAccount(roleAccessController).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/BatchAddAccess_test.go b/chains/solana/gobindings/timelock/BatchAddAccess_test.go
new file mode 100644
index 000000000..115847127
--- /dev/null
+++ b/chains/solana/gobindings/timelock/BatchAddAccess_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_BatchAddAccess(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("BatchAddAccess"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(BatchAddAccess)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(BatchAddAccess)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/BlockFunctionSelector.go b/chains/solana/gobindings/timelock/BlockFunctionSelector.go
new file mode 100644
index 000000000..9723f9fa0
--- /dev/null
+++ b/chains/solana/gobindings/timelock/BlockFunctionSelector.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// BlockFunctionSelector is the `blockFunctionSelector` instruction.
+type BlockFunctionSelector struct {
+ Selector *[8]uint8
+
+ // [0] = [] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewBlockFunctionSelectorInstructionBuilder creates a new `BlockFunctionSelector` instruction builder.
+func NewBlockFunctionSelectorInstructionBuilder() *BlockFunctionSelector {
+ nd := &BlockFunctionSelector{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetSelector sets the "selector" parameter.
+func (inst *BlockFunctionSelector) SetSelector(selector [8]uint8) *BlockFunctionSelector {
+ inst.Selector = &selector
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *BlockFunctionSelector) SetConfigAccount(config ag_solanago.PublicKey) *BlockFunctionSelector {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *BlockFunctionSelector) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *BlockFunctionSelector) SetAuthorityAccount(authority ag_solanago.PublicKey) *BlockFunctionSelector {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *BlockFunctionSelector) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst BlockFunctionSelector) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_BlockFunctionSelector,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst BlockFunctionSelector) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *BlockFunctionSelector) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Selector == nil {
+ return errors.New("Selector parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *BlockFunctionSelector) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("BlockFunctionSelector")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Selector", *inst.Selector))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj BlockFunctionSelector) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Selector` param:
+ err = encoder.Encode(obj.Selector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *BlockFunctionSelector) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Selector`:
+ err = decoder.Decode(&obj.Selector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewBlockFunctionSelectorInstruction declares a new BlockFunctionSelector instruction with the provided parameters and accounts.
+func NewBlockFunctionSelectorInstruction(
+ // Parameters:
+ selector [8]uint8,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *BlockFunctionSelector {
+ return NewBlockFunctionSelectorInstructionBuilder().
+ SetSelector(selector).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/BlockFunctionSelector_test.go b/chains/solana/gobindings/timelock/BlockFunctionSelector_test.go
new file mode 100644
index 000000000..b1270feba
--- /dev/null
+++ b/chains/solana/gobindings/timelock/BlockFunctionSelector_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_BlockFunctionSelector(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("BlockFunctionSelector"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(BlockFunctionSelector)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(BlockFunctionSelector)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/BypasserExecuteBatch.go b/chains/solana/gobindings/timelock/BypasserExecuteBatch.go
new file mode 100644
index 000000000..6c7b73819
--- /dev/null
+++ b/chains/solana/gobindings/timelock/BypasserExecuteBatch.go
@@ -0,0 +1,203 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// BypasserExecuteBatch is the `bypasserExecuteBatch` instruction.
+type BypasserExecuteBatch struct {
+ Id *[32]uint8
+
+ // [0] = [] config
+ //
+ // [1] = [] timelockSigner
+ //
+ // [2] = [WRITE] operation
+ //
+ // [3] = [] roleAccessController
+ //
+ // [4] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewBypasserExecuteBatchInstructionBuilder creates a new `BypasserExecuteBatch` instruction builder.
+func NewBypasserExecuteBatchInstructionBuilder() *BypasserExecuteBatch {
+ nd := &BypasserExecuteBatch{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 5),
+ }
+ return nd
+}
+
+// SetId sets the "id" parameter.
+func (inst *BypasserExecuteBatch) SetId(id [32]uint8) *BypasserExecuteBatch {
+ inst.Id = &id
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *BypasserExecuteBatch) SetConfigAccount(config ag_solanago.PublicKey) *BypasserExecuteBatch {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *BypasserExecuteBatch) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetTimelockSignerAccount sets the "timelockSigner" account.
+func (inst *BypasserExecuteBatch) SetTimelockSignerAccount(timelockSigner ag_solanago.PublicKey) *BypasserExecuteBatch {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(timelockSigner)
+ return inst
+}
+
+// GetTimelockSignerAccount gets the "timelockSigner" account.
+func (inst *BypasserExecuteBatch) GetTimelockSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetOperationAccount sets the "operation" account.
+func (inst *BypasserExecuteBatch) SetOperationAccount(operation ag_solanago.PublicKey) *BypasserExecuteBatch {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(operation).WRITE()
+ return inst
+}
+
+// GetOperationAccount gets the "operation" account.
+func (inst *BypasserExecuteBatch) GetOperationAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetRoleAccessControllerAccount sets the "roleAccessController" account.
+func (inst *BypasserExecuteBatch) SetRoleAccessControllerAccount(roleAccessController ag_solanago.PublicKey) *BypasserExecuteBatch {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(roleAccessController)
+ return inst
+}
+
+// GetRoleAccessControllerAccount gets the "roleAccessController" account.
+func (inst *BypasserExecuteBatch) GetRoleAccessControllerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *BypasserExecuteBatch) SetAuthorityAccount(authority ag_solanago.PublicKey) *BypasserExecuteBatch {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *BypasserExecuteBatch) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+func (inst BypasserExecuteBatch) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_BypasserExecuteBatch,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst BypasserExecuteBatch) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *BypasserExecuteBatch) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Id == nil {
+ return errors.New("Id parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.TimelockSigner is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Operation is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.RoleAccessController is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *BypasserExecuteBatch) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("BypasserExecuteBatch")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Id", *inst.Id))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=5]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" timelockSigner", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" operation", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("roleAccessController", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[4]))
+ })
+ })
+ })
+}
+
+func (obj BypasserExecuteBatch) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Id` param:
+ err = encoder.Encode(obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *BypasserExecuteBatch) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Id`:
+ err = decoder.Decode(&obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewBypasserExecuteBatchInstruction declares a new BypasserExecuteBatch instruction with the provided parameters and accounts.
+func NewBypasserExecuteBatchInstruction(
+ // Parameters:
+ id [32]uint8,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ timelockSigner ag_solanago.PublicKey,
+ operation ag_solanago.PublicKey,
+ roleAccessController ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *BypasserExecuteBatch {
+ return NewBypasserExecuteBatchInstructionBuilder().
+ SetId(id).
+ SetConfigAccount(config).
+ SetTimelockSignerAccount(timelockSigner).
+ SetOperationAccount(operation).
+ SetRoleAccessControllerAccount(roleAccessController).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/BypasserExecuteBatch_test.go b/chains/solana/gobindings/timelock/BypasserExecuteBatch_test.go
new file mode 100644
index 000000000..b7b745acd
--- /dev/null
+++ b/chains/solana/gobindings/timelock/BypasserExecuteBatch_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_BypasserExecuteBatch(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("BypasserExecuteBatch"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(BypasserExecuteBatch)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(BypasserExecuteBatch)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/Cancel.go b/chains/solana/gobindings/timelock/Cancel.go
new file mode 100644
index 000000000..32444c6fb
--- /dev/null
+++ b/chains/solana/gobindings/timelock/Cancel.go
@@ -0,0 +1,184 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Cancel is the `cancel` instruction.
+type Cancel struct {
+ Id *[32]uint8
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] operation
+ //
+ // [2] = [] roleAccessController
+ //
+ // [3] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewCancelInstructionBuilder creates a new `Cancel` instruction builder.
+func NewCancelInstructionBuilder() *Cancel {
+ nd := &Cancel{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetId sets the "id" parameter.
+func (inst *Cancel) SetId(id [32]uint8) *Cancel {
+ inst.Id = &id
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *Cancel) SetConfigAccount(config ag_solanago.PublicKey) *Cancel {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *Cancel) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetOperationAccount sets the "operation" account.
+func (inst *Cancel) SetOperationAccount(operation ag_solanago.PublicKey) *Cancel {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(operation).WRITE()
+ return inst
+}
+
+// GetOperationAccount gets the "operation" account.
+func (inst *Cancel) GetOperationAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetRoleAccessControllerAccount sets the "roleAccessController" account.
+func (inst *Cancel) SetRoleAccessControllerAccount(roleAccessController ag_solanago.PublicKey) *Cancel {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(roleAccessController)
+ return inst
+}
+
+// GetRoleAccessControllerAccount gets the "roleAccessController" account.
+func (inst *Cancel) GetRoleAccessControllerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *Cancel) SetAuthorityAccount(authority ag_solanago.PublicKey) *Cancel {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *Cancel) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst Cancel) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Cancel,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Cancel) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Cancel) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Id == nil {
+ return errors.New("Id parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Operation is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.RoleAccessController is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Cancel) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Cancel")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Id", *inst.Id))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" operation", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("roleAccessController", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj Cancel) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Id` param:
+ err = encoder.Encode(obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *Cancel) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Id`:
+ err = decoder.Decode(&obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewCancelInstruction declares a new Cancel instruction with the provided parameters and accounts.
+func NewCancelInstruction(
+ // Parameters:
+ id [32]uint8,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ operation ag_solanago.PublicKey,
+ roleAccessController ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *Cancel {
+ return NewCancelInstructionBuilder().
+ SetId(id).
+ SetConfigAccount(config).
+ SetOperationAccount(operation).
+ SetRoleAccessControllerAccount(roleAccessController).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/Cancel_test.go b/chains/solana/gobindings/timelock/Cancel_test.go
new file mode 100644
index 000000000..9fdca536c
--- /dev/null
+++ b/chains/solana/gobindings/timelock/Cancel_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Cancel(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Cancel"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Cancel)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Cancel)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/ClearOperation.go b/chains/solana/gobindings/timelock/ClearOperation.go
new file mode 100644
index 000000000..6226b6974
--- /dev/null
+++ b/chains/solana/gobindings/timelock/ClearOperation.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// ClearOperation is the `clearOperation` instruction.
+type ClearOperation struct {
+ Id *[32]uint8
+
+ // [0] = [WRITE] operation
+ //
+ // [1] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewClearOperationInstructionBuilder creates a new `ClearOperation` instruction builder.
+func NewClearOperationInstructionBuilder() *ClearOperation {
+ nd := &ClearOperation{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetId sets the "id" parameter.
+func (inst *ClearOperation) SetId(id [32]uint8) *ClearOperation {
+ inst.Id = &id
+ return inst
+}
+
+// SetOperationAccount sets the "operation" account.
+func (inst *ClearOperation) SetOperationAccount(operation ag_solanago.PublicKey) *ClearOperation {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(operation).WRITE()
+ return inst
+}
+
+// GetOperationAccount gets the "operation" account.
+func (inst *ClearOperation) GetOperationAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *ClearOperation) SetAuthorityAccount(authority ag_solanago.PublicKey) *ClearOperation {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *ClearOperation) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst ClearOperation) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_ClearOperation,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst ClearOperation) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *ClearOperation) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Id == nil {
+ return errors.New("Id parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Operation is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *ClearOperation) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("ClearOperation")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Id", *inst.Id))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("operation", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj ClearOperation) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Id` param:
+ err = encoder.Encode(obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *ClearOperation) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Id`:
+ err = decoder.Decode(&obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewClearOperationInstruction declares a new ClearOperation instruction with the provided parameters and accounts.
+func NewClearOperationInstruction(
+ // Parameters:
+ id [32]uint8,
+ // Accounts:
+ operation ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *ClearOperation {
+ return NewClearOperationInstructionBuilder().
+ SetId(id).
+ SetOperationAccount(operation).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/ClearOperation_test.go b/chains/solana/gobindings/timelock/ClearOperation_test.go
new file mode 100644
index 000000000..ce4856919
--- /dev/null
+++ b/chains/solana/gobindings/timelock/ClearOperation_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_ClearOperation(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("ClearOperation"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(ClearOperation)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(ClearOperation)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/ExecuteBatch.go b/chains/solana/gobindings/timelock/ExecuteBatch.go
new file mode 100644
index 000000000..6dd8326ba
--- /dev/null
+++ b/chains/solana/gobindings/timelock/ExecuteBatch.go
@@ -0,0 +1,222 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// ExecuteBatch is the `executeBatch` instruction.
+type ExecuteBatch struct {
+ Id *[32]uint8
+
+ // [0] = [] config
+ //
+ // [1] = [] timelockSigner
+ //
+ // [2] = [WRITE] operation
+ //
+ // [3] = [] predecessorOperation
+ //
+ // [4] = [] roleAccessController
+ //
+ // [5] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewExecuteBatchInstructionBuilder creates a new `ExecuteBatch` instruction builder.
+func NewExecuteBatchInstructionBuilder() *ExecuteBatch {
+ nd := &ExecuteBatch{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 6),
+ }
+ return nd
+}
+
+// SetId sets the "id" parameter.
+func (inst *ExecuteBatch) SetId(id [32]uint8) *ExecuteBatch {
+ inst.Id = &id
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *ExecuteBatch) SetConfigAccount(config ag_solanago.PublicKey) *ExecuteBatch {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *ExecuteBatch) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetTimelockSignerAccount sets the "timelockSigner" account.
+func (inst *ExecuteBatch) SetTimelockSignerAccount(timelockSigner ag_solanago.PublicKey) *ExecuteBatch {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(timelockSigner)
+ return inst
+}
+
+// GetTimelockSignerAccount gets the "timelockSigner" account.
+func (inst *ExecuteBatch) GetTimelockSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetOperationAccount sets the "operation" account.
+func (inst *ExecuteBatch) SetOperationAccount(operation ag_solanago.PublicKey) *ExecuteBatch {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(operation).WRITE()
+ return inst
+}
+
+// GetOperationAccount gets the "operation" account.
+func (inst *ExecuteBatch) GetOperationAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetPredecessorOperationAccount sets the "predecessorOperation" account.
+func (inst *ExecuteBatch) SetPredecessorOperationAccount(predecessorOperation ag_solanago.PublicKey) *ExecuteBatch {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(predecessorOperation)
+ return inst
+}
+
+// GetPredecessorOperationAccount gets the "predecessorOperation" account.
+func (inst *ExecuteBatch) GetPredecessorOperationAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetRoleAccessControllerAccount sets the "roleAccessController" account.
+func (inst *ExecuteBatch) SetRoleAccessControllerAccount(roleAccessController ag_solanago.PublicKey) *ExecuteBatch {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(roleAccessController)
+ return inst
+}
+
+// GetRoleAccessControllerAccount gets the "roleAccessController" account.
+func (inst *ExecuteBatch) GetRoleAccessControllerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *ExecuteBatch) SetAuthorityAccount(authority ag_solanago.PublicKey) *ExecuteBatch {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *ExecuteBatch) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+func (inst ExecuteBatch) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_ExecuteBatch,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst ExecuteBatch) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *ExecuteBatch) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Id == nil {
+ return errors.New("Id parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.TimelockSigner is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Operation is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.PredecessorOperation is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.RoleAccessController is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *ExecuteBatch) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("ExecuteBatch")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Id", *inst.Id))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=6]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" timelockSigner", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" operation", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("predecessorOperation", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta("roleAccessController", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[5]))
+ })
+ })
+ })
+}
+
+func (obj ExecuteBatch) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Id` param:
+ err = encoder.Encode(obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *ExecuteBatch) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Id`:
+ err = decoder.Decode(&obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewExecuteBatchInstruction declares a new ExecuteBatch instruction with the provided parameters and accounts.
+func NewExecuteBatchInstruction(
+ // Parameters:
+ id [32]uint8,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ timelockSigner ag_solanago.PublicKey,
+ operation ag_solanago.PublicKey,
+ predecessorOperation ag_solanago.PublicKey,
+ roleAccessController ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *ExecuteBatch {
+ return NewExecuteBatchInstructionBuilder().
+ SetId(id).
+ SetConfigAccount(config).
+ SetTimelockSignerAccount(timelockSigner).
+ SetOperationAccount(operation).
+ SetPredecessorOperationAccount(predecessorOperation).
+ SetRoleAccessControllerAccount(roleAccessController).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/ExecuteBatch_test.go b/chains/solana/gobindings/timelock/ExecuteBatch_test.go
new file mode 100644
index 000000000..764cff47b
--- /dev/null
+++ b/chains/solana/gobindings/timelock/ExecuteBatch_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_ExecuteBatch(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("ExecuteBatch"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(ExecuteBatch)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(ExecuteBatch)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/FinalizeOperation.go b/chains/solana/gobindings/timelock/FinalizeOperation.go
new file mode 100644
index 000000000..8ea319110
--- /dev/null
+++ b/chains/solana/gobindings/timelock/FinalizeOperation.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// FinalizeOperation is the `finalizeOperation` instruction.
+type FinalizeOperation struct {
+ Id *[32]uint8
+
+ // [0] = [WRITE] operation
+ //
+ // [1] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewFinalizeOperationInstructionBuilder creates a new `FinalizeOperation` instruction builder.
+func NewFinalizeOperationInstructionBuilder() *FinalizeOperation {
+ nd := &FinalizeOperation{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetId sets the "id" parameter.
+func (inst *FinalizeOperation) SetId(id [32]uint8) *FinalizeOperation {
+ inst.Id = &id
+ return inst
+}
+
+// SetOperationAccount sets the "operation" account.
+func (inst *FinalizeOperation) SetOperationAccount(operation ag_solanago.PublicKey) *FinalizeOperation {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(operation).WRITE()
+ return inst
+}
+
+// GetOperationAccount gets the "operation" account.
+func (inst *FinalizeOperation) GetOperationAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *FinalizeOperation) SetAuthorityAccount(authority ag_solanago.PublicKey) *FinalizeOperation {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *FinalizeOperation) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst FinalizeOperation) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_FinalizeOperation,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst FinalizeOperation) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *FinalizeOperation) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Id == nil {
+ return errors.New("Id parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Operation is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *FinalizeOperation) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("FinalizeOperation")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Id", *inst.Id))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta("operation", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj FinalizeOperation) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Id` param:
+ err = encoder.Encode(obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *FinalizeOperation) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Id`:
+ err = decoder.Decode(&obj.Id)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewFinalizeOperationInstruction declares a new FinalizeOperation instruction with the provided parameters and accounts.
+func NewFinalizeOperationInstruction(
+ // Parameters:
+ id [32]uint8,
+ // Accounts:
+ operation ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *FinalizeOperation {
+ return NewFinalizeOperationInstructionBuilder().
+ SetId(id).
+ SetOperationAccount(operation).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/FinalizeOperation_test.go b/chains/solana/gobindings/timelock/FinalizeOperation_test.go
new file mode 100644
index 000000000..beaab44c2
--- /dev/null
+++ b/chains/solana/gobindings/timelock/FinalizeOperation_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_FinalizeOperation(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("FinalizeOperation"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(FinalizeOperation)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(FinalizeOperation)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/Initialize.go b/chains/solana/gobindings/timelock/Initialize.go
new file mode 100644
index 000000000..1ce4b0e0e
--- /dev/null
+++ b/chains/solana/gobindings/timelock/Initialize.go
@@ -0,0 +1,298 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Initialize is the `initialize` instruction.
+type Initialize struct {
+ MinDelay *uint64
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [WRITE, SIGNER] authority
+ //
+ // [2] = [] systemProgram
+ //
+ // [3] = [] program
+ //
+ // [4] = [] programData
+ //
+ // [5] = [] accessControllerProgram
+ //
+ // [6] = [] proposerRoleAccessController
+ //
+ // [7] = [] executorRoleAccessController
+ //
+ // [8] = [] cancellerRoleAccessController
+ //
+ // [9] = [] bypasserRoleAccessController
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitializeInstructionBuilder creates a new `Initialize` instruction builder.
+func NewInitializeInstructionBuilder() *Initialize {
+ nd := &Initialize{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 10),
+ }
+ return nd
+}
+
+// SetMinDelay sets the "minDelay" parameter.
+func (inst *Initialize) SetMinDelay(minDelay uint64) *Initialize {
+ inst.MinDelay = &minDelay
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *Initialize) SetConfigAccount(config ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *Initialize) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *Initialize) SetAuthorityAccount(authority ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *Initialize) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *Initialize) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *Initialize) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetProgramAccount sets the "program" account.
+func (inst *Initialize) SetProgramAccount(program ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(program)
+ return inst
+}
+
+// GetProgramAccount gets the "program" account.
+func (inst *Initialize) GetProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetProgramDataAccount sets the "programData" account.
+func (inst *Initialize) SetProgramDataAccount(programData ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(programData)
+ return inst
+}
+
+// GetProgramDataAccount gets the "programData" account.
+func (inst *Initialize) GetProgramDataAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetAccessControllerProgramAccount sets the "accessControllerProgram" account.
+func (inst *Initialize) SetAccessControllerProgramAccount(accessControllerProgram ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(accessControllerProgram)
+ return inst
+}
+
+// GetAccessControllerProgramAccount gets the "accessControllerProgram" account.
+func (inst *Initialize) GetAccessControllerProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetProposerRoleAccessControllerAccount sets the "proposerRoleAccessController" account.
+func (inst *Initialize) SetProposerRoleAccessControllerAccount(proposerRoleAccessController ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(proposerRoleAccessController)
+ return inst
+}
+
+// GetProposerRoleAccessControllerAccount gets the "proposerRoleAccessController" account.
+func (inst *Initialize) GetProposerRoleAccessControllerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+// SetExecutorRoleAccessControllerAccount sets the "executorRoleAccessController" account.
+func (inst *Initialize) SetExecutorRoleAccessControllerAccount(executorRoleAccessController ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[7] = ag_solanago.Meta(executorRoleAccessController)
+ return inst
+}
+
+// GetExecutorRoleAccessControllerAccount gets the "executorRoleAccessController" account.
+func (inst *Initialize) GetExecutorRoleAccessControllerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[7]
+}
+
+// SetCancellerRoleAccessControllerAccount sets the "cancellerRoleAccessController" account.
+func (inst *Initialize) SetCancellerRoleAccessControllerAccount(cancellerRoleAccessController ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[8] = ag_solanago.Meta(cancellerRoleAccessController)
+ return inst
+}
+
+// GetCancellerRoleAccessControllerAccount gets the "cancellerRoleAccessController" account.
+func (inst *Initialize) GetCancellerRoleAccessControllerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[8]
+}
+
+// SetBypasserRoleAccessControllerAccount sets the "bypasserRoleAccessController" account.
+func (inst *Initialize) SetBypasserRoleAccessControllerAccount(bypasserRoleAccessController ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[9] = ag_solanago.Meta(bypasserRoleAccessController)
+ return inst
+}
+
+// GetBypasserRoleAccessControllerAccount gets the "bypasserRoleAccessController" account.
+func (inst *Initialize) GetBypasserRoleAccessControllerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[9]
+}
+
+func (inst Initialize) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Initialize,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Initialize) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Initialize) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.MinDelay == nil {
+ return errors.New("MinDelay parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Program is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.ProgramData is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.AccessControllerProgram is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.ProposerRoleAccessController is not set")
+ }
+ if inst.AccountMetaSlice[7] == nil {
+ return errors.New("accounts.ExecutorRoleAccessController is not set")
+ }
+ if inst.AccountMetaSlice[8] == nil {
+ return errors.New("accounts.CancellerRoleAccessController is not set")
+ }
+ if inst.AccountMetaSlice[9] == nil {
+ return errors.New("accounts.BypasserRoleAccessController is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Initialize) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Initialize")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("MinDelay", *inst.MinDelay))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=10]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" program", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" programData", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" accessControllerProgram", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" proposerRoleAccessController", inst.AccountMetaSlice[6]))
+ accountsBranch.Child(ag_format.Meta(" executorRoleAccessController", inst.AccountMetaSlice[7]))
+ accountsBranch.Child(ag_format.Meta("cancellerRoleAccessController", inst.AccountMetaSlice[8]))
+ accountsBranch.Child(ag_format.Meta(" bypasserRoleAccessController", inst.AccountMetaSlice[9]))
+ })
+ })
+ })
+}
+
+func (obj Initialize) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `MinDelay` param:
+ err = encoder.Encode(obj.MinDelay)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `MinDelay`:
+ err = decoder.Decode(&obj.MinDelay)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewInitializeInstruction declares a new Initialize instruction with the provided parameters and accounts.
+func NewInitializeInstruction(
+ // Parameters:
+ minDelay uint64,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey,
+ program ag_solanago.PublicKey,
+ programData ag_solanago.PublicKey,
+ accessControllerProgram ag_solanago.PublicKey,
+ proposerRoleAccessController ag_solanago.PublicKey,
+ executorRoleAccessController ag_solanago.PublicKey,
+ cancellerRoleAccessController ag_solanago.PublicKey,
+ bypasserRoleAccessController ag_solanago.PublicKey) *Initialize {
+ return NewInitializeInstructionBuilder().
+ SetMinDelay(minDelay).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram).
+ SetProgramAccount(program).
+ SetProgramDataAccount(programData).
+ SetAccessControllerProgramAccount(accessControllerProgram).
+ SetProposerRoleAccessControllerAccount(proposerRoleAccessController).
+ SetExecutorRoleAccessControllerAccount(executorRoleAccessController).
+ SetCancellerRoleAccessControllerAccount(cancellerRoleAccessController).
+ SetBypasserRoleAccessControllerAccount(bypasserRoleAccessController)
+}
diff --git a/chains/solana/gobindings/timelock/InitializeOperation.go b/chains/solana/gobindings/timelock/InitializeOperation.go
new file mode 100644
index 000000000..0be4f9ae6
--- /dev/null
+++ b/chains/solana/gobindings/timelock/InitializeOperation.go
@@ -0,0 +1,272 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// InitializeOperation is the `initializeOperation` instruction.
+type InitializeOperation struct {
+ Id *[32]uint8
+ Predecessor *[32]uint8
+ Salt *[32]uint8
+ InstructionCount *uint32
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] operation
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] proposer
+ //
+ // [4] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitializeOperationInstructionBuilder creates a new `InitializeOperation` instruction builder.
+func NewInitializeOperationInstructionBuilder() *InitializeOperation {
+ nd := &InitializeOperation{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 5),
+ }
+ return nd
+}
+
+// SetId sets the "id" parameter.
+func (inst *InitializeOperation) SetId(id [32]uint8) *InitializeOperation {
+ inst.Id = &id
+ return inst
+}
+
+// SetPredecessor sets the "predecessor" parameter.
+func (inst *InitializeOperation) SetPredecessor(predecessor [32]uint8) *InitializeOperation {
+ inst.Predecessor = &predecessor
+ return inst
+}
+
+// SetSalt sets the "salt" parameter.
+func (inst *InitializeOperation) SetSalt(salt [32]uint8) *InitializeOperation {
+ inst.Salt = &salt
+ return inst
+}
+
+// SetInstructionCount sets the "instructionCount" parameter.
+func (inst *InitializeOperation) SetInstructionCount(instructionCount uint32) *InitializeOperation {
+ inst.InstructionCount = &instructionCount
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *InitializeOperation) SetConfigAccount(config ag_solanago.PublicKey) *InitializeOperation {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *InitializeOperation) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetOperationAccount sets the "operation" account.
+func (inst *InitializeOperation) SetOperationAccount(operation ag_solanago.PublicKey) *InitializeOperation {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(operation).WRITE()
+ return inst
+}
+
+// GetOperationAccount gets the "operation" account.
+func (inst *InitializeOperation) GetOperationAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *InitializeOperation) SetAuthorityAccount(authority ag_solanago.PublicKey) *InitializeOperation {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *InitializeOperation) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetProposerAccount sets the "proposer" account.
+func (inst *InitializeOperation) SetProposerAccount(proposer ag_solanago.PublicKey) *InitializeOperation {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(proposer)
+ return inst
+}
+
+// GetProposerAccount gets the "proposer" account.
+func (inst *InitializeOperation) GetProposerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *InitializeOperation) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *InitializeOperation {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *InitializeOperation) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+func (inst InitializeOperation) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_InitializeOperation,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst InitializeOperation) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *InitializeOperation) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Id == nil {
+ return errors.New("Id parameter is not set")
+ }
+ if inst.Predecessor == nil {
+ return errors.New("Predecessor parameter is not set")
+ }
+ if inst.Salt == nil {
+ return errors.New("Salt parameter is not set")
+ }
+ if inst.InstructionCount == nil {
+ return errors.New("InstructionCount parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Operation is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Proposer is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *InitializeOperation) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("InitializeOperation")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=4]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" Id", *inst.Id))
+ paramsBranch.Child(ag_format.Param(" Predecessor", *inst.Predecessor))
+ paramsBranch.Child(ag_format.Param(" Salt", *inst.Salt))
+ paramsBranch.Child(ag_format.Param("InstructionCount", *inst.InstructionCount))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=5]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" operation", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" proposer", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[4]))
+ })
+ })
+ })
+}
+
+func (obj InitializeOperation) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Id` param:
+ err = encoder.Encode(obj.Id)
+ if err != nil {
+ return err
+ }
+ // Serialize `Predecessor` param:
+ err = encoder.Encode(obj.Predecessor)
+ if err != nil {
+ return err
+ }
+ // Serialize `Salt` param:
+ err = encoder.Encode(obj.Salt)
+ if err != nil {
+ return err
+ }
+ // Serialize `InstructionCount` param:
+ err = encoder.Encode(obj.InstructionCount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *InitializeOperation) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Id`:
+ err = decoder.Decode(&obj.Id)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Predecessor`:
+ err = decoder.Decode(&obj.Predecessor)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Salt`:
+ err = decoder.Decode(&obj.Salt)
+ if err != nil {
+ return err
+ }
+ // Deserialize `InstructionCount`:
+ err = decoder.Decode(&obj.InstructionCount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewInitializeOperationInstruction declares a new InitializeOperation instruction with the provided parameters and accounts.
+func NewInitializeOperationInstruction(
+ // Parameters:
+ id [32]uint8,
+ predecessor [32]uint8,
+ salt [32]uint8,
+ instructionCount uint32,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ operation ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ proposer ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *InitializeOperation {
+ return NewInitializeOperationInstructionBuilder().
+ SetId(id).
+ SetPredecessor(predecessor).
+ SetSalt(salt).
+ SetInstructionCount(instructionCount).
+ SetConfigAccount(config).
+ SetOperationAccount(operation).
+ SetAuthorityAccount(authority).
+ SetProposerAccount(proposer).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/timelock/InitializeOperation_test.go b/chains/solana/gobindings/timelock/InitializeOperation_test.go
new file mode 100644
index 000000000..e4f51d024
--- /dev/null
+++ b/chains/solana/gobindings/timelock/InitializeOperation_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_InitializeOperation(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("InitializeOperation"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(InitializeOperation)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(InitializeOperation)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/Initialize_test.go b/chains/solana/gobindings/timelock/Initialize_test.go
new file mode 100644
index 000000000..ed2016d2e
--- /dev/null
+++ b/chains/solana/gobindings/timelock/Initialize_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Initialize(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Initialize"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Initialize)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Initialize)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/ScheduleBatch.go b/chains/solana/gobindings/timelock/ScheduleBatch.go
new file mode 100644
index 000000000..3f6a01ccc
--- /dev/null
+++ b/chains/solana/gobindings/timelock/ScheduleBatch.go
@@ -0,0 +1,207 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// ScheduleBatch is the `scheduleBatch` instruction.
+type ScheduleBatch struct {
+ Id *[32]uint8
+ Delay *uint64
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] operation
+ //
+ // [2] = [] roleAccessController
+ //
+ // [3] = [WRITE, SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewScheduleBatchInstructionBuilder creates a new `ScheduleBatch` instruction builder.
+func NewScheduleBatchInstructionBuilder() *ScheduleBatch {
+ nd := &ScheduleBatch{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetId sets the "id" parameter.
+func (inst *ScheduleBatch) SetId(id [32]uint8) *ScheduleBatch {
+ inst.Id = &id
+ return inst
+}
+
+// SetDelay sets the "delay" parameter.
+func (inst *ScheduleBatch) SetDelay(delay uint64) *ScheduleBatch {
+ inst.Delay = &delay
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *ScheduleBatch) SetConfigAccount(config ag_solanago.PublicKey) *ScheduleBatch {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *ScheduleBatch) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetOperationAccount sets the "operation" account.
+func (inst *ScheduleBatch) SetOperationAccount(operation ag_solanago.PublicKey) *ScheduleBatch {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(operation).WRITE()
+ return inst
+}
+
+// GetOperationAccount gets the "operation" account.
+func (inst *ScheduleBatch) GetOperationAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetRoleAccessControllerAccount sets the "roleAccessController" account.
+func (inst *ScheduleBatch) SetRoleAccessControllerAccount(roleAccessController ag_solanago.PublicKey) *ScheduleBatch {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(roleAccessController)
+ return inst
+}
+
+// GetRoleAccessControllerAccount gets the "roleAccessController" account.
+func (inst *ScheduleBatch) GetRoleAccessControllerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *ScheduleBatch) SetAuthorityAccount(authority ag_solanago.PublicKey) *ScheduleBatch {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *ScheduleBatch) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst ScheduleBatch) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_ScheduleBatch,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst ScheduleBatch) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *ScheduleBatch) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Id == nil {
+ return errors.New("Id parameter is not set")
+ }
+ if inst.Delay == nil {
+ return errors.New("Delay parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Operation is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.RoleAccessController is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *ScheduleBatch) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("ScheduleBatch")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" Id", *inst.Id))
+ paramsBranch.Child(ag_format.Param("Delay", *inst.Delay))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" operation", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("roleAccessController", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj ScheduleBatch) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Id` param:
+ err = encoder.Encode(obj.Id)
+ if err != nil {
+ return err
+ }
+ // Serialize `Delay` param:
+ err = encoder.Encode(obj.Delay)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *ScheduleBatch) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Id`:
+ err = decoder.Decode(&obj.Id)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Delay`:
+ err = decoder.Decode(&obj.Delay)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewScheduleBatchInstruction declares a new ScheduleBatch instruction with the provided parameters and accounts.
+func NewScheduleBatchInstruction(
+ // Parameters:
+ id [32]uint8,
+ delay uint64,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ operation ag_solanago.PublicKey,
+ roleAccessController ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *ScheduleBatch {
+ return NewScheduleBatchInstructionBuilder().
+ SetId(id).
+ SetDelay(delay).
+ SetConfigAccount(config).
+ SetOperationAccount(operation).
+ SetRoleAccessControllerAccount(roleAccessController).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/ScheduleBatch_test.go b/chains/solana/gobindings/timelock/ScheduleBatch_test.go
new file mode 100644
index 000000000..8fad35577
--- /dev/null
+++ b/chains/solana/gobindings/timelock/ScheduleBatch_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_ScheduleBatch(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("ScheduleBatch"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(ScheduleBatch)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(ScheduleBatch)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/TransferOwnership.go b/chains/solana/gobindings/timelock/TransferOwnership.go
new file mode 100644
index 000000000..1b596daf9
--- /dev/null
+++ b/chains/solana/gobindings/timelock/TransferOwnership.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// TransferOwnership is the `transferOwnership` instruction.
+type TransferOwnership struct {
+ ProposedOwner *ag_solanago.PublicKey
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewTransferOwnershipInstructionBuilder creates a new `TransferOwnership` instruction builder.
+func NewTransferOwnershipInstructionBuilder() *TransferOwnership {
+ nd := &TransferOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetProposedOwner sets the "proposedOwner" parameter.
+func (inst *TransferOwnership) SetProposedOwner(proposedOwner ag_solanago.PublicKey) *TransferOwnership {
+ inst.ProposedOwner = &proposedOwner
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *TransferOwnership) SetConfigAccount(config ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *TransferOwnership) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *TransferOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *TransferOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst TransferOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_TransferOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst TransferOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *TransferOwnership) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ProposedOwner == nil {
+ return errors.New("ProposedOwner parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *TransferOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("TransferOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("ProposedOwner", *inst.ProposedOwner))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj TransferOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *TransferOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewTransferOwnershipInstruction declares a new TransferOwnership instruction with the provided parameters and accounts.
+func NewTransferOwnershipInstruction(
+ // Parameters:
+ proposedOwner ag_solanago.PublicKey,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *TransferOwnership {
+ return NewTransferOwnershipInstructionBuilder().
+ SetProposedOwner(proposedOwner).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/TransferOwnership_test.go b/chains/solana/gobindings/timelock/TransferOwnership_test.go
new file mode 100644
index 000000000..288f41b3e
--- /dev/null
+++ b/chains/solana/gobindings/timelock/TransferOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_TransferOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("TransferOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(TransferOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(TransferOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/UnblockFunctionSelector.go b/chains/solana/gobindings/timelock/UnblockFunctionSelector.go
new file mode 100644
index 000000000..3ab751407
--- /dev/null
+++ b/chains/solana/gobindings/timelock/UnblockFunctionSelector.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// UnblockFunctionSelector is the `unblockFunctionSelector` instruction.
+type UnblockFunctionSelector struct {
+ Selector *[8]uint8
+
+ // [0] = [] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewUnblockFunctionSelectorInstructionBuilder creates a new `UnblockFunctionSelector` instruction builder.
+func NewUnblockFunctionSelectorInstructionBuilder() *UnblockFunctionSelector {
+ nd := &UnblockFunctionSelector{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetSelector sets the "selector" parameter.
+func (inst *UnblockFunctionSelector) SetSelector(selector [8]uint8) *UnblockFunctionSelector {
+ inst.Selector = &selector
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *UnblockFunctionSelector) SetConfigAccount(config ag_solanago.PublicKey) *UnblockFunctionSelector {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *UnblockFunctionSelector) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *UnblockFunctionSelector) SetAuthorityAccount(authority ag_solanago.PublicKey) *UnblockFunctionSelector {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *UnblockFunctionSelector) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst UnblockFunctionSelector) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_UnblockFunctionSelector,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst UnblockFunctionSelector) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *UnblockFunctionSelector) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Selector == nil {
+ return errors.New("Selector parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *UnblockFunctionSelector) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("UnblockFunctionSelector")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Selector", *inst.Selector))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj UnblockFunctionSelector) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Selector` param:
+ err = encoder.Encode(obj.Selector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *UnblockFunctionSelector) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Selector`:
+ err = decoder.Decode(&obj.Selector)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewUnblockFunctionSelectorInstruction declares a new UnblockFunctionSelector instruction with the provided parameters and accounts.
+func NewUnblockFunctionSelectorInstruction(
+ // Parameters:
+ selector [8]uint8,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *UnblockFunctionSelector {
+ return NewUnblockFunctionSelectorInstructionBuilder().
+ SetSelector(selector).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/UnblockFunctionSelector_test.go b/chains/solana/gobindings/timelock/UnblockFunctionSelector_test.go
new file mode 100644
index 000000000..0e2163774
--- /dev/null
+++ b/chains/solana/gobindings/timelock/UnblockFunctionSelector_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_UnblockFunctionSelector(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("UnblockFunctionSelector"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(UnblockFunctionSelector)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(UnblockFunctionSelector)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/UpdateDelay.go b/chains/solana/gobindings/timelock/UpdateDelay.go
new file mode 100644
index 000000000..f07a1d868
--- /dev/null
+++ b/chains/solana/gobindings/timelock/UpdateDelay.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// UpdateDelay is the `updateDelay` instruction.
+type UpdateDelay struct {
+ Delay *uint64
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewUpdateDelayInstructionBuilder creates a new `UpdateDelay` instruction builder.
+func NewUpdateDelayInstructionBuilder() *UpdateDelay {
+ nd := &UpdateDelay{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetDelay sets the "delay" parameter.
+func (inst *UpdateDelay) SetDelay(delay uint64) *UpdateDelay {
+ inst.Delay = &delay
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *UpdateDelay) SetConfigAccount(config ag_solanago.PublicKey) *UpdateDelay {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *UpdateDelay) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *UpdateDelay) SetAuthorityAccount(authority ag_solanago.PublicKey) *UpdateDelay {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *UpdateDelay) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst UpdateDelay) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_UpdateDelay,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst UpdateDelay) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *UpdateDelay) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.Delay == nil {
+ return errors.New("Delay parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *UpdateDelay) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("UpdateDelay")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("Delay", *inst.Delay))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj UpdateDelay) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Delay` param:
+ err = encoder.Encode(obj.Delay)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *UpdateDelay) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Delay`:
+ err = decoder.Decode(&obj.Delay)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewUpdateDelayInstruction declares a new UpdateDelay instruction with the provided parameters and accounts.
+func NewUpdateDelayInstruction(
+ // Parameters:
+ delay uint64,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *UpdateDelay {
+ return NewUpdateDelayInstructionBuilder().
+ SetDelay(delay).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/timelock/UpdateDelay_test.go b/chains/solana/gobindings/timelock/UpdateDelay_test.go
new file mode 100644
index 000000000..d99f24948
--- /dev/null
+++ b/chains/solana/gobindings/timelock/UpdateDelay_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_UpdateDelay(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("UpdateDelay"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(UpdateDelay)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(UpdateDelay)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/timelock/accounts.go b/chains/solana/gobindings/timelock/accounts.go
new file mode 100644
index 000000000..15468da0b
--- /dev/null
+++ b/chains/solana/gobindings/timelock/accounts.go
@@ -0,0 +1,236 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type Config struct {
+ Owner ag_solanago.PublicKey
+ ProposedOwner ag_solanago.PublicKey
+ ProposerRoleAccessController ag_solanago.PublicKey
+ ExecutorRoleAccessController ag_solanago.PublicKey
+ CancellerRoleAccessController ag_solanago.PublicKey
+ BypasserRoleAccessController ag_solanago.PublicKey
+ MinDelay uint64
+}
+
+var ConfigDiscriminator = [8]byte{155, 12, 170, 224, 30, 250, 204, 130}
+
+func (obj Config) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ConfigDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Owner` param:
+ err = encoder.Encode(obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Serialize `ProposerRoleAccessController` param:
+ err = encoder.Encode(obj.ProposerRoleAccessController)
+ if err != nil {
+ return err
+ }
+ // Serialize `ExecutorRoleAccessController` param:
+ err = encoder.Encode(obj.ExecutorRoleAccessController)
+ if err != nil {
+ return err
+ }
+ // Serialize `CancellerRoleAccessController` param:
+ err = encoder.Encode(obj.CancellerRoleAccessController)
+ if err != nil {
+ return err
+ }
+ // Serialize `BypasserRoleAccessController` param:
+ err = encoder.Encode(obj.BypasserRoleAccessController)
+ if err != nil {
+ return err
+ }
+ // Serialize `MinDelay` param:
+ err = encoder.Encode(obj.MinDelay)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Config) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ConfigDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[155 12 170 224 30 250 204 130]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Owner`:
+ err = decoder.Decode(&obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ProposerRoleAccessController`:
+ err = decoder.Decode(&obj.ProposerRoleAccessController)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ExecutorRoleAccessController`:
+ err = decoder.Decode(&obj.ExecutorRoleAccessController)
+ if err != nil {
+ return err
+ }
+ // Deserialize `CancellerRoleAccessController`:
+ err = decoder.Decode(&obj.CancellerRoleAccessController)
+ if err != nil {
+ return err
+ }
+ // Deserialize `BypasserRoleAccessController`:
+ err = decoder.Decode(&obj.BypasserRoleAccessController)
+ if err != nil {
+ return err
+ }
+ // Deserialize `MinDelay`:
+ err = decoder.Decode(&obj.MinDelay)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type Operation struct {
+ Timestamp uint64
+ Id [32]uint8
+ Predecessor [32]uint8
+ Salt [32]uint8
+ Authority ag_solanago.PublicKey
+ IsFinalized bool
+ TotalInstructions uint32
+ Instructions []InstructionData
+}
+
+var OperationDiscriminator = [8]byte{171, 150, 196, 17, 229, 166, 58, 44}
+
+func (obj Operation) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(OperationDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Timestamp` param:
+ err = encoder.Encode(obj.Timestamp)
+ if err != nil {
+ return err
+ }
+ // Serialize `Id` param:
+ err = encoder.Encode(obj.Id)
+ if err != nil {
+ return err
+ }
+ // Serialize `Predecessor` param:
+ err = encoder.Encode(obj.Predecessor)
+ if err != nil {
+ return err
+ }
+ // Serialize `Salt` param:
+ err = encoder.Encode(obj.Salt)
+ if err != nil {
+ return err
+ }
+ // Serialize `Authority` param:
+ err = encoder.Encode(obj.Authority)
+ if err != nil {
+ return err
+ }
+ // Serialize `IsFinalized` param:
+ err = encoder.Encode(obj.IsFinalized)
+ if err != nil {
+ return err
+ }
+ // Serialize `TotalInstructions` param:
+ err = encoder.Encode(obj.TotalInstructions)
+ if err != nil {
+ return err
+ }
+ // Serialize `Instructions` param:
+ err = encoder.Encode(obj.Instructions)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Operation) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(OperationDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[171 150 196 17 229 166 58 44]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Timestamp`:
+ err = decoder.Decode(&obj.Timestamp)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Id`:
+ err = decoder.Decode(&obj.Id)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Predecessor`:
+ err = decoder.Decode(&obj.Predecessor)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Salt`:
+ err = decoder.Decode(&obj.Salt)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Authority`:
+ err = decoder.Decode(&obj.Authority)
+ if err != nil {
+ return err
+ }
+ // Deserialize `IsFinalized`:
+ err = decoder.Decode(&obj.IsFinalized)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TotalInstructions`:
+ err = decoder.Decode(&obj.TotalInstructions)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Instructions`:
+ err = decoder.Decode(&obj.Instructions)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/timelock/instructions.go b/chains/solana/gobindings/timelock/instructions.go
new file mode 100644
index 000000000..79d940306
--- /dev/null
+++ b/chains/solana/gobindings/timelock/instructions.go
@@ -0,0 +1,215 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ "fmt"
+ ag_spew "github.com/davecgh/go-spew/spew"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_text "github.com/gagliardetto/solana-go/text"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+var ProgramID ag_solanago.PublicKey
+
+func SetProgramID(pubkey ag_solanago.PublicKey) {
+ ProgramID = pubkey
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+}
+
+const ProgramName = "Timelock"
+
+func init() {
+ if !ProgramID.IsZero() {
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+ }
+}
+
+var (
+ Instruction_Initialize = ag_binary.TypeID([8]byte{175, 175, 109, 31, 13, 152, 155, 237})
+
+ Instruction_BatchAddAccess = ag_binary.TypeID([8]byte{73, 141, 223, 79, 66, 154, 226, 67})
+
+ Instruction_ScheduleBatch = ag_binary.TypeID([8]byte{242, 140, 87, 106, 71, 226, 86, 32})
+
+ Instruction_InitializeOperation = ag_binary.TypeID([8]byte{15, 96, 217, 171, 124, 4, 113, 243})
+
+ Instruction_AppendInstructions = ag_binary.TypeID([8]byte{58, 58, 137, 122, 115, 51, 144, 134})
+
+ Instruction_ClearOperation = ag_binary.TypeID([8]byte{111, 217, 62, 240, 224, 75, 60, 58})
+
+ Instruction_FinalizeOperation = ag_binary.TypeID([8]byte{63, 208, 32, 98, 85, 182, 236, 140})
+
+ Instruction_Cancel = ag_binary.TypeID([8]byte{232, 219, 223, 41, 219, 236, 220, 190})
+
+ Instruction_ExecuteBatch = ag_binary.TypeID([8]byte{112, 159, 211, 51, 238, 70, 212, 60})
+
+ Instruction_BypasserExecuteBatch = ag_binary.TypeID([8]byte{90, 62, 66, 6, 227, 174, 30, 194})
+
+ Instruction_UpdateDelay = ag_binary.TypeID([8]byte{164, 186, 80, 62, 85, 88, 182, 147})
+
+ Instruction_BlockFunctionSelector = ag_binary.TypeID([8]byte{119, 89, 101, 41, 72, 143, 218, 185})
+
+ Instruction_UnblockFunctionSelector = ag_binary.TypeID([8]byte{53, 84, 245, 196, 149, 52, 30, 57})
+
+ Instruction_TransferOwnership = ag_binary.TypeID([8]byte{65, 177, 215, 73, 53, 45, 99, 47})
+
+ Instruction_AcceptOwnership = ag_binary.TypeID([8]byte{172, 23, 43, 13, 238, 213, 85, 150})
+)
+
+// InstructionIDToName returns the name of the instruction given its ID.
+func InstructionIDToName(id ag_binary.TypeID) string {
+ switch id {
+ case Instruction_Initialize:
+ return "Initialize"
+ case Instruction_BatchAddAccess:
+ return "BatchAddAccess"
+ case Instruction_ScheduleBatch:
+ return "ScheduleBatch"
+ case Instruction_InitializeOperation:
+ return "InitializeOperation"
+ case Instruction_AppendInstructions:
+ return "AppendInstructions"
+ case Instruction_ClearOperation:
+ return "ClearOperation"
+ case Instruction_FinalizeOperation:
+ return "FinalizeOperation"
+ case Instruction_Cancel:
+ return "Cancel"
+ case Instruction_ExecuteBatch:
+ return "ExecuteBatch"
+ case Instruction_BypasserExecuteBatch:
+ return "BypasserExecuteBatch"
+ case Instruction_UpdateDelay:
+ return "UpdateDelay"
+ case Instruction_BlockFunctionSelector:
+ return "BlockFunctionSelector"
+ case Instruction_UnblockFunctionSelector:
+ return "UnblockFunctionSelector"
+ case Instruction_TransferOwnership:
+ return "TransferOwnership"
+ case Instruction_AcceptOwnership:
+ return "AcceptOwnership"
+ default:
+ return ""
+ }
+}
+
+type Instruction struct {
+ ag_binary.BaseVariant
+}
+
+func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) {
+ if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok {
+ enToTree.EncodeToTree(parent)
+ } else {
+ parent.Child(ag_spew.Sdump(inst))
+ }
+}
+
+var InstructionImplDef = ag_binary.NewVariantDefinition(
+ ag_binary.AnchorTypeIDEncoding,
+ []ag_binary.VariantType{
+ {
+ "initialize", (*Initialize)(nil),
+ },
+ {
+ "batch_add_access", (*BatchAddAccess)(nil),
+ },
+ {
+ "schedule_batch", (*ScheduleBatch)(nil),
+ },
+ {
+ "initialize_operation", (*InitializeOperation)(nil),
+ },
+ {
+ "append_instructions", (*AppendInstructions)(nil),
+ },
+ {
+ "clear_operation", (*ClearOperation)(nil),
+ },
+ {
+ "finalize_operation", (*FinalizeOperation)(nil),
+ },
+ {
+ "cancel", (*Cancel)(nil),
+ },
+ {
+ "execute_batch", (*ExecuteBatch)(nil),
+ },
+ {
+ "bypasser_execute_batch", (*BypasserExecuteBatch)(nil),
+ },
+ {
+ "update_delay", (*UpdateDelay)(nil),
+ },
+ {
+ "block_function_selector", (*BlockFunctionSelector)(nil),
+ },
+ {
+ "unblock_function_selector", (*UnblockFunctionSelector)(nil),
+ },
+ {
+ "transfer_ownership", (*TransferOwnership)(nil),
+ },
+ {
+ "accept_ownership", (*AcceptOwnership)(nil),
+ },
+ },
+)
+
+func (inst *Instruction) ProgramID() ag_solanago.PublicKey {
+ return ProgramID
+}
+
+func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) {
+ return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts()
+}
+
+func (inst *Instruction) Data() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil {
+ return nil, fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return buf.Bytes(), nil
+}
+
+func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
+ return encoder.Encode(inst.Impl, option)
+}
+
+func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
+ return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
+}
+
+func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
+ err := encoder.WriteBytes(inst.TypeID.Bytes(), false)
+ if err != nil {
+ return fmt.Errorf("unable to write variant type: %w", err)
+ }
+ return encoder.Encode(inst.Impl)
+}
+
+func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) {
+ inst, err := DecodeInstruction(accounts, data)
+ if err != nil {
+ return nil, err
+ }
+ return inst, nil
+}
+
+func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) {
+ inst := new(Instruction)
+ if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil {
+ return nil, fmt.Errorf("unable to decode instruction: %w", err)
+ }
+ if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok {
+ err := v.SetAccounts(accounts)
+ if err != nil {
+ return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
+ }
+ }
+ return inst, nil
+}
diff --git a/chains/solana/gobindings/timelock/testing_utils.go b/chains/solana/gobindings/timelock/testing_utils.go
new file mode 100644
index 000000000..46eff47cd
--- /dev/null
+++ b/chains/solana/gobindings/timelock/testing_utils.go
@@ -0,0 +1,20 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ "bytes"
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+func encodeT(data interface{}, buf *bytes.Buffer) error {
+ if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil {
+ return fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return nil
+}
+
+func decodeT(dst interface{}, data []byte) error {
+ return ag_binary.NewBorshDecoder(data).Decode(dst)
+}
diff --git a/chains/solana/gobindings/timelock/types.go b/chains/solana/gobindings/timelock/types.go
new file mode 100644
index 000000000..ff1d6e11e
--- /dev/null
+++ b/chains/solana/gobindings/timelock/types.go
@@ -0,0 +1,177 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package timelock
+
+import (
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type InstructionData struct {
+ ProgramId ag_solanago.PublicKey
+ Data []byte
+ Accounts []InstructionAccount
+}
+
+func (obj InstructionData) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ProgramId` param:
+ err = encoder.Encode(obj.ProgramId)
+ if err != nil {
+ return err
+ }
+ // Serialize `Data` param:
+ err = encoder.Encode(obj.Data)
+ if err != nil {
+ return err
+ }
+ // Serialize `Accounts` param:
+ err = encoder.Encode(obj.Accounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *InstructionData) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ProgramId`:
+ err = decoder.Decode(&obj.ProgramId)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Data`:
+ err = decoder.Decode(&obj.Data)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Accounts`:
+ err = decoder.Decode(&obj.Accounts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type InstructionAccount struct {
+ Pubkey ag_solanago.PublicKey
+ IsSigner bool
+ IsWritable bool
+}
+
+func (obj InstructionAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Pubkey` param:
+ err = encoder.Encode(obj.Pubkey)
+ if err != nil {
+ return err
+ }
+ // Serialize `IsSigner` param:
+ err = encoder.Encode(obj.IsSigner)
+ if err != nil {
+ return err
+ }
+ // Serialize `IsWritable` param:
+ err = encoder.Encode(obj.IsWritable)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *InstructionAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Pubkey`:
+ err = decoder.Decode(&obj.Pubkey)
+ if err != nil {
+ return err
+ }
+ // Deserialize `IsSigner`:
+ err = decoder.Decode(&obj.IsSigner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `IsWritable`:
+ err = decoder.Decode(&obj.IsWritable)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type TimelockError ag_binary.BorshEnum
+
+const (
+ Unauthorized_TimelockError TimelockError = iota
+ InvalidInput_TimelockError
+ Overflow_TimelockError
+ InvalidId_TimelockError
+ OperationNotFinalized_TimelockError
+ OperationAlreadyFinalized_TimelockError
+ TooManyInstructions_TimelockError
+ OperationAlreadyScheduled_TimelockError
+ DelayInsufficient_TimelockError
+ OperationNotCancellable_TimelockError
+ OperationNotReady_TimelockError
+ MissingDependency_TimelockError
+ BlockedSelector_TimelockError
+ InvalidAccessController_TimelockError
+)
+
+func (value TimelockError) String() string {
+ switch value {
+ case Unauthorized_TimelockError:
+ return "Unauthorized"
+ case InvalidInput_TimelockError:
+ return "InvalidInput"
+ case Overflow_TimelockError:
+ return "Overflow"
+ case InvalidId_TimelockError:
+ return "InvalidId"
+ case OperationNotFinalized_TimelockError:
+ return "OperationNotFinalized"
+ case OperationAlreadyFinalized_TimelockError:
+ return "OperationAlreadyFinalized"
+ case TooManyInstructions_TimelockError:
+ return "TooManyInstructions"
+ case OperationAlreadyScheduled_TimelockError:
+ return "OperationAlreadyScheduled"
+ case DelayInsufficient_TimelockError:
+ return "DelayInsufficient"
+ case OperationNotCancellable_TimelockError:
+ return "OperationNotCancellable"
+ case OperationNotReady_TimelockError:
+ return "OperationNotReady"
+ case MissingDependency_TimelockError:
+ return "MissingDependency"
+ case BlockedSelector_TimelockError:
+ return "BlockedSelector"
+ case InvalidAccessController_TimelockError:
+ return "InvalidAccessController"
+ default:
+ return ""
+ }
+}
+
+type Role ag_binary.BorshEnum
+
+const (
+ Admin_Role Role = iota
+ Proposer_Role
+ Executor_Role
+ Canceller_Role
+ Bypasser_Role
+)
+
+func (value Role) String() string {
+ switch value {
+ case Admin_Role:
+ return "Admin"
+ case Proposer_Role:
+ return "Proposer"
+ case Executor_Role:
+ return "Executor"
+ case Canceller_Role:
+ return "Canceller"
+ case Bypasser_Role:
+ return "Bypasser"
+ default:
+ return ""
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/AcceptOwnership.go b/chains/solana/gobindings/token_pool/AcceptOwnership.go
new file mode 100644
index 000000000..c082a5e86
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/AcceptOwnership.go
@@ -0,0 +1,117 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// AcceptOwnership is the `acceptOwnership` instruction.
+type AcceptOwnership struct {
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewAcceptOwnershipInstructionBuilder creates a new `AcceptOwnership` instruction builder.
+func NewAcceptOwnershipInstructionBuilder() *AcceptOwnership {
+ nd := &AcceptOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *AcceptOwnership) SetConfigAccount(config ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *AcceptOwnership) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *AcceptOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *AcceptOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *AcceptOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst AcceptOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_AcceptOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst AcceptOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *AcceptOwnership) Validate() error {
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *AcceptOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("AcceptOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {})
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj AcceptOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ return nil
+}
+func (obj *AcceptOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ return nil
+}
+
+// NewAcceptOwnershipInstruction declares a new AcceptOwnership instruction with the provided parameters and accounts.
+func NewAcceptOwnershipInstruction(
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *AcceptOwnership {
+ return NewAcceptOwnershipInstructionBuilder().
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/token_pool/AcceptOwnership_test.go b/chains/solana/gobindings/token_pool/AcceptOwnership_test.go
new file mode 100644
index 000000000..1b51004e0
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/AcceptOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_AcceptOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("AcceptOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(AcceptOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(AcceptOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/DeleteChainConfig.go b/chains/solana/gobindings/token_pool/DeleteChainConfig.go
new file mode 100644
index 000000000..8236ee088
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/DeleteChainConfig.go
@@ -0,0 +1,207 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// DeleteChainConfig is the `deleteChainConfig` instruction.
+type DeleteChainConfig struct {
+ RemoteChainSelector *uint64
+ Mint *ag_solanago.PublicKey
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] chainConfig
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewDeleteChainConfigInstructionBuilder creates a new `DeleteChainConfig` instruction builder.
+func NewDeleteChainConfigInstructionBuilder() *DeleteChainConfig {
+ nd := &DeleteChainConfig{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetRemoteChainSelector sets the "remoteChainSelector" parameter.
+func (inst *DeleteChainConfig) SetRemoteChainSelector(remoteChainSelector uint64) *DeleteChainConfig {
+ inst.RemoteChainSelector = &remoteChainSelector
+ return inst
+}
+
+// SetMint sets the "mint" parameter.
+func (inst *DeleteChainConfig) SetMint(mint ag_solanago.PublicKey) *DeleteChainConfig {
+ inst.Mint = &mint
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *DeleteChainConfig) SetConfigAccount(config ag_solanago.PublicKey) *DeleteChainConfig {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *DeleteChainConfig) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetChainConfigAccount sets the "chainConfig" account.
+func (inst *DeleteChainConfig) SetChainConfigAccount(chainConfig ag_solanago.PublicKey) *DeleteChainConfig {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(chainConfig).WRITE()
+ return inst
+}
+
+// GetChainConfigAccount gets the "chainConfig" account.
+func (inst *DeleteChainConfig) GetChainConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *DeleteChainConfig) SetAuthorityAccount(authority ag_solanago.PublicKey) *DeleteChainConfig {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *DeleteChainConfig) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *DeleteChainConfig) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *DeleteChainConfig {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *DeleteChainConfig) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst DeleteChainConfig) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_DeleteChainConfig,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst DeleteChainConfig) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *DeleteChainConfig) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.RemoteChainSelector == nil {
+ return errors.New("RemoteChainSelector parameter is not set")
+ }
+ if inst.Mint == nil {
+ return errors.New("Mint parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ChainConfig is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *DeleteChainConfig) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("DeleteChainConfig")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("RemoteChainSelector", *inst.RemoteChainSelector))
+ paramsBranch.Child(ag_format.Param(" Mint", *inst.Mint))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" chainConfig", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj DeleteChainConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `RemoteChainSelector` param:
+ err = encoder.Encode(obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *DeleteChainConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `RemoteChainSelector`:
+ err = decoder.Decode(&obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewDeleteChainConfigInstruction declares a new DeleteChainConfig instruction with the provided parameters and accounts.
+func NewDeleteChainConfigInstruction(
+ // Parameters:
+ remoteChainSelector uint64,
+ mint ag_solanago.PublicKey,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ chainConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *DeleteChainConfig {
+ return NewDeleteChainConfigInstructionBuilder().
+ SetRemoteChainSelector(remoteChainSelector).
+ SetMint(mint).
+ SetConfigAccount(config).
+ SetChainConfigAccount(chainConfig).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/token_pool/DeleteChainConfig_test.go b/chains/solana/gobindings/token_pool/DeleteChainConfig_test.go
new file mode 100644
index 000000000..f3a65e496
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/DeleteChainConfig_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_DeleteChainConfig(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("DeleteChainConfig"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(DeleteChainConfig)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(DeleteChainConfig)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/Initialize.go b/chains/solana/gobindings/token_pool/Initialize.go
new file mode 100644
index 000000000..a90472d6e
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/Initialize.go
@@ -0,0 +1,226 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// Initialize is the `initialize` instruction.
+type Initialize struct {
+ PoolType *PoolType
+ RampAuthority *ag_solanago.PublicKey
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [] mint
+ //
+ // [2] = [WRITE] poolSigner
+ //
+ // [3] = [WRITE, SIGNER] authority
+ //
+ // [4] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewInitializeInstructionBuilder creates a new `Initialize` instruction builder.
+func NewInitializeInstructionBuilder() *Initialize {
+ nd := &Initialize{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 5),
+ }
+ return nd
+}
+
+// SetPoolType sets the "poolType" parameter.
+func (inst *Initialize) SetPoolType(poolType PoolType) *Initialize {
+ inst.PoolType = &poolType
+ return inst
+}
+
+// SetRampAuthority sets the "rampAuthority" parameter.
+func (inst *Initialize) SetRampAuthority(rampAuthority ag_solanago.PublicKey) *Initialize {
+ inst.RampAuthority = &rampAuthority
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *Initialize) SetConfigAccount(config ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *Initialize) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetMintAccount sets the "mint" account.
+func (inst *Initialize) SetMintAccount(mint ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(mint)
+ return inst
+}
+
+// GetMintAccount gets the "mint" account.
+func (inst *Initialize) GetMintAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetPoolSignerAccount sets the "poolSigner" account.
+func (inst *Initialize) SetPoolSignerAccount(poolSigner ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(poolSigner).WRITE()
+ return inst
+}
+
+// GetPoolSignerAccount gets the "poolSigner" account.
+func (inst *Initialize) GetPoolSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *Initialize) SetAuthorityAccount(authority ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *Initialize) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *Initialize) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Initialize {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *Initialize) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+func (inst Initialize) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_Initialize,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst Initialize) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *Initialize) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.PoolType == nil {
+ return errors.New("PoolType parameter is not set")
+ }
+ if inst.RampAuthority == nil {
+ return errors.New("RampAuthority parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Mint is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.PoolSigner is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *Initialize) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("Initialize")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param(" PoolType", *inst.PoolType))
+ paramsBranch.Child(ag_format.Param("RampAuthority", *inst.RampAuthority))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=5]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" poolSigner", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[4]))
+ })
+ })
+ })
+}
+
+func (obj Initialize) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `PoolType` param:
+ err = encoder.Encode(obj.PoolType)
+ if err != nil {
+ return err
+ }
+ // Serialize `RampAuthority` param:
+ err = encoder.Encode(obj.RampAuthority)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `PoolType`:
+ err = decoder.Decode(&obj.PoolType)
+ if err != nil {
+ return err
+ }
+ // Deserialize `RampAuthority`:
+ err = decoder.Decode(&obj.RampAuthority)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewInitializeInstruction declares a new Initialize instruction with the provided parameters and accounts.
+func NewInitializeInstruction(
+ // Parameters:
+ poolType PoolType,
+ rampAuthority ag_solanago.PublicKey,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ mint ag_solanago.PublicKey,
+ poolSigner ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *Initialize {
+ return NewInitializeInstructionBuilder().
+ SetPoolType(poolType).
+ SetRampAuthority(rampAuthority).
+ SetConfigAccount(config).
+ SetMintAccount(mint).
+ SetPoolSignerAccount(poolSigner).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/token_pool/Initialize_test.go b/chains/solana/gobindings/token_pool/Initialize_test.go
new file mode 100644
index 000000000..539246dec
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/Initialize_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_Initialize(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("Initialize"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(Initialize)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(Initialize)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/LockOrBurnTokens.go b/chains/solana/gobindings/token_pool/LockOrBurnTokens.go
new file mode 100644
index 000000000..797ef21b1
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/LockOrBurnTokens.go
@@ -0,0 +1,241 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// LockOrBurnTokens is the `lockOrBurnTokens` instruction.
+type LockOrBurnTokens struct {
+ LockOrBurn *LockOrBurnInV1
+
+ // [0] = [SIGNER] authority
+ //
+ // [1] = [WRITE] config
+ //
+ // [2] = [] tokenProgram
+ //
+ // [3] = [WRITE] mint
+ //
+ // [4] = [] poolSigner
+ //
+ // [5] = [WRITE] poolTokenAccount
+ //
+ // [6] = [WRITE] chainConfig
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewLockOrBurnTokensInstructionBuilder creates a new `LockOrBurnTokens` instruction builder.
+func NewLockOrBurnTokensInstructionBuilder() *LockOrBurnTokens {
+ nd := &LockOrBurnTokens{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 7),
+ }
+ return nd
+}
+
+// SetLockOrBurn sets the "lockOrBurn" parameter.
+func (inst *LockOrBurnTokens) SetLockOrBurn(lockOrBurn LockOrBurnInV1) *LockOrBurnTokens {
+ inst.LockOrBurn = &lockOrBurn
+ return inst
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *LockOrBurnTokens) SetAuthorityAccount(authority ag_solanago.PublicKey) *LockOrBurnTokens {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *LockOrBurnTokens) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *LockOrBurnTokens) SetConfigAccount(config ag_solanago.PublicKey) *LockOrBurnTokens {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *LockOrBurnTokens) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetTokenProgramAccount sets the "tokenProgram" account.
+func (inst *LockOrBurnTokens) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *LockOrBurnTokens {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(tokenProgram)
+ return inst
+}
+
+// GetTokenProgramAccount gets the "tokenProgram" account.
+func (inst *LockOrBurnTokens) GetTokenProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetMintAccount sets the "mint" account.
+func (inst *LockOrBurnTokens) SetMintAccount(mint ag_solanago.PublicKey) *LockOrBurnTokens {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(mint).WRITE()
+ return inst
+}
+
+// GetMintAccount gets the "mint" account.
+func (inst *LockOrBurnTokens) GetMintAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetPoolSignerAccount sets the "poolSigner" account.
+func (inst *LockOrBurnTokens) SetPoolSignerAccount(poolSigner ag_solanago.PublicKey) *LockOrBurnTokens {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(poolSigner)
+ return inst
+}
+
+// GetPoolSignerAccount gets the "poolSigner" account.
+func (inst *LockOrBurnTokens) GetPoolSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetPoolTokenAccountAccount sets the "poolTokenAccount" account.
+func (inst *LockOrBurnTokens) SetPoolTokenAccountAccount(poolTokenAccount ag_solanago.PublicKey) *LockOrBurnTokens {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(poolTokenAccount).WRITE()
+ return inst
+}
+
+// GetPoolTokenAccountAccount gets the "poolTokenAccount" account.
+func (inst *LockOrBurnTokens) GetPoolTokenAccountAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetChainConfigAccount sets the "chainConfig" account.
+func (inst *LockOrBurnTokens) SetChainConfigAccount(chainConfig ag_solanago.PublicKey) *LockOrBurnTokens {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(chainConfig).WRITE()
+ return inst
+}
+
+// GetChainConfigAccount gets the "chainConfig" account.
+func (inst *LockOrBurnTokens) GetChainConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+func (inst LockOrBurnTokens) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_LockOrBurnTokens,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst LockOrBurnTokens) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *LockOrBurnTokens) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.LockOrBurn == nil {
+ return errors.New("LockOrBurn parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.TokenProgram is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Mint is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.PoolSigner is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.PoolTokenAccount is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.ChainConfig is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *LockOrBurnTokens) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("LockOrBurnTokens")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("LockOrBurn", *inst.LockOrBurn))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=7]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta("tokenProgram", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" poolSigner", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" poolToken", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" chainConfig", inst.AccountMetaSlice[6]))
+ })
+ })
+ })
+}
+
+func (obj LockOrBurnTokens) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `LockOrBurn` param:
+ err = encoder.Encode(obj.LockOrBurn)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *LockOrBurnTokens) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `LockOrBurn`:
+ err = decoder.Decode(&obj.LockOrBurn)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewLockOrBurnTokensInstruction declares a new LockOrBurnTokens instruction with the provided parameters and accounts.
+func NewLockOrBurnTokensInstruction(
+ // Parameters:
+ lockOrBurn LockOrBurnInV1,
+ // Accounts:
+ authority ag_solanago.PublicKey,
+ config ag_solanago.PublicKey,
+ tokenProgram ag_solanago.PublicKey,
+ mint ag_solanago.PublicKey,
+ poolSigner ag_solanago.PublicKey,
+ poolTokenAccount ag_solanago.PublicKey,
+ chainConfig ag_solanago.PublicKey) *LockOrBurnTokens {
+ return NewLockOrBurnTokensInstructionBuilder().
+ SetLockOrBurn(lockOrBurn).
+ SetAuthorityAccount(authority).
+ SetConfigAccount(config).
+ SetTokenProgramAccount(tokenProgram).
+ SetMintAccount(mint).
+ SetPoolSignerAccount(poolSigner).
+ SetPoolTokenAccountAccount(poolTokenAccount).
+ SetChainConfigAccount(chainConfig)
+}
diff --git a/chains/solana/gobindings/token_pool/LockOrBurnTokens_test.go b/chains/solana/gobindings/token_pool/LockOrBurnTokens_test.go
new file mode 100644
index 000000000..757309061
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/LockOrBurnTokens_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_LockOrBurnTokens(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("LockOrBurnTokens"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(LockOrBurnTokens)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(LockOrBurnTokens)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/ReleaseOrMintTokens.go b/chains/solana/gobindings/token_pool/ReleaseOrMintTokens.go
new file mode 100644
index 000000000..65e24721b
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/ReleaseOrMintTokens.go
@@ -0,0 +1,260 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// ReleaseOrMintTokens is the `releaseOrMintTokens` instruction.
+type ReleaseOrMintTokens struct {
+ ReleaseOrMint *ReleaseOrMintInV1
+
+ // [0] = [SIGNER] authority
+ //
+ // [1] = [WRITE] config
+ //
+ // [2] = [] tokenProgram
+ //
+ // [3] = [WRITE] mint
+ //
+ // [4] = [] poolSigner
+ //
+ // [5] = [WRITE] poolTokenAccount
+ //
+ // [6] = [WRITE] chainConfig
+ //
+ // [7] = [WRITE] receiverTokenAccount
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewReleaseOrMintTokensInstructionBuilder creates a new `ReleaseOrMintTokens` instruction builder.
+func NewReleaseOrMintTokensInstructionBuilder() *ReleaseOrMintTokens {
+ nd := &ReleaseOrMintTokens{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 8),
+ }
+ return nd
+}
+
+// SetReleaseOrMint sets the "releaseOrMint" parameter.
+func (inst *ReleaseOrMintTokens) SetReleaseOrMint(releaseOrMint ReleaseOrMintInV1) *ReleaseOrMintTokens {
+ inst.ReleaseOrMint = &releaseOrMint
+ return inst
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *ReleaseOrMintTokens) SetAuthorityAccount(authority ag_solanago.PublicKey) *ReleaseOrMintTokens {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *ReleaseOrMintTokens) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *ReleaseOrMintTokens) SetConfigAccount(config ag_solanago.PublicKey) *ReleaseOrMintTokens {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *ReleaseOrMintTokens) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetTokenProgramAccount sets the "tokenProgram" account.
+func (inst *ReleaseOrMintTokens) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *ReleaseOrMintTokens {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(tokenProgram)
+ return inst
+}
+
+// GetTokenProgramAccount gets the "tokenProgram" account.
+func (inst *ReleaseOrMintTokens) GetTokenProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetMintAccount sets the "mint" account.
+func (inst *ReleaseOrMintTokens) SetMintAccount(mint ag_solanago.PublicKey) *ReleaseOrMintTokens {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(mint).WRITE()
+ return inst
+}
+
+// GetMintAccount gets the "mint" account.
+func (inst *ReleaseOrMintTokens) GetMintAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+// SetPoolSignerAccount sets the "poolSigner" account.
+func (inst *ReleaseOrMintTokens) SetPoolSignerAccount(poolSigner ag_solanago.PublicKey) *ReleaseOrMintTokens {
+ inst.AccountMetaSlice[4] = ag_solanago.Meta(poolSigner)
+ return inst
+}
+
+// GetPoolSignerAccount gets the "poolSigner" account.
+func (inst *ReleaseOrMintTokens) GetPoolSignerAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[4]
+}
+
+// SetPoolTokenAccountAccount sets the "poolTokenAccount" account.
+func (inst *ReleaseOrMintTokens) SetPoolTokenAccountAccount(poolTokenAccount ag_solanago.PublicKey) *ReleaseOrMintTokens {
+ inst.AccountMetaSlice[5] = ag_solanago.Meta(poolTokenAccount).WRITE()
+ return inst
+}
+
+// GetPoolTokenAccountAccount gets the "poolTokenAccount" account.
+func (inst *ReleaseOrMintTokens) GetPoolTokenAccountAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[5]
+}
+
+// SetChainConfigAccount sets the "chainConfig" account.
+func (inst *ReleaseOrMintTokens) SetChainConfigAccount(chainConfig ag_solanago.PublicKey) *ReleaseOrMintTokens {
+ inst.AccountMetaSlice[6] = ag_solanago.Meta(chainConfig).WRITE()
+ return inst
+}
+
+// GetChainConfigAccount gets the "chainConfig" account.
+func (inst *ReleaseOrMintTokens) GetChainConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[6]
+}
+
+// SetReceiverTokenAccountAccount sets the "receiverTokenAccount" account.
+func (inst *ReleaseOrMintTokens) SetReceiverTokenAccountAccount(receiverTokenAccount ag_solanago.PublicKey) *ReleaseOrMintTokens {
+ inst.AccountMetaSlice[7] = ag_solanago.Meta(receiverTokenAccount).WRITE()
+ return inst
+}
+
+// GetReceiverTokenAccountAccount gets the "receiverTokenAccount" account.
+func (inst *ReleaseOrMintTokens) GetReceiverTokenAccountAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[7]
+}
+
+func (inst ReleaseOrMintTokens) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_ReleaseOrMintTokens,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst ReleaseOrMintTokens) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *ReleaseOrMintTokens) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ReleaseOrMint == nil {
+ return errors.New("ReleaseOrMint parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.TokenProgram is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.Mint is not set")
+ }
+ if inst.AccountMetaSlice[4] == nil {
+ return errors.New("accounts.PoolSigner is not set")
+ }
+ if inst.AccountMetaSlice[5] == nil {
+ return errors.New("accounts.PoolTokenAccount is not set")
+ }
+ if inst.AccountMetaSlice[6] == nil {
+ return errors.New("accounts.ChainConfig is not set")
+ }
+ if inst.AccountMetaSlice[7] == nil {
+ return errors.New("accounts.ReceiverTokenAccount is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *ReleaseOrMintTokens) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("ReleaseOrMintTokens")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("ReleaseOrMint", *inst.ReleaseOrMint))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=8]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" tokenProgram", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[3]))
+ accountsBranch.Child(ag_format.Meta(" poolSigner", inst.AccountMetaSlice[4]))
+ accountsBranch.Child(ag_format.Meta(" poolToken", inst.AccountMetaSlice[5]))
+ accountsBranch.Child(ag_format.Meta(" chainConfig", inst.AccountMetaSlice[6]))
+ accountsBranch.Child(ag_format.Meta("receiverToken", inst.AccountMetaSlice[7]))
+ })
+ })
+ })
+}
+
+func (obj ReleaseOrMintTokens) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ReleaseOrMint` param:
+ err = encoder.Encode(obj.ReleaseOrMint)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *ReleaseOrMintTokens) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ReleaseOrMint`:
+ err = decoder.Decode(&obj.ReleaseOrMint)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewReleaseOrMintTokensInstruction declares a new ReleaseOrMintTokens instruction with the provided parameters and accounts.
+func NewReleaseOrMintTokensInstruction(
+ // Parameters:
+ releaseOrMint ReleaseOrMintInV1,
+ // Accounts:
+ authority ag_solanago.PublicKey,
+ config ag_solanago.PublicKey,
+ tokenProgram ag_solanago.PublicKey,
+ mint ag_solanago.PublicKey,
+ poolSigner ag_solanago.PublicKey,
+ poolTokenAccount ag_solanago.PublicKey,
+ chainConfig ag_solanago.PublicKey,
+ receiverTokenAccount ag_solanago.PublicKey) *ReleaseOrMintTokens {
+ return NewReleaseOrMintTokensInstructionBuilder().
+ SetReleaseOrMint(releaseOrMint).
+ SetAuthorityAccount(authority).
+ SetConfigAccount(config).
+ SetTokenProgramAccount(tokenProgram).
+ SetMintAccount(mint).
+ SetPoolSignerAccount(poolSigner).
+ SetPoolTokenAccountAccount(poolTokenAccount).
+ SetChainConfigAccount(chainConfig).
+ SetReceiverTokenAccountAccount(receiverTokenAccount)
+}
diff --git a/chains/solana/gobindings/token_pool/ReleaseOrMintTokens_test.go b/chains/solana/gobindings/token_pool/ReleaseOrMintTokens_test.go
new file mode 100644
index 000000000..66081c764
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/ReleaseOrMintTokens_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_ReleaseOrMintTokens(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("ReleaseOrMintTokens"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(ReleaseOrMintTokens)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(ReleaseOrMintTokens)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/SetChainRateLimit.go b/chains/solana/gobindings/token_pool/SetChainRateLimit.go
new file mode 100644
index 000000000..d2d78f8d0
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/SetChainRateLimit.go
@@ -0,0 +1,253 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// SetChainRateLimit is the `setChainRateLimit` instruction.
+type SetChainRateLimit struct {
+ RemoteChainSelector *uint64
+ Mint *ag_solanago.PublicKey
+ Inbound *RateLimitConfig
+ Outbound *RateLimitConfig
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] chainConfig
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewSetChainRateLimitInstructionBuilder creates a new `SetChainRateLimit` instruction builder.
+func NewSetChainRateLimitInstructionBuilder() *SetChainRateLimit {
+ nd := &SetChainRateLimit{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetRemoteChainSelector sets the "remoteChainSelector" parameter.
+func (inst *SetChainRateLimit) SetRemoteChainSelector(remoteChainSelector uint64) *SetChainRateLimit {
+ inst.RemoteChainSelector = &remoteChainSelector
+ return inst
+}
+
+// SetMint sets the "mint" parameter.
+func (inst *SetChainRateLimit) SetMint(mint ag_solanago.PublicKey) *SetChainRateLimit {
+ inst.Mint = &mint
+ return inst
+}
+
+// SetInbound sets the "inbound" parameter.
+func (inst *SetChainRateLimit) SetInbound(inbound RateLimitConfig) *SetChainRateLimit {
+ inst.Inbound = &inbound
+ return inst
+}
+
+// SetOutbound sets the "outbound" parameter.
+func (inst *SetChainRateLimit) SetOutbound(outbound RateLimitConfig) *SetChainRateLimit {
+ inst.Outbound = &outbound
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *SetChainRateLimit) SetConfigAccount(config ag_solanago.PublicKey) *SetChainRateLimit {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *SetChainRateLimit) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetChainConfigAccount sets the "chainConfig" account.
+func (inst *SetChainRateLimit) SetChainConfigAccount(chainConfig ag_solanago.PublicKey) *SetChainRateLimit {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(chainConfig).WRITE()
+ return inst
+}
+
+// GetChainConfigAccount gets the "chainConfig" account.
+func (inst *SetChainRateLimit) GetChainConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *SetChainRateLimit) SetAuthorityAccount(authority ag_solanago.PublicKey) *SetChainRateLimit {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *SetChainRateLimit) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *SetChainRateLimit) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *SetChainRateLimit {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *SetChainRateLimit) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst SetChainRateLimit) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_SetChainRateLimit,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst SetChainRateLimit) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *SetChainRateLimit) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.RemoteChainSelector == nil {
+ return errors.New("RemoteChainSelector parameter is not set")
+ }
+ if inst.Mint == nil {
+ return errors.New("Mint parameter is not set")
+ }
+ if inst.Inbound == nil {
+ return errors.New("Inbound parameter is not set")
+ }
+ if inst.Outbound == nil {
+ return errors.New("Outbound parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ChainConfig is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *SetChainRateLimit) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("SetChainRateLimit")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=4]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("RemoteChainSelector", *inst.RemoteChainSelector))
+ paramsBranch.Child(ag_format.Param(" Mint", *inst.Mint))
+ paramsBranch.Child(ag_format.Param(" Inbound", *inst.Inbound))
+ paramsBranch.Child(ag_format.Param(" Outbound", *inst.Outbound))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" chainConfig", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj SetChainRateLimit) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `RemoteChainSelector` param:
+ err = encoder.Encode(obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Serialize `Inbound` param:
+ err = encoder.Encode(obj.Inbound)
+ if err != nil {
+ return err
+ }
+ // Serialize `Outbound` param:
+ err = encoder.Encode(obj.Outbound)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *SetChainRateLimit) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `RemoteChainSelector`:
+ err = decoder.Decode(&obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Inbound`:
+ err = decoder.Decode(&obj.Inbound)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Outbound`:
+ err = decoder.Decode(&obj.Outbound)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewSetChainRateLimitInstruction declares a new SetChainRateLimit instruction with the provided parameters and accounts.
+func NewSetChainRateLimitInstruction(
+ // Parameters:
+ remoteChainSelector uint64,
+ mint ag_solanago.PublicKey,
+ inbound RateLimitConfig,
+ outbound RateLimitConfig,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ chainConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *SetChainRateLimit {
+ return NewSetChainRateLimitInstructionBuilder().
+ SetRemoteChainSelector(remoteChainSelector).
+ SetMint(mint).
+ SetInbound(inbound).
+ SetOutbound(outbound).
+ SetConfigAccount(config).
+ SetChainConfigAccount(chainConfig).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/token_pool/SetChainRateLimit_test.go b/chains/solana/gobindings/token_pool/SetChainRateLimit_test.go
new file mode 100644
index 000000000..cb9990601
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/SetChainRateLimit_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_SetChainRateLimit(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("SetChainRateLimit"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(SetChainRateLimit)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(SetChainRateLimit)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/SetChainRemoteConfig.go b/chains/solana/gobindings/token_pool/SetChainRemoteConfig.go
new file mode 100644
index 000000000..506aa915d
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/SetChainRemoteConfig.go
@@ -0,0 +1,230 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// SetChainRemoteConfig is the `setChainRemoteConfig` instruction.
+type SetChainRemoteConfig struct {
+ RemoteChainSelector *uint64
+ Mint *ag_solanago.PublicKey
+ Cfg *RemoteConfig
+
+ // [0] = [] config
+ //
+ // [1] = [WRITE] chainConfig
+ //
+ // [2] = [WRITE, SIGNER] authority
+ //
+ // [3] = [] systemProgram
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewSetChainRemoteConfigInstructionBuilder creates a new `SetChainRemoteConfig` instruction builder.
+func NewSetChainRemoteConfigInstructionBuilder() *SetChainRemoteConfig {
+ nd := &SetChainRemoteConfig{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4),
+ }
+ return nd
+}
+
+// SetRemoteChainSelector sets the "remoteChainSelector" parameter.
+func (inst *SetChainRemoteConfig) SetRemoteChainSelector(remoteChainSelector uint64) *SetChainRemoteConfig {
+ inst.RemoteChainSelector = &remoteChainSelector
+ return inst
+}
+
+// SetMint sets the "mint" parameter.
+func (inst *SetChainRemoteConfig) SetMint(mint ag_solanago.PublicKey) *SetChainRemoteConfig {
+ inst.Mint = &mint
+ return inst
+}
+
+// SetCfg sets the "cfg" parameter.
+func (inst *SetChainRemoteConfig) SetCfg(cfg RemoteConfig) *SetChainRemoteConfig {
+ inst.Cfg = &cfg
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *SetChainRemoteConfig) SetConfigAccount(config ag_solanago.PublicKey) *SetChainRemoteConfig {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config)
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *SetChainRemoteConfig) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetChainConfigAccount sets the "chainConfig" account.
+func (inst *SetChainRemoteConfig) SetChainConfigAccount(chainConfig ag_solanago.PublicKey) *SetChainRemoteConfig {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(chainConfig).WRITE()
+ return inst
+}
+
+// GetChainConfigAccount gets the "chainConfig" account.
+func (inst *SetChainRemoteConfig) GetChainConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *SetChainRemoteConfig) SetAuthorityAccount(authority ag_solanago.PublicKey) *SetChainRemoteConfig {
+ inst.AccountMetaSlice[2] = ag_solanago.Meta(authority).WRITE().SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *SetChainRemoteConfig) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[2]
+}
+
+// SetSystemProgramAccount sets the "systemProgram" account.
+func (inst *SetChainRemoteConfig) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *SetChainRemoteConfig {
+ inst.AccountMetaSlice[3] = ag_solanago.Meta(systemProgram)
+ return inst
+}
+
+// GetSystemProgramAccount gets the "systemProgram" account.
+func (inst *SetChainRemoteConfig) GetSystemProgramAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[3]
+}
+
+func (inst SetChainRemoteConfig) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_SetChainRemoteConfig,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst SetChainRemoteConfig) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *SetChainRemoteConfig) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.RemoteChainSelector == nil {
+ return errors.New("RemoteChainSelector parameter is not set")
+ }
+ if inst.Mint == nil {
+ return errors.New("Mint parameter is not set")
+ }
+ if inst.Cfg == nil {
+ return errors.New("Cfg parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.ChainConfig is not set")
+ }
+ if inst.AccountMetaSlice[2] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ if inst.AccountMetaSlice[3] == nil {
+ return errors.New("accounts.SystemProgram is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *SetChainRemoteConfig) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("SetChainRemoteConfig")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=3]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("RemoteChainSelector", *inst.RemoteChainSelector))
+ paramsBranch.Child(ag_format.Param(" Mint", *inst.Mint))
+ paramsBranch.Child(ag_format.Param(" Cfg", *inst.Cfg))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=4]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta(" chainConfig", inst.AccountMetaSlice[1]))
+ accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[2]))
+ accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[3]))
+ })
+ })
+ })
+}
+
+func (obj SetChainRemoteConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `RemoteChainSelector` param:
+ err = encoder.Encode(obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Serialize `Cfg` param:
+ err = encoder.Encode(obj.Cfg)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *SetChainRemoteConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `RemoteChainSelector`:
+ err = decoder.Decode(&obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Cfg`:
+ err = decoder.Decode(&obj.Cfg)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewSetChainRemoteConfigInstruction declares a new SetChainRemoteConfig instruction with the provided parameters and accounts.
+func NewSetChainRemoteConfigInstruction(
+ // Parameters:
+ remoteChainSelector uint64,
+ mint ag_solanago.PublicKey,
+ cfg RemoteConfig,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ chainConfig ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey,
+ systemProgram ag_solanago.PublicKey) *SetChainRemoteConfig {
+ return NewSetChainRemoteConfigInstructionBuilder().
+ SetRemoteChainSelector(remoteChainSelector).
+ SetMint(mint).
+ SetCfg(cfg).
+ SetConfigAccount(config).
+ SetChainConfigAccount(chainConfig).
+ SetAuthorityAccount(authority).
+ SetSystemProgramAccount(systemProgram)
+}
diff --git a/chains/solana/gobindings/token_pool/SetChainRemoteConfig_test.go b/chains/solana/gobindings/token_pool/SetChainRemoteConfig_test.go
new file mode 100644
index 000000000..d80e9661d
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/SetChainRemoteConfig_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_SetChainRemoteConfig(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("SetChainRemoteConfig"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(SetChainRemoteConfig)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(SetChainRemoteConfig)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/SetRampAuthority.go b/chains/solana/gobindings/token_pool/SetRampAuthority.go
new file mode 100644
index 000000000..f71217122
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/SetRampAuthority.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// SetRampAuthority is the `setRampAuthority` instruction.
+type SetRampAuthority struct {
+ NewAuthority *ag_solanago.PublicKey
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewSetRampAuthorityInstructionBuilder creates a new `SetRampAuthority` instruction builder.
+func NewSetRampAuthorityInstructionBuilder() *SetRampAuthority {
+ nd := &SetRampAuthority{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetNewAuthority sets the "newAuthority" parameter.
+func (inst *SetRampAuthority) SetNewAuthority(newAuthority ag_solanago.PublicKey) *SetRampAuthority {
+ inst.NewAuthority = &newAuthority
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *SetRampAuthority) SetConfigAccount(config ag_solanago.PublicKey) *SetRampAuthority {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *SetRampAuthority) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *SetRampAuthority) SetAuthorityAccount(authority ag_solanago.PublicKey) *SetRampAuthority {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *SetRampAuthority) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst SetRampAuthority) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_SetRampAuthority,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst SetRampAuthority) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *SetRampAuthority) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.NewAuthority == nil {
+ return errors.New("NewAuthority parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *SetRampAuthority) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("SetRampAuthority")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("NewAuthority", *inst.NewAuthority))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj SetRampAuthority) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `NewAuthority` param:
+ err = encoder.Encode(obj.NewAuthority)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *SetRampAuthority) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `NewAuthority`:
+ err = decoder.Decode(&obj.NewAuthority)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewSetRampAuthorityInstruction declares a new SetRampAuthority instruction with the provided parameters and accounts.
+func NewSetRampAuthorityInstruction(
+ // Parameters:
+ newAuthority ag_solanago.PublicKey,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *SetRampAuthority {
+ return NewSetRampAuthorityInstructionBuilder().
+ SetNewAuthority(newAuthority).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/token_pool/SetRampAuthority_test.go b/chains/solana/gobindings/token_pool/SetRampAuthority_test.go
new file mode 100644
index 000000000..d4528a4db
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/SetRampAuthority_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_SetRampAuthority(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("SetRampAuthority"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(SetRampAuthority)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(SetRampAuthority)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/TransferOwnership.go b/chains/solana/gobindings/token_pool/TransferOwnership.go
new file mode 100644
index 000000000..1ec197f48
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/TransferOwnership.go
@@ -0,0 +1,146 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "errors"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_format "github.com/gagliardetto/solana-go/text/format"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+// TransferOwnership is the `transferOwnership` instruction.
+type TransferOwnership struct {
+ ProposedOwner *ag_solanago.PublicKey
+
+ // [0] = [WRITE] config
+ //
+ // [1] = [SIGNER] authority
+ ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
+}
+
+// NewTransferOwnershipInstructionBuilder creates a new `TransferOwnership` instruction builder.
+func NewTransferOwnershipInstructionBuilder() *TransferOwnership {
+ nd := &TransferOwnership{
+ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
+ }
+ return nd
+}
+
+// SetProposedOwner sets the "proposedOwner" parameter.
+func (inst *TransferOwnership) SetProposedOwner(proposedOwner ag_solanago.PublicKey) *TransferOwnership {
+ inst.ProposedOwner = &proposedOwner
+ return inst
+}
+
+// SetConfigAccount sets the "config" account.
+func (inst *TransferOwnership) SetConfigAccount(config ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE()
+ return inst
+}
+
+// GetConfigAccount gets the "config" account.
+func (inst *TransferOwnership) GetConfigAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[0]
+}
+
+// SetAuthorityAccount sets the "authority" account.
+func (inst *TransferOwnership) SetAuthorityAccount(authority ag_solanago.PublicKey) *TransferOwnership {
+ inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER()
+ return inst
+}
+
+// GetAuthorityAccount gets the "authority" account.
+func (inst *TransferOwnership) GetAuthorityAccount() *ag_solanago.AccountMeta {
+ return inst.AccountMetaSlice[1]
+}
+
+func (inst TransferOwnership) Build() *Instruction {
+ return &Instruction{BaseVariant: ag_binary.BaseVariant{
+ Impl: inst,
+ TypeID: Instruction_TransferOwnership,
+ }}
+}
+
+// ValidateAndBuild validates the instruction parameters and accounts;
+// if there is a validation error, it returns the error.
+// Otherwise, it builds and returns the instruction.
+func (inst TransferOwnership) ValidateAndBuild() (*Instruction, error) {
+ if err := inst.Validate(); err != nil {
+ return nil, err
+ }
+ return inst.Build(), nil
+}
+
+func (inst *TransferOwnership) Validate() error {
+ // Check whether all (required) parameters are set:
+ {
+ if inst.ProposedOwner == nil {
+ return errors.New("ProposedOwner parameter is not set")
+ }
+ }
+
+ // Check whether all (required) accounts are set:
+ {
+ if inst.AccountMetaSlice[0] == nil {
+ return errors.New("accounts.Config is not set")
+ }
+ if inst.AccountMetaSlice[1] == nil {
+ return errors.New("accounts.Authority is not set")
+ }
+ }
+ return nil
+}
+
+func (inst *TransferOwnership) EncodeToTree(parent ag_treeout.Branches) {
+ parent.Child(ag_format.Program(ProgramName, ProgramID)).
+ //
+ ParentFunc(func(programBranch ag_treeout.Branches) {
+ programBranch.Child(ag_format.Instruction("TransferOwnership")).
+ //
+ ParentFunc(func(instructionBranch ag_treeout.Branches) {
+
+ // Parameters of the instruction:
+ instructionBranch.Child("Params[len=1]").ParentFunc(func(paramsBranch ag_treeout.Branches) {
+ paramsBranch.Child(ag_format.Param("ProposedOwner", *inst.ProposedOwner))
+ })
+
+ // Accounts of the instruction:
+ instructionBranch.Child("Accounts[len=2]").ParentFunc(func(accountsBranch ag_treeout.Branches) {
+ accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0]))
+ accountsBranch.Child(ag_format.Meta("authority", inst.AccountMetaSlice[1]))
+ })
+ })
+ })
+}
+
+func (obj TransferOwnership) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func (obj *TransferOwnership) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// NewTransferOwnershipInstruction declares a new TransferOwnership instruction with the provided parameters and accounts.
+func NewTransferOwnershipInstruction(
+ // Parameters:
+ proposedOwner ag_solanago.PublicKey,
+ // Accounts:
+ config ag_solanago.PublicKey,
+ authority ag_solanago.PublicKey) *TransferOwnership {
+ return NewTransferOwnershipInstructionBuilder().
+ SetProposedOwner(proposedOwner).
+ SetConfigAccount(config).
+ SetAuthorityAccount(authority)
+}
diff --git a/chains/solana/gobindings/token_pool/TransferOwnership_test.go b/chains/solana/gobindings/token_pool/TransferOwnership_test.go
new file mode 100644
index 000000000..127dedef8
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/TransferOwnership_test.go
@@ -0,0 +1,32 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ ag_gofuzz "github.com/gagliardetto/gofuzz"
+ ag_require "github.com/stretchr/testify/require"
+ "strconv"
+ "testing"
+)
+
+func TestEncodeDecode_TransferOwnership(t *testing.T) {
+ fu := ag_gofuzz.New().NilChance(0)
+ for i := 0; i < 1; i++ {
+ t.Run("TransferOwnership"+strconv.Itoa(i), func(t *testing.T) {
+ {
+ params := new(TransferOwnership)
+ fu.Fuzz(params)
+ params.AccountMetaSlice = nil
+ buf := new(bytes.Buffer)
+ err := encodeT(*params, buf)
+ ag_require.NoError(t, err)
+ got := new(TransferOwnership)
+ err = decodeT(got, buf.Bytes())
+ got.AccountMetaSlice = nil
+ ag_require.NoError(t, err)
+ ag_require.Equal(t, params, got)
+ }
+ })
+ }
+}
diff --git a/chains/solana/gobindings/token_pool/accounts.go b/chains/solana/gobindings/token_pool/accounts.go
new file mode 100644
index 000000000..b37db9701
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/accounts.go
@@ -0,0 +1,244 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type Config struct {
+ Version uint8
+ PoolType PoolType
+ TokenProgram ag_solanago.PublicKey
+ Mint ag_solanago.PublicKey
+ Decimals uint8
+ PoolSigner ag_solanago.PublicKey
+ PoolTokenAccount ag_solanago.PublicKey
+ Owner ag_solanago.PublicKey
+ ProposedOwner ag_solanago.PublicKey
+ RampAuthority ag_solanago.PublicKey
+}
+
+var ConfigDiscriminator = [8]byte{155, 12, 170, 224, 30, 250, 204, 130}
+
+func (obj Config) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ConfigDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Version` param:
+ err = encoder.Encode(obj.Version)
+ if err != nil {
+ return err
+ }
+ // Serialize `PoolType` param:
+ err = encoder.Encode(obj.PoolType)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenProgram` param:
+ err = encoder.Encode(obj.TokenProgram)
+ if err != nil {
+ return err
+ }
+ // Serialize `Mint` param:
+ err = encoder.Encode(obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Serialize `Decimals` param:
+ err = encoder.Encode(obj.Decimals)
+ if err != nil {
+ return err
+ }
+ // Serialize `PoolSigner` param:
+ err = encoder.Encode(obj.PoolSigner)
+ if err != nil {
+ return err
+ }
+ // Serialize `PoolTokenAccount` param:
+ err = encoder.Encode(obj.PoolTokenAccount)
+ if err != nil {
+ return err
+ }
+ // Serialize `Owner` param:
+ err = encoder.Encode(obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Serialize `ProposedOwner` param:
+ err = encoder.Encode(obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Serialize `RampAuthority` param:
+ err = encoder.Encode(obj.RampAuthority)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *Config) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ConfigDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[155 12 170 224 30 250 204 130]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Version`:
+ err = decoder.Decode(&obj.Version)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PoolType`:
+ err = decoder.Decode(&obj.PoolType)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenProgram`:
+ err = decoder.Decode(&obj.TokenProgram)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Mint`:
+ err = decoder.Decode(&obj.Mint)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Decimals`:
+ err = decoder.Decode(&obj.Decimals)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PoolSigner`:
+ err = decoder.Decode(&obj.PoolSigner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `PoolTokenAccount`:
+ err = decoder.Decode(&obj.PoolTokenAccount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Owner`:
+ err = decoder.Decode(&obj.Owner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `ProposedOwner`:
+ err = decoder.Decode(&obj.ProposedOwner)
+ if err != nil {
+ return err
+ }
+ // Deserialize `RampAuthority`:
+ err = decoder.Decode(&obj.RampAuthority)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ExternalExecutionConfig struct{}
+
+var ExternalExecutionConfigDiscriminator = [8]byte{159, 157, 150, 212, 168, 103, 117, 39}
+
+func (obj ExternalExecutionConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ExternalExecutionConfigDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ExternalExecutionConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ExternalExecutionConfigDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[159 157 150 212 168 103 117 39]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ return nil
+}
+
+type ChainConfig struct {
+ Remote RemoteConfig
+ InboundRateLimit RateLimitTokenBucket
+ OutboundRateLimit RateLimitTokenBucket
+}
+
+var ChainConfigDiscriminator = [8]byte{13, 177, 233, 141, 212, 29, 148, 56}
+
+func (obj ChainConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Write account discriminator:
+ err = encoder.WriteBytes(ChainConfigDiscriminator[:], false)
+ if err != nil {
+ return err
+ }
+ // Serialize `Remote` param:
+ err = encoder.Encode(obj.Remote)
+ if err != nil {
+ return err
+ }
+ // Serialize `InboundRateLimit` param:
+ err = encoder.Encode(obj.InboundRateLimit)
+ if err != nil {
+ return err
+ }
+ // Serialize `OutboundRateLimit` param:
+ err = encoder.Encode(obj.OutboundRateLimit)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ChainConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Read and check account discriminator:
+ {
+ discriminator, err := decoder.ReadTypeID()
+ if err != nil {
+ return err
+ }
+ if !discriminator.Equal(ChainConfigDiscriminator[:]) {
+ return fmt.Errorf(
+ "wrong discriminator: wanted %s, got %s",
+ "[13 177 233 141 212 29 148 56]",
+ fmt.Sprint(discriminator[:]))
+ }
+ }
+ // Deserialize `Remote`:
+ err = decoder.Decode(&obj.Remote)
+ if err != nil {
+ return err
+ }
+ // Deserialize `InboundRateLimit`:
+ err = decoder.Decode(&obj.InboundRateLimit)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OutboundRateLimit`:
+ err = decoder.Decode(&obj.OutboundRateLimit)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/chains/solana/gobindings/token_pool/instructions.go b/chains/solana/gobindings/token_pool/instructions.go
new file mode 100644
index 000000000..1e7c5324c
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/instructions.go
@@ -0,0 +1,173 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ "fmt"
+ ag_spew "github.com/davecgh/go-spew/spew"
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+ ag_text "github.com/gagliardetto/solana-go/text"
+ ag_treeout "github.com/gagliardetto/treeout"
+)
+
+var ProgramID ag_solanago.PublicKey
+
+func SetProgramID(pubkey ag_solanago.PublicKey) {
+ ProgramID = pubkey
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+}
+
+const ProgramName = "TokenPool"
+
+func init() {
+ if !ProgramID.IsZero() {
+ ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
+ }
+}
+
+var (
+ Instruction_Initialize = ag_binary.TypeID([8]byte{175, 175, 109, 31, 13, 152, 155, 237})
+
+ Instruction_TransferOwnership = ag_binary.TypeID([8]byte{65, 177, 215, 73, 53, 45, 99, 47})
+
+ Instruction_AcceptOwnership = ag_binary.TypeID([8]byte{172, 23, 43, 13, 238, 213, 85, 150})
+
+ Instruction_SetRampAuthority = ag_binary.TypeID([8]byte{181, 180, 204, 162, 156, 188, 239, 153})
+
+ Instruction_SetChainRemoteConfig = ag_binary.TypeID([8]byte{147, 161, 6, 246, 121, 59, 37, 28})
+
+ Instruction_SetChainRateLimit = ag_binary.TypeID([8]byte{188, 188, 161, 37, 100, 249, 123, 170})
+
+ Instruction_DeleteChainConfig = ag_binary.TypeID([8]byte{241, 159, 142, 210, 64, 173, 77, 179})
+
+ Instruction_ReleaseOrMintTokens = ag_binary.TypeID([8]byte{92, 100, 150, 198, 252, 63, 164, 228})
+
+ Instruction_LockOrBurnTokens = ag_binary.TypeID([8]byte{114, 161, 94, 29, 147, 25, 232, 191})
+)
+
+// InstructionIDToName returns the name of the instruction given its ID.
+func InstructionIDToName(id ag_binary.TypeID) string {
+ switch id {
+ case Instruction_Initialize:
+ return "Initialize"
+ case Instruction_TransferOwnership:
+ return "TransferOwnership"
+ case Instruction_AcceptOwnership:
+ return "AcceptOwnership"
+ case Instruction_SetRampAuthority:
+ return "SetRampAuthority"
+ case Instruction_SetChainRemoteConfig:
+ return "SetChainRemoteConfig"
+ case Instruction_SetChainRateLimit:
+ return "SetChainRateLimit"
+ case Instruction_DeleteChainConfig:
+ return "DeleteChainConfig"
+ case Instruction_ReleaseOrMintTokens:
+ return "ReleaseOrMintTokens"
+ case Instruction_LockOrBurnTokens:
+ return "LockOrBurnTokens"
+ default:
+ return ""
+ }
+}
+
+type Instruction struct {
+ ag_binary.BaseVariant
+}
+
+func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) {
+ if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok {
+ enToTree.EncodeToTree(parent)
+ } else {
+ parent.Child(ag_spew.Sdump(inst))
+ }
+}
+
+var InstructionImplDef = ag_binary.NewVariantDefinition(
+ ag_binary.AnchorTypeIDEncoding,
+ []ag_binary.VariantType{
+ {
+ "initialize", (*Initialize)(nil),
+ },
+ {
+ "transfer_ownership", (*TransferOwnership)(nil),
+ },
+ {
+ "accept_ownership", (*AcceptOwnership)(nil),
+ },
+ {
+ "set_ramp_authority", (*SetRampAuthority)(nil),
+ },
+ {
+ "set_chain_remote_config", (*SetChainRemoteConfig)(nil),
+ },
+ {
+ "set_chain_rate_limit", (*SetChainRateLimit)(nil),
+ },
+ {
+ "delete_chain_config", (*DeleteChainConfig)(nil),
+ },
+ {
+ "release_or_mint_tokens", (*ReleaseOrMintTokens)(nil),
+ },
+ {
+ "lock_or_burn_tokens", (*LockOrBurnTokens)(nil),
+ },
+ },
+)
+
+func (inst *Instruction) ProgramID() ag_solanago.PublicKey {
+ return ProgramID
+}
+
+func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) {
+ return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts()
+}
+
+func (inst *Instruction) Data() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil {
+ return nil, fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return buf.Bytes(), nil
+}
+
+func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
+ return encoder.Encode(inst.Impl, option)
+}
+
+func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
+ return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
+}
+
+func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
+ err := encoder.WriteBytes(inst.TypeID.Bytes(), false)
+ if err != nil {
+ return fmt.Errorf("unable to write variant type: %w", err)
+ }
+ return encoder.Encode(inst.Impl)
+}
+
+func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) {
+ inst, err := DecodeInstruction(accounts, data)
+ if err != nil {
+ return nil, err
+ }
+ return inst, nil
+}
+
+func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) {
+ inst := new(Instruction)
+ if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil {
+ return nil, fmt.Errorf("unable to decode instruction: %w", err)
+ }
+ if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok {
+ err := v.SetAccounts(accounts)
+ if err != nil {
+ return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
+ }
+ }
+ return inst, nil
+}
diff --git a/chains/solana/gobindings/token_pool/testing_utils.go b/chains/solana/gobindings/token_pool/testing_utils.go
new file mode 100644
index 000000000..05ec4cd0f
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/testing_utils.go
@@ -0,0 +1,20 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ "bytes"
+ "fmt"
+ ag_binary "github.com/gagliardetto/binary"
+)
+
+func encodeT(data interface{}, buf *bytes.Buffer) error {
+ if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil {
+ return fmt.Errorf("unable to encode instruction: %w", err)
+ }
+ return nil
+}
+
+func decodeT(dst interface{}, data []byte) error {
+ return ag_binary.NewBorshDecoder(data).Decode(dst)
+}
diff --git a/chains/solana/gobindings/token_pool/types.go b/chains/solana/gobindings/token_pool/types.go
new file mode 100644
index 000000000..5ae3cc585
--- /dev/null
+++ b/chains/solana/gobindings/token_pool/types.go
@@ -0,0 +1,386 @@
+// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT.
+
+package token_pool
+
+import (
+ ag_binary "github.com/gagliardetto/binary"
+ ag_solanago "github.com/gagliardetto/solana-go"
+)
+
+type RateLimitTokenBucket struct {
+ Tokens uint64
+ LastUpdated uint64
+ Cfg RateLimitConfig
+}
+
+func (obj RateLimitTokenBucket) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Tokens` param:
+ err = encoder.Encode(obj.Tokens)
+ if err != nil {
+ return err
+ }
+ // Serialize `LastUpdated` param:
+ err = encoder.Encode(obj.LastUpdated)
+ if err != nil {
+ return err
+ }
+ // Serialize `Cfg` param:
+ err = encoder.Encode(obj.Cfg)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *RateLimitTokenBucket) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Tokens`:
+ err = decoder.Decode(&obj.Tokens)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LastUpdated`:
+ err = decoder.Decode(&obj.LastUpdated)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Cfg`:
+ err = decoder.Decode(&obj.Cfg)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type RateLimitConfig struct {
+ Enabled bool
+ Capacity uint64
+ Rate uint64
+}
+
+func (obj RateLimitConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Enabled` param:
+ err = encoder.Encode(obj.Enabled)
+ if err != nil {
+ return err
+ }
+ // Serialize `Capacity` param:
+ err = encoder.Encode(obj.Capacity)
+ if err != nil {
+ return err
+ }
+ // Serialize `Rate` param:
+ err = encoder.Encode(obj.Rate)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *RateLimitConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Enabled`:
+ err = decoder.Decode(&obj.Enabled)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Capacity`:
+ err = decoder.Decode(&obj.Capacity)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Rate`:
+ err = decoder.Decode(&obj.Rate)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type LockOrBurnInV1 struct {
+ Receiver []byte
+ RemoteChainSelector uint64
+ OriginalSender ag_solanago.PublicKey
+ Amount uint64
+ LocalToken ag_solanago.PublicKey
+}
+
+func (obj LockOrBurnInV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `Receiver` param:
+ err = encoder.Encode(obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Serialize `RemoteChainSelector` param:
+ err = encoder.Encode(obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `OriginalSender` param:
+ err = encoder.Encode(obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Serialize `LocalToken` param:
+ err = encoder.Encode(obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *LockOrBurnInV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `Receiver`:
+ err = decoder.Decode(&obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Deserialize `RemoteChainSelector`:
+ err = decoder.Decode(&obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OriginalSender`:
+ err = decoder.Decode(&obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LocalToken`:
+ err = decoder.Decode(&obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type LockOrBurnOutV1 struct {
+ DestTokenAddress []byte
+ DestPoolData []byte
+}
+
+func (obj LockOrBurnOutV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `DestTokenAddress` param:
+ err = encoder.Encode(obj.DestTokenAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `DestPoolData` param:
+ err = encoder.Encode(obj.DestPoolData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *LockOrBurnOutV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `DestTokenAddress`:
+ err = decoder.Decode(&obj.DestTokenAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `DestPoolData`:
+ err = decoder.Decode(&obj.DestPoolData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ReleaseOrMintInV1 struct {
+ OriginalSender []byte
+ RemoteChainSelector uint64
+ Receiver ag_solanago.PublicKey
+ Amount [32]uint8
+ LocalToken ag_solanago.PublicKey
+
+ // @dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the
+ // expected pool address for the given remoteChainSelector.
+ SourcePoolAddress []byte
+ SourcePoolData []byte
+
+ // @dev WARNING: offchainTokenData is untrusted data.
+ OffchainTokenData []byte
+}
+
+func (obj ReleaseOrMintInV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `OriginalSender` param:
+ err = encoder.Encode(obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Serialize `RemoteChainSelector` param:
+ err = encoder.Encode(obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Serialize `Receiver` param:
+ err = encoder.Encode(obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Serialize `Amount` param:
+ err = encoder.Encode(obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Serialize `LocalToken` param:
+ err = encoder.Encode(obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourcePoolAddress` param:
+ err = encoder.Encode(obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `SourcePoolData` param:
+ err = encoder.Encode(obj.SourcePoolData)
+ if err != nil {
+ return err
+ }
+ // Serialize `OffchainTokenData` param:
+ err = encoder.Encode(obj.OffchainTokenData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ReleaseOrMintInV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `OriginalSender`:
+ err = decoder.Decode(&obj.OriginalSender)
+ if err != nil {
+ return err
+ }
+ // Deserialize `RemoteChainSelector`:
+ err = decoder.Decode(&obj.RemoteChainSelector)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Receiver`:
+ err = decoder.Decode(&obj.Receiver)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Amount`:
+ err = decoder.Decode(&obj.Amount)
+ if err != nil {
+ return err
+ }
+ // Deserialize `LocalToken`:
+ err = decoder.Decode(&obj.LocalToken)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourcePoolAddress`:
+ err = decoder.Decode(&obj.SourcePoolAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `SourcePoolData`:
+ err = decoder.Decode(&obj.SourcePoolData)
+ if err != nil {
+ return err
+ }
+ // Deserialize `OffchainTokenData`:
+ err = decoder.Decode(&obj.OffchainTokenData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type ReleaseOrMintOutV1 struct {
+ DestinationAmount uint64
+}
+
+func (obj ReleaseOrMintOutV1) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `DestinationAmount` param:
+ err = encoder.Encode(obj.DestinationAmount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *ReleaseOrMintOutV1) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `DestinationAmount`:
+ err = decoder.Decode(&obj.DestinationAmount)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type RemoteConfig struct {
+ PoolAddress []byte
+ TokenAddress []byte
+ Decimals uint8
+}
+
+func (obj RemoteConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) {
+ // Serialize `PoolAddress` param:
+ err = encoder.Encode(obj.PoolAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `TokenAddress` param:
+ err = encoder.Encode(obj.TokenAddress)
+ if err != nil {
+ return err
+ }
+ // Serialize `Decimals` param:
+ err = encoder.Encode(obj.Decimals)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (obj *RemoteConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) {
+ // Deserialize `PoolAddress`:
+ err = decoder.Decode(&obj.PoolAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `TokenAddress`:
+ err = decoder.Decode(&obj.TokenAddress)
+ if err != nil {
+ return err
+ }
+ // Deserialize `Decimals`:
+ err = decoder.Decode(&obj.Decimals)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type PoolType ag_binary.BorshEnum
+
+const (
+ LockAndRelease_PoolType PoolType = iota
+ BurnAndMint_PoolType
+ Wrapped_PoolType
+)
+
+func (value PoolType) String() string {
+ switch value {
+ case LockAndRelease_PoolType:
+ return "LockAndRelease"
+ case BurnAndMint_PoolType:
+ return "BurnAndMint"
+ case Wrapped_PoolType:
+ return "Wrapped"
+ default:
+ return ""
+ }
+}
diff --git a/chains/solana/scripts/anchor-go-gen.sh b/chains/solana/scripts/anchor-go-gen.sh
new file mode 100755
index 000000000..f1eb2a713
--- /dev/null
+++ b/chains/solana/scripts/anchor-go-gen.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -e
+
+for idl_path_str in "contracts/target/idl"/*
+do
+ IFS='/' read -r -a idl_path <<< "${idl_path_str}"
+ IFS='.' read -r -a idl_name <<< "${idl_path[3]}"
+ anchor-go -src "${idl_path_str}" -dst ./gobindings/"${idl_name}" -codec borsh
+done
+
+go fmt ./...