From 814a10af5610b2b441f8f94142a70940f8037ad6 Mon Sep 17 00:00:00 2001 From: Aaron Lu <50029043+aalu1418@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:36:32 -0700 Subject: [PATCH] solana: porting over contracts (#366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * solana: porting over contracts Co-authored-by: Agustina Aldasoro Co-authored-by: Tobías Lichtig Co-authored-by: Jonghyeon Park Co-authored-by: PabloMansanet * cleanup gitignore * add solana build cache * enable tests * enable linting * fix lint * reorganize tests * use ccip-onchain-solana team as codeowner * Update chains/solana/.gitignore Co-authored-by: Agustina Aldasoro * move contracts/generated -> gobindings * add readme * go fmt --------- Co-authored-by: Agustina Aldasoro Co-authored-by: Tobías Lichtig Co-authored-by: Jonghyeon Park Co-authored-by: PabloMansanet --- .github/workflows/solana.yml | 203 + CODEOWNERS | 3 + chains/solana/.gitignore | 29 + chains/solana/.golangci.yml | 156 + chains/solana/Makefile | 37 + chains/solana/README.md | 28 + chains/solana/contracts/.dockerignore | 1 + chains/solana/contracts/.env.template | 4 + chains/solana/contracts/.prettierignore | 2 + chains/solana/contracts/Anchor.toml | 33 + chains/solana/contracts/Cargo.lock | 2759 ++++++++++ chains/solana/contracts/Cargo.toml | 18 + chains/solana/contracts/Dockerfile | 10 + chains/solana/contracts/README.md | 97 + .../contracts/crates/arrayvec/Cargo.toml | 8 + .../contracts/crates/arrayvec/src/lib.rs | 165 + .../programs/access-controller/Cargo.toml | 21 + .../programs/access-controller/Xargo.toml | 2 + .../programs/access-controller/src/lib.rs | 141 + .../programs/ccip-invalid-receiver/Cargo.toml | 19 + .../programs/ccip-invalid-receiver/Xargo.toml | 2 + .../programs/ccip-invalid-receiver/src/lib.rs | 62 + .../programs/ccip-receiver/Cargo.toml | 21 + .../programs/ccip-receiver/Xargo.toml | 2 + .../programs/ccip-receiver/src/lib.rs | 197 + .../contracts/programs/ccip-router/Cargo.toml | 25 + .../contracts/programs/ccip-router/README.md | 117 + .../contracts/programs/ccip-router/Xargo.toml | 2 + .../programs/ccip-router/src/context.rs | 648 +++ .../programs/ccip-router/src/event.rs | 138 + .../programs/ccip-router/src/fee_quoter.rs | 141 + .../contracts/programs/ccip-router/src/lib.rs | 1620 ++++++ .../programs/ccip-router/src/merkle.rs | 138 + .../programs/ccip-router/src/messages.rs | 346 ++ .../programs/ccip-router/src/ocr3base.rs | 312 ++ .../programs/ccip-router/src/pools.rs | 294 + .../programs/ccip-router/src/state.rs | 331 ++ .../programs/ccip-router/src/token_context.rs | 114 + .../external-program-cpi-stub/Cargo.toml | 19 + .../external-program-cpi-stub/Xargo.toml | 2 + .../external-program-cpi-stub/src/lib.rs | 101 + .../solana/contracts/programs/mcm/Cargo.toml | 22 + .../solana/contracts/programs/mcm/Xargo.toml | 2 + .../contracts/programs/mcm/src/constant.rs | 24 + .../contracts/programs/mcm/src/error.rs | 121 + .../contracts/programs/mcm/src/eth_utils.rs | 327 ++ .../contracts/programs/mcm/src/event.rs | 35 + .../programs/mcm/src/instructions/execute.rs | 277 + .../programs/mcm/src/instructions/mod.rs | 7 + .../mcm/src/instructions/set_config.rs | 301 ++ .../programs/mcm/src/instructions/set_root.rs | 345 ++ .../solana/contracts/programs/mcm/src/lib.rs | 251 + .../programs/mcm/src/state/config.rs | 58 + .../contracts/programs/mcm/src/state/mod.rs | 2 + .../contracts/programs/mcm/src/state/root.rs | 51 + .../contracts/programs/timelock/Cargo.toml | 24 + .../contracts/programs/timelock/Xargo.toml | 2 + .../contracts/programs/timelock/src/access.rs | 61 + .../programs/timelock/src/constants.rs | 8 + .../contracts/programs/timelock/src/error.rs | 54 + .../contracts/programs/timelock/src/event.rs | 55 + .../timelock/src/instructions/cancel.rs | 37 + .../timelock/src/instructions/execute.rs | 184 + .../timelock/src/instructions/initialize.rs | 108 + .../programs/timelock/src/instructions/mod.rs | 9 + .../timelock/src/instructions/schedule.rs | 230 + .../contracts/programs/timelock/src/lib.rs | 195 + .../programs/timelock/src/state/config.rs | 36 + .../programs/timelock/src/state/mod.rs | 5 + .../programs/timelock/src/state/operation.rs | 273 + .../contracts/programs/token-pool/Cargo.toml | 22 + .../contracts/programs/token-pool/Xargo.toml | 2 + .../programs/token-pool/src/context.rs | 275 + .../contracts/programs/token-pool/src/lib.rs | 609 +++ .../programs/token-pool/src/rate_limiter.rs | 131 + .../target/idl/access_controller.json | 174 + .../target/idl/ccip_invalid_receiver.json | 103 + .../contracts/target/idl/ccip_receiver.json | 302 ++ .../contracts/target/idl/ccip_router.json | 3236 ++++++++++++ .../target/idl/external_program_cpi_stub.json | 100 + chains/solana/contracts/target/idl/mcm.json | 1194 +++++ .../solana/contracts/target/idl/timelock.json | 917 ++++ .../contracts/target/idl/token_pool.json | 845 +++ .../contracts/tests/ccip/ccip_errors.go | 63 + .../contracts/tests/ccip/ccip_events.go | 67 + .../contracts/tests/ccip/ccip_messages.go | 192 + .../tests/ccip/ccip_messages_test.go | 43 + .../contracts/tests/ccip/ccip_router_test.go | 4705 +++++++++++++++++ .../contracts/tests/ccip/ccip_transactions.go | 55 + .../solana/contracts/tests/ccip/tokenpool.go | 191 + .../contracts/tests/ccip/tokenpool_test.go | 425 ++ .../tests/config/access_controller_config.go | 10 + .../contracts/tests/config/ccip_config.go | 54 + .../solana/contracts/tests/config/config.go | 41 + .../contracts/tests/config/mcm_config.go | 64 + .../contracts/tests/config/timelock_config.go | 25 + .../contracts/tests/lookuptable_test.go | 124 + chains/solana/contracts/tests/mcms/mcm.go | 265 + .../solana/contracts/tests/mcms/mcm_errors.go | 99 + .../tests/mcms/mcm_multiple_multisigs_test.go | 369 ++ .../tests/mcms/mcm_set_config_test.go | 444 ++ .../tests/mcms/mcm_set_root_execute_test.go | 542 ++ .../contracts/tests/mcms/mcm_timelock_test.go | 1619 ++++++ .../solana/contracts/tests/mcms/timelock.go | 197 + .../mcms/timelock_bypasser_execute_test.go | 345 ++ .../tests/mcms/timelock_operations.go | 160 + .../tests/mcms/timelock_rbac_test.go | 448 ++ .../mcms/timelock_schedule_execute_test.go | 560 ++ chains/solana/contracts/tests/utils/anchor.go | 318 ++ .../contracts/tests/utils/anchor_test.go | 189 + .../contracts/tests/utils/eth/signer.go | 148 + .../tests/utils/fees/computebudget.go | 203 + .../tests/utils/fees/computebudget_test.go | 224 + .../contracts/tests/utils/localvalidator.go | 94 + .../contracts/tests/utils/lookuptable.go | 157 + .../contracts/tests/utils/mcms/common.go | 133 + .../contracts/tests/utils/mcms/common_test.go | 77 + .../solana/contracts/tests/utils/mcms/mcm.go | 130 + .../contracts/tests/utils/mcms/merkle.go | 404 ++ .../contracts/tests/utils/mcms/merkle_test.go | 174 + .../contracts/tests/utils/mcms/timelock.go | 85 + chains/solana/contracts/tests/utils/ocr.go | 67 + .../contracts/tests/utils/offchainConfig.go | 9 + .../contracts/tests/utils/project_path.go | 14 + chains/solana/contracts/tests/utils/token.go | 153 + .../contracts/tests/utils/transactions.go | 259 + chains/solana/contracts/tsconfig.json | 9 + chains/solana/go.mod | 62 + chains/solana/go.sum | 224 + .../gobindings/access_controller/.gitkeep | 0 .../access_controller/AcceptOwnership.go | 117 + .../access_controller/AcceptOwnership_test.go | 32 + .../gobindings/access_controller/AddAccess.go | 136 + .../access_controller/AddAccess_test.go | 32 + .../access_controller/Initialize.go | 117 + .../access_controller/Initialize_test.go | 32 + .../access_controller/RemoveAccess.go | 136 + .../access_controller/RemoveAccess_test.go | 32 + .../access_controller/TransferOwnership.go | 146 + .../TransferOwnership_test.go | 32 + .../gobindings/access_controller/accounts.go | 73 + .../access_controller/instructions.go | 145 + .../access_controller/testing_utils.go | 20 + .../gobindings/access_controller/types.go | 41 + .../ccip_invalid_receiver/CcipReceive.go | 165 + .../ccip_invalid_receiver/CcipReceive_test.go | 32 + .../ccip_invalid_receiver/accounts.go | 50 + .../ccip_invalid_receiver/instructions.go | 117 + .../ccip_invalid_receiver/testing_utils.go | 20 + .../gobindings/ccip_invalid_receiver/types.go | 107 + .../gobindings/ccip_receiver/CcipReceive.go | 189 + .../ccip_receiver/CcipReceive_test.go | 32 + .../ccip_receiver/CcipTokenLockBurn.go | 184 + .../ccip_receiver/CcipTokenLockBurn_test.go | 32 + .../ccip_receiver/CcipTokenReleaseMint.go | 184 + .../CcipTokenReleaseMint_test.go | 32 + .../gobindings/ccip_receiver/Initialize.go | 155 + .../ccip_receiver/Initialize_test.go | 32 + .../gobindings/ccip_receiver/accounts.go | 80 + .../gobindings/ccip_receiver/instructions.go | 147 + .../gobindings/ccip_receiver/testing_utils.go | 20 + .../solana/gobindings/ccip_receiver/types.go | 277 + .../AcceptAdminRoleTokenAdminRegistry.go | 153 + .../AcceptAdminRoleTokenAdminRegistry_test.go | 32 + .../gobindings/ccip_router/AcceptOwnership.go | 124 + .../ccip_router/AcceptOwnership_test.go | 32 + .../ccip_router/AddBillingTokenConfig.go | 291 + .../ccip_router/AddBillingTokenConfig_test.go | 32 + .../ccip_router/AddChainSelector.go | 241 + .../ccip_router/AddChainSelector_test.go | 32 + .../solana/gobindings/ccip_router/CcipSend.go | 378 ++ .../gobindings/ccip_router/CcipSend_test.go | 32 + .../solana/gobindings/ccip_router/Commit.go | 290 + .../gobindings/ccip_router/Commit_test.go | 32 + .../ccip_router/DisableDestChainSelector.go | 172 + .../DisableDestChainSelector_test.go | 32 + .../ccip_router/DisableSourceChainSelector.go | 172 + .../DisableSourceChainSelector_test.go | 32 + .../solana/gobindings/ccip_router/Execute.go | 304 ++ .../gobindings/ccip_router/Execute_test.go | 32 + .../solana/gobindings/ccip_router/GetFee.go | 179 + .../gobindings/ccip_router/GetFee_test.go | 32 + .../gobindings/ccip_router/Initialize.go | 320 ++ .../gobindings/ccip_router/Initialize_test.go | 32 + .../gobindings/ccip_router/ManuallyExecute.go | 269 + .../ccip_router/ManuallyExecute_test.go | 32 + ...gisterTokenAdminRegistryViaGetCcipAdmin.go | 213 + ...rTokenAdminRegistryViaGetCcipAdmin_test.go | 32 + .../RegisterTokenAdminRegistryViaOwner.go | 161 + ...RegisterTokenAdminRegistryViaOwner_test.go | 32 + .../ccip_router/RemoveBillingTokenConfig.go | 242 + .../RemoveBillingTokenConfig_test.go | 32 + .../gobindings/ccip_router/SetOcrConfig.go | 224 + .../ccip_router/SetOcrConfig_test.go | 32 + .../solana/gobindings/ccip_router/SetPool.go | 177 + .../gobindings/ccip_router/SetPool_test.go | 32 + .../gobindings/ccip_router/SetTokenBilling.go | 239 + .../ccip_router/SetTokenBilling_test.go | 32 + .../TransferAdminRoleTokenAdminRegistry.go | 177 + ...ransferAdminRoleTokenAdminRegistry_test.go | 32 + .../ccip_router/TransferOwnership.go | 153 + .../ccip_router/TransferOwnership_test.go | 32 + .../ccip_router/UpdateBillingTokenConfig.go | 171 + .../UpdateBillingTokenConfig_test.go | 32 + .../UpdateDefaultAllowOutOfOrderExecution.go | 171 + ...ateDefaultAllowOutOfOrderExecution_test.go | 32 + .../ccip_router/UpdateDefaultGasLimit.go | 173 + .../ccip_router/UpdateDefaultGasLimit_test.go | 32 + .../ccip_router/UpdateDestChainConfig.go | 196 + .../ccip_router/UpdateDestChainConfig_test.go | 32 + .../UpdateEnableManualExecutionAfter.go | 173 + .../UpdateEnableManualExecutionAfter_test.go | 32 + .../ccip_router/UpdateSolanaChainSelector.go | 172 + .../UpdateSolanaChainSelector_test.go | 32 + .../ccip_router/UpdateSourceChainConfig.go | 196 + .../UpdateSourceChainConfig_test.go | 32 + .../solana/gobindings/ccip_router/accounts.go | 619 +++ .../gobindings/ccip_router/instructions.go | 570 ++ .../gobindings/ccip_router/testing_utils.go | 20 + chains/solana/gobindings/ccip_router/types.go | 2074 ++++++++ .../external_program_cpi_stub/AccountMut.go | 136 + .../AccountMut_test.go | 32 + .../external_program_cpi_stub/AccountRead.go | 98 + .../AccountRead_test.go | 32 + .../external_program_cpi_stub/Empty.go | 76 + .../external_program_cpi_stub/Empty_test.go | 32 + .../external_program_cpi_stub/Initialize.go | 136 + .../Initialize_test.go | 32 + .../StructInstructionData.go | 107 + .../StructInstructionData_test.go | 32 + .../U8InstructionData.go | 107 + .../U8InstructionData_test.go | 32 + .../external_program_cpi_stub/accounts.go | 50 + .../external_program_cpi_stub/instructions.go | 152 + .../testing_utils.go | 20 + .../external_program_cpi_stub/types.go | 3 + .../solana/gobindings/mcm/AcceptOwnership.go | 146 + .../gobindings/mcm/AcceptOwnership_test.go | 32 + .../solana/gobindings/mcm/AppendSignatures.go | 215 + .../gobindings/mcm/AppendSignatures_test.go | 32 + chains/solana/gobindings/mcm/AppendSigners.go | 188 + .../gobindings/mcm/AppendSigners_test.go | 32 + .../solana/gobindings/mcm/ClearSignatures.go | 192 + .../gobindings/mcm/ClearSignatures_test.go | 32 + chains/solana/gobindings/mcm/ClearSigners.go | 165 + .../gobindings/mcm/ClearSigners_test.go | 32 + chains/solana/gobindings/mcm/Execute.go | 314 ++ chains/solana/gobindings/mcm/Execute_test.go | 32 + .../gobindings/mcm/FinalizeSignatures.go | 192 + .../gobindings/mcm/FinalizeSignatures_test.go | 32 + .../solana/gobindings/mcm/FinalizeSigners.go | 165 + .../gobindings/mcm/FinalizeSigners_test.go | 32 + .../solana/gobindings/mcm/InitSignatures.go | 234 + .../gobindings/mcm/InitSignatures_test.go | 32 + chains/solana/gobindings/mcm/InitSigners.go | 207 + .../solana/gobindings/mcm/InitSigners_test.go | 32 + chains/solana/gobindings/mcm/Initialize.go | 265 + .../solana/gobindings/mcm/Initialize_test.go | 32 + chains/solana/gobindings/mcm/SetConfig.go | 276 + .../solana/gobindings/mcm/SetConfig_test.go | 32 + chains/solana/gobindings/mcm/SetRoot.go | 333 ++ chains/solana/gobindings/mcm/SetRoot_test.go | 32 + .../gobindings/mcm/TransferOwnership.go | 169 + .../gobindings/mcm/TransferOwnership_test.go | 32 + chains/solana/gobindings/mcm/accounts.go | 459 ++ chains/solana/gobindings/mcm/instructions.go | 212 + chains/solana/gobindings/mcm/testing_utils.go | 20 + chains/solana/gobindings/mcm/types.go | 162 + .../gobindings/timelock/AcceptOwnership.go | 117 + .../timelock/AcceptOwnership_test.go | 32 + .../gobindings/timelock/AppendInstructions.go | 188 + .../timelock/AppendInstructions_test.go | 32 + .../gobindings/timelock/BatchAddAccess.go | 184 + .../timelock/BatchAddAccess_test.go | 32 + .../timelock/BlockFunctionSelector.go | 146 + .../timelock/BlockFunctionSelector_test.go | 32 + .../timelock/BypasserExecuteBatch.go | 203 + .../timelock/BypasserExecuteBatch_test.go | 32 + chains/solana/gobindings/timelock/Cancel.go | 184 + .../solana/gobindings/timelock/Cancel_test.go | 32 + .../gobindings/timelock/ClearOperation.go | 146 + .../timelock/ClearOperation_test.go | 32 + .../gobindings/timelock/ExecuteBatch.go | 222 + .../gobindings/timelock/ExecuteBatch_test.go | 32 + .../gobindings/timelock/FinalizeOperation.go | 146 + .../timelock/FinalizeOperation_test.go | 32 + .../solana/gobindings/timelock/Initialize.go | 298 ++ .../timelock/InitializeOperation.go | 272 + .../timelock/InitializeOperation_test.go | 32 + .../gobindings/timelock/Initialize_test.go | 32 + .../gobindings/timelock/ScheduleBatch.go | 207 + .../gobindings/timelock/ScheduleBatch_test.go | 32 + .../gobindings/timelock/TransferOwnership.go | 146 + .../timelock/TransferOwnership_test.go | 32 + .../timelock/UnblockFunctionSelector.go | 146 + .../timelock/UnblockFunctionSelector_test.go | 32 + .../solana/gobindings/timelock/UpdateDelay.go | 146 + .../gobindings/timelock/UpdateDelay_test.go | 32 + chains/solana/gobindings/timelock/accounts.go | 236 + .../gobindings/timelock/instructions.go | 215 + .../gobindings/timelock/testing_utils.go | 20 + chains/solana/gobindings/timelock/types.go | 177 + .../gobindings/token_pool/AcceptOwnership.go | 117 + .../token_pool/AcceptOwnership_test.go | 32 + .../token_pool/DeleteChainConfig.go | 207 + .../token_pool/DeleteChainConfig_test.go | 32 + .../gobindings/token_pool/Initialize.go | 226 + .../gobindings/token_pool/Initialize_test.go | 32 + .../gobindings/token_pool/LockOrBurnTokens.go | 241 + .../token_pool/LockOrBurnTokens_test.go | 32 + .../token_pool/ReleaseOrMintTokens.go | 260 + .../token_pool/ReleaseOrMintTokens_test.go | 32 + .../token_pool/SetChainRateLimit.go | 253 + .../token_pool/SetChainRateLimit_test.go | 32 + .../token_pool/SetChainRemoteConfig.go | 230 + .../token_pool/SetChainRemoteConfig_test.go | 32 + .../gobindings/token_pool/SetRampAuthority.go | 146 + .../token_pool/SetRampAuthority_test.go | 32 + .../token_pool/TransferOwnership.go | 146 + .../token_pool/TransferOwnership_test.go | 32 + .../solana/gobindings/token_pool/accounts.go | 244 + .../gobindings/token_pool/instructions.go | 173 + .../gobindings/token_pool/testing_utils.go | 20 + chains/solana/gobindings/token_pool/types.go | 386 ++ chains/solana/scripts/anchor-go-gen.sh | 12 + 325 files changed, 58713 insertions(+) create mode 100644 .github/workflows/solana.yml create mode 100644 chains/solana/.gitignore create mode 100644 chains/solana/.golangci.yml create mode 100644 chains/solana/Makefile create mode 100644 chains/solana/README.md create mode 100644 chains/solana/contracts/.dockerignore create mode 100644 chains/solana/contracts/.env.template create mode 100644 chains/solana/contracts/.prettierignore create mode 100644 chains/solana/contracts/Anchor.toml create mode 100644 chains/solana/contracts/Cargo.lock create mode 100644 chains/solana/contracts/Cargo.toml create mode 100644 chains/solana/contracts/Dockerfile create mode 100644 chains/solana/contracts/README.md create mode 100644 chains/solana/contracts/crates/arrayvec/Cargo.toml create mode 100644 chains/solana/contracts/crates/arrayvec/src/lib.rs create mode 100644 chains/solana/contracts/programs/access-controller/Cargo.toml create mode 100644 chains/solana/contracts/programs/access-controller/Xargo.toml create mode 100644 chains/solana/contracts/programs/access-controller/src/lib.rs create mode 100644 chains/solana/contracts/programs/ccip-invalid-receiver/Cargo.toml create mode 100644 chains/solana/contracts/programs/ccip-invalid-receiver/Xargo.toml create mode 100644 chains/solana/contracts/programs/ccip-invalid-receiver/src/lib.rs create mode 100644 chains/solana/contracts/programs/ccip-receiver/Cargo.toml create mode 100644 chains/solana/contracts/programs/ccip-receiver/Xargo.toml create mode 100644 chains/solana/contracts/programs/ccip-receiver/src/lib.rs create mode 100644 chains/solana/contracts/programs/ccip-router/Cargo.toml create mode 100644 chains/solana/contracts/programs/ccip-router/README.md create mode 100644 chains/solana/contracts/programs/ccip-router/Xargo.toml create mode 100644 chains/solana/contracts/programs/ccip-router/src/context.rs create mode 100644 chains/solana/contracts/programs/ccip-router/src/event.rs create mode 100644 chains/solana/contracts/programs/ccip-router/src/fee_quoter.rs create mode 100644 chains/solana/contracts/programs/ccip-router/src/lib.rs create mode 100644 chains/solana/contracts/programs/ccip-router/src/merkle.rs create mode 100644 chains/solana/contracts/programs/ccip-router/src/messages.rs create mode 100644 chains/solana/contracts/programs/ccip-router/src/ocr3base.rs create mode 100644 chains/solana/contracts/programs/ccip-router/src/pools.rs create mode 100644 chains/solana/contracts/programs/ccip-router/src/state.rs create mode 100644 chains/solana/contracts/programs/ccip-router/src/token_context.rs create mode 100644 chains/solana/contracts/programs/external-program-cpi-stub/Cargo.toml create mode 100644 chains/solana/contracts/programs/external-program-cpi-stub/Xargo.toml create mode 100644 chains/solana/contracts/programs/external-program-cpi-stub/src/lib.rs create mode 100644 chains/solana/contracts/programs/mcm/Cargo.toml create mode 100644 chains/solana/contracts/programs/mcm/Xargo.toml create mode 100644 chains/solana/contracts/programs/mcm/src/constant.rs create mode 100644 chains/solana/contracts/programs/mcm/src/error.rs create mode 100644 chains/solana/contracts/programs/mcm/src/eth_utils.rs create mode 100644 chains/solana/contracts/programs/mcm/src/event.rs create mode 100644 chains/solana/contracts/programs/mcm/src/instructions/execute.rs create mode 100644 chains/solana/contracts/programs/mcm/src/instructions/mod.rs create mode 100644 chains/solana/contracts/programs/mcm/src/instructions/set_config.rs create mode 100644 chains/solana/contracts/programs/mcm/src/instructions/set_root.rs create mode 100644 chains/solana/contracts/programs/mcm/src/lib.rs create mode 100644 chains/solana/contracts/programs/mcm/src/state/config.rs create mode 100644 chains/solana/contracts/programs/mcm/src/state/mod.rs create mode 100644 chains/solana/contracts/programs/mcm/src/state/root.rs create mode 100644 chains/solana/contracts/programs/timelock/Cargo.toml create mode 100644 chains/solana/contracts/programs/timelock/Xargo.toml create mode 100644 chains/solana/contracts/programs/timelock/src/access.rs create mode 100644 chains/solana/contracts/programs/timelock/src/constants.rs create mode 100644 chains/solana/contracts/programs/timelock/src/error.rs create mode 100644 chains/solana/contracts/programs/timelock/src/event.rs create mode 100644 chains/solana/contracts/programs/timelock/src/instructions/cancel.rs create mode 100644 chains/solana/contracts/programs/timelock/src/instructions/execute.rs create mode 100644 chains/solana/contracts/programs/timelock/src/instructions/initialize.rs create mode 100644 chains/solana/contracts/programs/timelock/src/instructions/mod.rs create mode 100644 chains/solana/contracts/programs/timelock/src/instructions/schedule.rs create mode 100644 chains/solana/contracts/programs/timelock/src/lib.rs create mode 100644 chains/solana/contracts/programs/timelock/src/state/config.rs create mode 100644 chains/solana/contracts/programs/timelock/src/state/mod.rs create mode 100644 chains/solana/contracts/programs/timelock/src/state/operation.rs create mode 100644 chains/solana/contracts/programs/token-pool/Cargo.toml create mode 100644 chains/solana/contracts/programs/token-pool/Xargo.toml create mode 100644 chains/solana/contracts/programs/token-pool/src/context.rs create mode 100644 chains/solana/contracts/programs/token-pool/src/lib.rs create mode 100644 chains/solana/contracts/programs/token-pool/src/rate_limiter.rs create mode 100644 chains/solana/contracts/target/idl/access_controller.json create mode 100644 chains/solana/contracts/target/idl/ccip_invalid_receiver.json create mode 100644 chains/solana/contracts/target/idl/ccip_receiver.json create mode 100644 chains/solana/contracts/target/idl/ccip_router.json create mode 100644 chains/solana/contracts/target/idl/external_program_cpi_stub.json create mode 100644 chains/solana/contracts/target/idl/mcm.json create mode 100644 chains/solana/contracts/target/idl/timelock.json create mode 100644 chains/solana/contracts/target/idl/token_pool.json create mode 100644 chains/solana/contracts/tests/ccip/ccip_errors.go create mode 100644 chains/solana/contracts/tests/ccip/ccip_events.go create mode 100644 chains/solana/contracts/tests/ccip/ccip_messages.go create mode 100644 chains/solana/contracts/tests/ccip/ccip_messages_test.go create mode 100644 chains/solana/contracts/tests/ccip/ccip_router_test.go create mode 100644 chains/solana/contracts/tests/ccip/ccip_transactions.go create mode 100644 chains/solana/contracts/tests/ccip/tokenpool.go create mode 100644 chains/solana/contracts/tests/ccip/tokenpool_test.go create mode 100644 chains/solana/contracts/tests/config/access_controller_config.go create mode 100644 chains/solana/contracts/tests/config/ccip_config.go create mode 100644 chains/solana/contracts/tests/config/config.go create mode 100644 chains/solana/contracts/tests/config/mcm_config.go create mode 100644 chains/solana/contracts/tests/config/timelock_config.go create mode 100644 chains/solana/contracts/tests/lookuptable_test.go create mode 100644 chains/solana/contracts/tests/mcms/mcm.go create mode 100644 chains/solana/contracts/tests/mcms/mcm_errors.go create mode 100644 chains/solana/contracts/tests/mcms/mcm_multiple_multisigs_test.go create mode 100644 chains/solana/contracts/tests/mcms/mcm_set_config_test.go create mode 100644 chains/solana/contracts/tests/mcms/mcm_set_root_execute_test.go create mode 100644 chains/solana/contracts/tests/mcms/mcm_timelock_test.go create mode 100644 chains/solana/contracts/tests/mcms/timelock.go create mode 100644 chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go create mode 100644 chains/solana/contracts/tests/mcms/timelock_operations.go create mode 100644 chains/solana/contracts/tests/mcms/timelock_rbac_test.go create mode 100644 chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go create mode 100644 chains/solana/contracts/tests/utils/anchor.go create mode 100644 chains/solana/contracts/tests/utils/anchor_test.go create mode 100644 chains/solana/contracts/tests/utils/eth/signer.go create mode 100644 chains/solana/contracts/tests/utils/fees/computebudget.go create mode 100644 chains/solana/contracts/tests/utils/fees/computebudget_test.go create mode 100644 chains/solana/contracts/tests/utils/localvalidator.go create mode 100644 chains/solana/contracts/tests/utils/lookuptable.go create mode 100644 chains/solana/contracts/tests/utils/mcms/common.go create mode 100644 chains/solana/contracts/tests/utils/mcms/common_test.go create mode 100644 chains/solana/contracts/tests/utils/mcms/mcm.go create mode 100644 chains/solana/contracts/tests/utils/mcms/merkle.go create mode 100644 chains/solana/contracts/tests/utils/mcms/merkle_test.go create mode 100644 chains/solana/contracts/tests/utils/mcms/timelock.go create mode 100644 chains/solana/contracts/tests/utils/ocr.go create mode 100644 chains/solana/contracts/tests/utils/offchainConfig.go create mode 100644 chains/solana/contracts/tests/utils/project_path.go create mode 100644 chains/solana/contracts/tests/utils/token.go create mode 100644 chains/solana/contracts/tests/utils/transactions.go create mode 100644 chains/solana/contracts/tsconfig.json create mode 100644 chains/solana/go.mod create mode 100644 chains/solana/go.sum create mode 100644 chains/solana/gobindings/access_controller/.gitkeep create mode 100644 chains/solana/gobindings/access_controller/AcceptOwnership.go create mode 100644 chains/solana/gobindings/access_controller/AcceptOwnership_test.go create mode 100644 chains/solana/gobindings/access_controller/AddAccess.go create mode 100644 chains/solana/gobindings/access_controller/AddAccess_test.go create mode 100644 chains/solana/gobindings/access_controller/Initialize.go create mode 100644 chains/solana/gobindings/access_controller/Initialize_test.go create mode 100644 chains/solana/gobindings/access_controller/RemoveAccess.go create mode 100644 chains/solana/gobindings/access_controller/RemoveAccess_test.go create mode 100644 chains/solana/gobindings/access_controller/TransferOwnership.go create mode 100644 chains/solana/gobindings/access_controller/TransferOwnership_test.go create mode 100644 chains/solana/gobindings/access_controller/accounts.go create mode 100644 chains/solana/gobindings/access_controller/instructions.go create mode 100644 chains/solana/gobindings/access_controller/testing_utils.go create mode 100644 chains/solana/gobindings/access_controller/types.go create mode 100644 chains/solana/gobindings/ccip_invalid_receiver/CcipReceive.go create mode 100644 chains/solana/gobindings/ccip_invalid_receiver/CcipReceive_test.go create mode 100644 chains/solana/gobindings/ccip_invalid_receiver/accounts.go create mode 100644 chains/solana/gobindings/ccip_invalid_receiver/instructions.go create mode 100644 chains/solana/gobindings/ccip_invalid_receiver/testing_utils.go create mode 100644 chains/solana/gobindings/ccip_invalid_receiver/types.go create mode 100644 chains/solana/gobindings/ccip_receiver/CcipReceive.go create mode 100644 chains/solana/gobindings/ccip_receiver/CcipReceive_test.go create mode 100644 chains/solana/gobindings/ccip_receiver/CcipTokenLockBurn.go create mode 100644 chains/solana/gobindings/ccip_receiver/CcipTokenLockBurn_test.go create mode 100644 chains/solana/gobindings/ccip_receiver/CcipTokenReleaseMint.go create mode 100644 chains/solana/gobindings/ccip_receiver/CcipTokenReleaseMint_test.go create mode 100644 chains/solana/gobindings/ccip_receiver/Initialize.go create mode 100644 chains/solana/gobindings/ccip_receiver/Initialize_test.go create mode 100644 chains/solana/gobindings/ccip_receiver/accounts.go create mode 100644 chains/solana/gobindings/ccip_receiver/instructions.go create mode 100644 chains/solana/gobindings/ccip_receiver/testing_utils.go create mode 100644 chains/solana/gobindings/ccip_receiver/types.go create mode 100644 chains/solana/gobindings/ccip_router/AcceptAdminRoleTokenAdminRegistry.go create mode 100644 chains/solana/gobindings/ccip_router/AcceptAdminRoleTokenAdminRegistry_test.go create mode 100644 chains/solana/gobindings/ccip_router/AcceptOwnership.go create mode 100644 chains/solana/gobindings/ccip_router/AcceptOwnership_test.go create mode 100644 chains/solana/gobindings/ccip_router/AddBillingTokenConfig.go create mode 100644 chains/solana/gobindings/ccip_router/AddBillingTokenConfig_test.go create mode 100644 chains/solana/gobindings/ccip_router/AddChainSelector.go create mode 100644 chains/solana/gobindings/ccip_router/AddChainSelector_test.go create mode 100644 chains/solana/gobindings/ccip_router/CcipSend.go create mode 100644 chains/solana/gobindings/ccip_router/CcipSend_test.go create mode 100644 chains/solana/gobindings/ccip_router/Commit.go create mode 100644 chains/solana/gobindings/ccip_router/Commit_test.go create mode 100644 chains/solana/gobindings/ccip_router/DisableDestChainSelector.go create mode 100644 chains/solana/gobindings/ccip_router/DisableDestChainSelector_test.go create mode 100644 chains/solana/gobindings/ccip_router/DisableSourceChainSelector.go create mode 100644 chains/solana/gobindings/ccip_router/DisableSourceChainSelector_test.go create mode 100644 chains/solana/gobindings/ccip_router/Execute.go create mode 100644 chains/solana/gobindings/ccip_router/Execute_test.go create mode 100644 chains/solana/gobindings/ccip_router/GetFee.go create mode 100644 chains/solana/gobindings/ccip_router/GetFee_test.go create mode 100644 chains/solana/gobindings/ccip_router/Initialize.go create mode 100644 chains/solana/gobindings/ccip_router/Initialize_test.go create mode 100644 chains/solana/gobindings/ccip_router/ManuallyExecute.go create mode 100644 chains/solana/gobindings/ccip_router/ManuallyExecute_test.go create mode 100644 chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaGetCcipAdmin.go create mode 100644 chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaGetCcipAdmin_test.go create mode 100644 chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaOwner.go create mode 100644 chains/solana/gobindings/ccip_router/RegisterTokenAdminRegistryViaOwner_test.go create mode 100644 chains/solana/gobindings/ccip_router/RemoveBillingTokenConfig.go create mode 100644 chains/solana/gobindings/ccip_router/RemoveBillingTokenConfig_test.go create mode 100644 chains/solana/gobindings/ccip_router/SetOcrConfig.go create mode 100644 chains/solana/gobindings/ccip_router/SetOcrConfig_test.go create mode 100644 chains/solana/gobindings/ccip_router/SetPool.go create mode 100644 chains/solana/gobindings/ccip_router/SetPool_test.go create mode 100644 chains/solana/gobindings/ccip_router/SetTokenBilling.go create mode 100644 chains/solana/gobindings/ccip_router/SetTokenBilling_test.go create mode 100644 chains/solana/gobindings/ccip_router/TransferAdminRoleTokenAdminRegistry.go create mode 100644 chains/solana/gobindings/ccip_router/TransferAdminRoleTokenAdminRegistry_test.go create mode 100644 chains/solana/gobindings/ccip_router/TransferOwnership.go create mode 100644 chains/solana/gobindings/ccip_router/TransferOwnership_test.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateBillingTokenConfig.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateBillingTokenConfig_test.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateDefaultAllowOutOfOrderExecution.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateDefaultAllowOutOfOrderExecution_test.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateDefaultGasLimit.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateDefaultGasLimit_test.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateDestChainConfig.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateDestChainConfig_test.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateEnableManualExecutionAfter.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateEnableManualExecutionAfter_test.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateSolanaChainSelector.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateSolanaChainSelector_test.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateSourceChainConfig.go create mode 100644 chains/solana/gobindings/ccip_router/UpdateSourceChainConfig_test.go create mode 100644 chains/solana/gobindings/ccip_router/accounts.go create mode 100644 chains/solana/gobindings/ccip_router/instructions.go create mode 100644 chains/solana/gobindings/ccip_router/testing_utils.go create mode 100644 chains/solana/gobindings/ccip_router/types.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/AccountMut.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/AccountMut_test.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/AccountRead.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/AccountRead_test.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/Empty.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/Empty_test.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/Initialize.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/Initialize_test.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/StructInstructionData.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/StructInstructionData_test.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/U8InstructionData.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/U8InstructionData_test.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/accounts.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/instructions.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/testing_utils.go create mode 100644 chains/solana/gobindings/external_program_cpi_stub/types.go create mode 100644 chains/solana/gobindings/mcm/AcceptOwnership.go create mode 100644 chains/solana/gobindings/mcm/AcceptOwnership_test.go create mode 100644 chains/solana/gobindings/mcm/AppendSignatures.go create mode 100644 chains/solana/gobindings/mcm/AppendSignatures_test.go create mode 100644 chains/solana/gobindings/mcm/AppendSigners.go create mode 100644 chains/solana/gobindings/mcm/AppendSigners_test.go create mode 100644 chains/solana/gobindings/mcm/ClearSignatures.go create mode 100644 chains/solana/gobindings/mcm/ClearSignatures_test.go create mode 100644 chains/solana/gobindings/mcm/ClearSigners.go create mode 100644 chains/solana/gobindings/mcm/ClearSigners_test.go create mode 100644 chains/solana/gobindings/mcm/Execute.go create mode 100644 chains/solana/gobindings/mcm/Execute_test.go create mode 100644 chains/solana/gobindings/mcm/FinalizeSignatures.go create mode 100644 chains/solana/gobindings/mcm/FinalizeSignatures_test.go create mode 100644 chains/solana/gobindings/mcm/FinalizeSigners.go create mode 100644 chains/solana/gobindings/mcm/FinalizeSigners_test.go create mode 100644 chains/solana/gobindings/mcm/InitSignatures.go create mode 100644 chains/solana/gobindings/mcm/InitSignatures_test.go create mode 100644 chains/solana/gobindings/mcm/InitSigners.go create mode 100644 chains/solana/gobindings/mcm/InitSigners_test.go create mode 100644 chains/solana/gobindings/mcm/Initialize.go create mode 100644 chains/solana/gobindings/mcm/Initialize_test.go create mode 100644 chains/solana/gobindings/mcm/SetConfig.go create mode 100644 chains/solana/gobindings/mcm/SetConfig_test.go create mode 100644 chains/solana/gobindings/mcm/SetRoot.go create mode 100644 chains/solana/gobindings/mcm/SetRoot_test.go create mode 100644 chains/solana/gobindings/mcm/TransferOwnership.go create mode 100644 chains/solana/gobindings/mcm/TransferOwnership_test.go create mode 100644 chains/solana/gobindings/mcm/accounts.go create mode 100644 chains/solana/gobindings/mcm/instructions.go create mode 100644 chains/solana/gobindings/mcm/testing_utils.go create mode 100644 chains/solana/gobindings/mcm/types.go create mode 100644 chains/solana/gobindings/timelock/AcceptOwnership.go create mode 100644 chains/solana/gobindings/timelock/AcceptOwnership_test.go create mode 100644 chains/solana/gobindings/timelock/AppendInstructions.go create mode 100644 chains/solana/gobindings/timelock/AppendInstructions_test.go create mode 100644 chains/solana/gobindings/timelock/BatchAddAccess.go create mode 100644 chains/solana/gobindings/timelock/BatchAddAccess_test.go create mode 100644 chains/solana/gobindings/timelock/BlockFunctionSelector.go create mode 100644 chains/solana/gobindings/timelock/BlockFunctionSelector_test.go create mode 100644 chains/solana/gobindings/timelock/BypasserExecuteBatch.go create mode 100644 chains/solana/gobindings/timelock/BypasserExecuteBatch_test.go create mode 100644 chains/solana/gobindings/timelock/Cancel.go create mode 100644 chains/solana/gobindings/timelock/Cancel_test.go create mode 100644 chains/solana/gobindings/timelock/ClearOperation.go create mode 100644 chains/solana/gobindings/timelock/ClearOperation_test.go create mode 100644 chains/solana/gobindings/timelock/ExecuteBatch.go create mode 100644 chains/solana/gobindings/timelock/ExecuteBatch_test.go create mode 100644 chains/solana/gobindings/timelock/FinalizeOperation.go create mode 100644 chains/solana/gobindings/timelock/FinalizeOperation_test.go create mode 100644 chains/solana/gobindings/timelock/Initialize.go create mode 100644 chains/solana/gobindings/timelock/InitializeOperation.go create mode 100644 chains/solana/gobindings/timelock/InitializeOperation_test.go create mode 100644 chains/solana/gobindings/timelock/Initialize_test.go create mode 100644 chains/solana/gobindings/timelock/ScheduleBatch.go create mode 100644 chains/solana/gobindings/timelock/ScheduleBatch_test.go create mode 100644 chains/solana/gobindings/timelock/TransferOwnership.go create mode 100644 chains/solana/gobindings/timelock/TransferOwnership_test.go create mode 100644 chains/solana/gobindings/timelock/UnblockFunctionSelector.go create mode 100644 chains/solana/gobindings/timelock/UnblockFunctionSelector_test.go create mode 100644 chains/solana/gobindings/timelock/UpdateDelay.go create mode 100644 chains/solana/gobindings/timelock/UpdateDelay_test.go create mode 100644 chains/solana/gobindings/timelock/accounts.go create mode 100644 chains/solana/gobindings/timelock/instructions.go create mode 100644 chains/solana/gobindings/timelock/testing_utils.go create mode 100644 chains/solana/gobindings/timelock/types.go create mode 100644 chains/solana/gobindings/token_pool/AcceptOwnership.go create mode 100644 chains/solana/gobindings/token_pool/AcceptOwnership_test.go create mode 100644 chains/solana/gobindings/token_pool/DeleteChainConfig.go create mode 100644 chains/solana/gobindings/token_pool/DeleteChainConfig_test.go create mode 100644 chains/solana/gobindings/token_pool/Initialize.go create mode 100644 chains/solana/gobindings/token_pool/Initialize_test.go create mode 100644 chains/solana/gobindings/token_pool/LockOrBurnTokens.go create mode 100644 chains/solana/gobindings/token_pool/LockOrBurnTokens_test.go create mode 100644 chains/solana/gobindings/token_pool/ReleaseOrMintTokens.go create mode 100644 chains/solana/gobindings/token_pool/ReleaseOrMintTokens_test.go create mode 100644 chains/solana/gobindings/token_pool/SetChainRateLimit.go create mode 100644 chains/solana/gobindings/token_pool/SetChainRateLimit_test.go create mode 100644 chains/solana/gobindings/token_pool/SetChainRemoteConfig.go create mode 100644 chains/solana/gobindings/token_pool/SetChainRemoteConfig_test.go create mode 100644 chains/solana/gobindings/token_pool/SetRampAuthority.go create mode 100644 chains/solana/gobindings/token_pool/SetRampAuthority_test.go create mode 100644 chains/solana/gobindings/token_pool/TransferOwnership.go create mode 100644 chains/solana/gobindings/token_pool/TransferOwnership_test.go create mode 100644 chains/solana/gobindings/token_pool/accounts.go create mode 100644 chains/solana/gobindings/token_pool/instructions.go create mode 100644 chains/solana/gobindings/token_pool/testing_utils.go create mode 100644 chains/solana/gobindings/token_pool/types.go create mode 100755 chains/solana/scripts/anchor-go-gen.sh 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 ./...