diff --git a/chains/solana/contracts/Cargo.lock b/chains/solana/contracts/Cargo.lock index 7a0c87a74..1e8bf6525 100644 --- a/chains/solana/contracts/Cargo.lock +++ b/chains/solana/contracts/Cargo.lock @@ -2401,7 +2401,9 @@ name = "test_ccip_invalid_receiver" version = "0.0.0-dev" dependencies = [ "anchor-lang", + "anchor-spl", "example_ccip_receiver", + "solana-program", ] [[package]] diff --git a/chains/solana/contracts/programs/ccip-offramp/src/context.rs b/chains/solana/contracts/programs/ccip-offramp/src/context.rs index 3a807a82e..d4eaba679 100644 --- a/chains/solana/contracts/programs/ccip-offramp/src/context.rs +++ b/chains/solana/contracts/programs/ccip-offramp/src/context.rs @@ -342,6 +342,8 @@ pub struct CommitReportContext<'info> { // [...chainConfig accounts] fee quoter accounts used to store gas prices } +const ALLOWED_OFFRAMP: &[u8] = b"allowed_offramp"; + #[derive(Accounts)] #[instruction(raw_report: Vec)] pub struct ExecuteReportContext<'info> { @@ -374,6 +376,25 @@ pub struct ExecuteReportContext<'info> { )] pub commit_report: Account<'info, CommitReport>, + pub offramp: Program<'info, CcipOfframp>, + + /// CHECK PDA of the router program verifying the signer is an allowed offramp. + /// If PDA does not exist, the router doesn't allow this offramp + #[account( + constraint = { + let (pda, _) = Pubkey::find_program_address( + &[ + ALLOWED_OFFRAMP, + source_chain.chain_selector.to_le_bytes().as_ref(), + offramp.key().as_ref(), + ], + &reference_addresses.router, + ); + allowed_offramp.key() == pda && allowed_offramp.owner.key() == reference_addresses.router + } @ CcipOfframpError::InvalidInputs + )] + pub allowed_offramp: UncheckedAccount<'info>, + /// CHECK: Using this to sign #[account(seeds = [seed::EXTERNAL_EXECUTION_CONFIG], bump)] pub external_execution_config: Account<'info, ExternalExecutionConfig>, diff --git a/chains/solana/contracts/programs/ccip-offramp/src/instructions/v1/execute.rs b/chains/solana/contracts/programs/ccip-offramp/src/instructions/v1/execute.rs index 535720b6b..7455e58e5 100644 --- a/chains/solana/contracts/programs/ccip-offramp/src/instructions/v1/execute.rs +++ b/chains/solana/contracts/programs/ccip-offramp/src/instructions/v1/execute.rs @@ -165,8 +165,10 @@ fn internal_execute<'info>( 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(&[ + let mut acc_infos = vec![ + router_token_pool_signer.to_account_info(), + ctx.accounts.offramp.to_account_info(), + ctx.accounts.allowed_offramp.to_account_info(), accs.pool_config.to_account_info(), accs.token_program.to_account_info(), accs.mint.to_account_info(), @@ -174,7 +176,7 @@ fn internal_execute<'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(), diff --git a/chains/solana/contracts/programs/test-ccip-invalid-receiver/Cargo.toml b/chains/solana/contracts/programs/test-ccip-invalid-receiver/Cargo.toml index 319124114..18037e1e7 100644 --- a/chains/solana/contracts/programs/test-ccip-invalid-receiver/Cargo.toml +++ b/chains/solana/contracts/programs/test-ccip-invalid-receiver/Cargo.toml @@ -17,4 +17,6 @@ default = [] [dependencies] anchor-lang = "0.29.0" +anchor-spl = "0.29.0" +solana-program = "1.17.25" # pin solana to 1.17 example_ccip_receiver = { version = "0.1.0-dev", path = "../example-ccip-receiver", features = ["no-entrypoint"] } diff --git a/chains/solana/contracts/programs/test-ccip-invalid-receiver/src/lib.rs b/chains/solana/contracts/programs/test-ccip-invalid-receiver/src/lib.rs index 36de6d8d3..54fcf2d82 100644 --- a/chains/solana/contracts/programs/test-ccip-invalid-receiver/src/lib.rs +++ b/chains/solana/contracts/programs/test-ccip-invalid-receiver/src/lib.rs @@ -3,12 +3,17 @@ * Used to test CCIP Router execute and check that it fails */ use anchor_lang::prelude::*; +use anchor_spl::token_interface::{Mint, TokenAccount}; use example_ccip_receiver::Any2SVMMessage; +use program::TestCcipInvalidReceiver; declare_id!("9Vjda3WU2gsJgE4VdU6QuDw8rfHLyigfFyWs3XDPNUn8"); #[program] pub mod test_ccip_invalid_receiver { + use anchor_lang::solana_program::instruction::Instruction; + use anchor_lang::solana_program::program::{get_return_data, invoke_signed}; + use super::*; pub fn ccip_receive(ctx: Context, _message: Any2SVMMessage) -> Result<()> { @@ -19,6 +24,51 @@ pub mod test_ccip_invalid_receiver { Ok(()) } + + // This is just a dumb proxy towards the test_pool program, but signing the call with a PDA that mimics + // what the offramp does + pub fn pool_proxy_release_or_mint<'info>( + ctx: Context<'_, '_, 'info, 'info, PoolProxyReleaseOrMint<'info>>, + release_or_mint: ReleaseOrMintInV1, + ) -> Result> { + let mut acc_infos = vec![ + ctx.accounts.cpi_signer.to_account_info(), + ctx.accounts.offramp_program.to_account_info(), + ctx.accounts.allowed_offramp.to_account_info(), + ctx.accounts.config.to_account_info(), + ctx.accounts.token_program.to_account_info(), + ctx.accounts.mint.to_account_info(), + ctx.accounts.pool_signer.to_account_info(), + ctx.accounts.pool_token_account.to_account_info(), + ctx.accounts.chain_config.to_account_info(), + ctx.accounts.receiver_token_account.to_account_info(), + ]; + + acc_infos.extend_from_slice(ctx.remaining_accounts); + + let acc_metas: Vec = acc_infos + .iter() + .flat_map(|acc_info| { + // Check signer from PDA External Execution config + let is_signer = acc_info.key() == ctx.accounts.cpi_signer.key(); + acc_info.to_account_metas(Some(is_signer)) + }) + .collect(); + + let ix = Instruction { + program_id: ctx.accounts.test_pool.key(), + accounts: acc_metas, + data: release_or_mint.to_tx_data(), + }; + + let seeds: &[&[u8]] = &[b"external_token_pools_signer", &[ctx.bumps.cpi_signer]]; + + invoke_signed(&ix, &acc_infos, &[seeds])?; + + let (_, data) = get_return_data().unwrap(); + + Ok(data) + } } const ANCHOR_DISCRIMINATOR: usize = 8; @@ -46,3 +96,76 @@ pub struct Initialize<'info> { pub struct Counter { pub value: u8, } + +#[derive(Accounts)] +#[instruction(release_or_mint: ReleaseOrMintInV1)] +pub struct PoolProxyReleaseOrMint<'info> { + /// CHECK + pub test_pool: UncheckedAccount<'info>, + + /// CHECK + #[account( + seeds = [b"external_token_pools_signer"], + bump, + )] + pub cpi_signer: UncheckedAccount<'info>, + + /////////////////////////////////// + // Accounts required by Pool CPI // + /////////////////////////////////// + pub offramp_program: Program<'info, TestCcipInvalidReceiver>, // this receiver acts as "dumb" offramp here + + /// CHECK + pub allowed_offramp: UncheckedAccount<'info>, + + /// CHECK + #[account(mut)] + pub config: UncheckedAccount<'info>, + + /// CHECK + pub token_program: AccountInfo<'info>, + + #[account(mut)] + pub mint: InterfaceAccount<'info, Mint>, + + /// CHECK + pub pool_signer: UncheckedAccount<'info>, + + #[account(mut)] + pub pool_token_account: InterfaceAccount<'info, TokenAccount>, + + /// CHECK + #[account(mut)] + pub chain_config: UncheckedAccount<'info>, + + /// CHECK + #[account(mut)] + pub receiver_token_account: UncheckedAccount<'info>, +} + +#[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 +} + +pub const TOKENPOOL_RELEASE_OR_MINT_DISCRIMINATOR: [u8; 8] = + [0x5c, 0x64, 0x96, 0xc6, 0xfc, 0x3f, 0xa4, 0xe4]; // release_or_mint_tokens + +impl ReleaseOrMintInV1 { + pub 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 + } +} diff --git a/chains/solana/contracts/programs/test-ccip-receiver/src/lib.rs b/chains/solana/contracts/programs/test-ccip-receiver/src/lib.rs index 115d95d13..765f94b18 100644 --- a/chains/solana/contracts/programs/test-ccip-receiver/src/lib.rs +++ b/chains/solana/contracts/programs/test-ccip-receiver/src/lib.rs @@ -3,6 +3,7 @@ use anchor_spl::{token_interface::Mint, token_interface::TokenAccount}; use example_ccip_receiver::{ Any2SVMMessage, BaseState, CcipReceiverError, EXTERNAL_EXECUTION_CONFIG_SEED, }; + use solana_program::pubkey; declare_id!("CtEVnHsQzhTNWav8skikiV2oF6Xx7r7uGGa8eCDQtTjH"); @@ -12,7 +13,8 @@ declare_id!("CtEVnHsQzhTNWav8skikiV2oF6Xx7r7uGGa8eCDQtTjH"); pub mod test_ccip_receiver { const CCIP_ROUTER: Pubkey = pubkey!("offRPDpDxT5MGFNmMh99QKTZfPWTkqYUrStEriAS1H5"); - use solana_program::{instruction::Instruction, program::invoke_signed}; + use solana_program::instruction::Instruction; + use solana_program::program::invoke_signed; use super::*; diff --git a/chains/solana/contracts/programs/token-pool/src/context.rs b/chains/solana/contracts/programs/token-pool/src/context.rs index f2554c4f1..85e830d32 100644 --- a/chains/solana/contracts/programs/token-pool/src/context.rs +++ b/chains/solana/contracts/programs/token-pool/src/context.rs @@ -13,8 +13,10 @@ 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 EXTERNAL_TOKENPOOL_SIGNER: &[u8] = b"external_token_pools_signer"; 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]; +pub const ALLOWED_OFFRAMP: &[u8] = b"allowed_offramp"; #[derive(Accounts)] pub struct InitializeTokenPool<'info> { @@ -71,9 +73,31 @@ pub struct AcceptOwnership<'info> { #[instruction(release_or_mint: ReleaseOrMintInV1)] pub struct TokenOfframp<'info> { // CCIP accounts ------------------------ - #[account(address = config.ramp_authority @ CcipTokenPoolError::InvalidPoolCaller)] + #[account( + seeds = [EXTERNAL_TOKENPOOL_SIGNER], + bump, + seeds::program = offramp_program.key(), + )] pub authority: Signer<'info>, + /// CHECK offramp program: exists only to derive the allowed offramp PDA + /// and the authority PDA. + pub offramp_program: UncheckedAccount<'info>, + + /// CHECK PDA of the router program verifying the signer is an allowed offramp. + /// If PDA does not exist, the router doesn't allow this offramp + #[account( + owner = config.ccip_router @ CcipTokenPoolError::InvalidPoolCaller, // this guarantees that it was initialized + seeds = [ + ALLOWED_OFFRAMP, + release_or_mint.remote_chain_selector.to_le_bytes().as_ref(), + offramp_program.key().as_ref() + ], + bump, + seeds::program = config.ccip_router, + )] + pub allowed_offramp: UncheckedAccount<'info>, + // Token pool accounts ------------------ // consistent set + token pool program #[account( @@ -341,7 +365,13 @@ pub struct RemoteChainRemoved { } #[event] -pub struct RouterUpdated { +pub struct RampAuthorityUpdated { pub old_authority: Pubkey, pub new_authority: Pubkey, } + +#[event] +pub struct RouterUpdated { + pub old_router: Pubkey, + pub new_router: Pubkey, +} diff --git a/chains/solana/contracts/programs/token-pool/src/lib.rs b/chains/solana/contracts/programs/token-pool/src/lib.rs index 29ac4afc2..94c476902 100644 --- a/chains/solana/contracts/programs/token-pool/src/lib.rs +++ b/chains/solana/contracts/programs/token-pool/src/lib.rs @@ -29,6 +29,7 @@ pub mod token_pool { ctx: Context, pool_type: PoolType, ramp_authority: Pubkey, + ccip_router: Pubkey, ) -> Result<()> { let token_info = ctx.accounts.mint.to_account_info(); @@ -45,6 +46,7 @@ pub mod token_pool { ); config.owner = ctx.accounts.authority.key(); config.ramp_authority = ramp_authority; + config.ccip_router = ccip_router; Ok(()) } @@ -67,8 +69,7 @@ pub mod token_pool { Ok(()) } - // set_ramp_authority changes the expected signer for mint/release + burn/lock method calls - // this is used to update the router address + // set_ramp_authority changes the expected signer for burn/lock method calls pub fn set_ramp_authority(ctx: Context, new_authority: Pubkey) -> Result<()> { require!( new_authority != Pubkey::zeroed(), @@ -77,13 +78,30 @@ pub mod token_pool { let old_authority = ctx.accounts.config.ramp_authority; ctx.accounts.config.ramp_authority = new_authority; - emit!(RouterUpdated { + emit!(RampAuthorityUpdated { old_authority, new_authority }); Ok(()) } + // set_router changes the router program ID. This is used to derive the list + // of valid mint/release callers (offramps). + pub fn set_router(ctx: Context, new_router: Pubkey) -> Result<()> { + require!( + new_router != Pubkey::zeroed(), + CcipTokenPoolError::InvalidInputs + ); + + let old_router = ctx.accounts.config.ccip_router; + ctx.accounts.config.ccip_router = new_router; + emit!(RouterUpdated { + old_router, + new_router + }); + Ok(()) + } + // initialize remote config (with no remote pools as it must be zero sized) pub fn init_chain_remote_config( ctx: Context, @@ -507,7 +525,9 @@ pub struct Config { // ownership pub owner: Pubkey, pub proposed_owner: Pubkey, - ramp_authority: Pubkey, // signer for CCIP calls + + ramp_authority: Pubkey, + ccip_router: Pubkey, } #[account] diff --git a/chains/solana/contracts/target/idl/ccip_offramp.json b/chains/solana/contracts/target/idl/ccip_offramp.json index edfdcabf1..c685b944f 100644 --- a/chains/solana/contracts/target/idl/ccip_offramp.json +++ b/chains/solana/contracts/target/idl/ccip_offramp.json @@ -585,6 +585,20 @@ "isMut": true, "isSigner": false }, + { + "name": "offramp", + "isMut": false, + "isSigner": false + }, + { + "name": "allowedOfframp", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK PDA of the router program verifying the signer is an allowed offramp.", + "If PDA does not exist, the router doesn't allow this offramp" + ] + }, { "name": "externalExecutionConfig", "isMut": false, @@ -671,6 +685,20 @@ "isMut": true, "isSigner": false }, + { + "name": "offramp", + "isMut": false, + "isSigner": false + }, + { + "name": "allowedOfframp", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK PDA of the router program verifying the signer is an allowed offramp.", + "If PDA does not exist, the router doesn't allow this offramp" + ] + }, { "name": "externalExecutionConfig", "isMut": false, diff --git a/chains/solana/contracts/target/idl/test_ccip_invalid_receiver.json b/chains/solana/contracts/target/idl/test_ccip_invalid_receiver.json index 9cb8b5212..f395728a3 100644 --- a/chains/solana/contracts/target/idl/test_ccip_invalid_receiver.json +++ b/chains/solana/contracts/target/idl/test_ccip_invalid_receiver.json @@ -29,6 +29,99 @@ } } ] + }, + { + "name": "poolProxyReleaseOrMint", + "accounts": [ + { + "name": "testPool", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK" + ] + }, + { + "name": "cpiSigner", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK" + ] + }, + { + "name": "offrampProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "allowedOfframp", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK" + ] + }, + { + "name": "config", + "isMut": true, + "isSigner": false, + "docs": [ + "CHECK" + ] + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK" + ] + }, + { + "name": "mint", + "isMut": true, + "isSigner": false + }, + { + "name": "poolSigner", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK" + ] + }, + { + "name": "poolTokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "chainConfig", + "isMut": true, + "isSigner": false, + "docs": [ + "CHECK" + ] + }, + { + "name": "receiverTokenAccount", + "isMut": true, + "isSigner": false, + "docs": [ + "CHECK" + ] + } + ], + "args": [ + { + "name": "releaseOrMint", + "type": { + "defined": "ReleaseOrMintInV1" + } + } + ], + "returns": "bytes" } ], "accounts": [ @@ -44,5 +137,59 @@ ] } } + ], + "types": [ + { + "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/token_pool.json b/chains/solana/contracts/target/idl/token_pool.json index 8fa3d3744..f6aef3543 100644 --- a/chains/solana/contracts/target/idl/token_pool.json +++ b/chains/solana/contracts/target/idl/token_pool.json @@ -41,6 +41,10 @@ { "name": "rampAuthority", "type": "publicKey" + }, + { + "name": "ccipRouter", + "type": "publicKey" } ] }, @@ -102,6 +106,27 @@ } ] }, + { + "name": "setRouter", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "authority", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "newRouter", + "type": "publicKey" + } + ] + }, { "name": "initChainRemoteConfig", "accounts": [ @@ -317,6 +342,24 @@ "isMut": false, "isSigner": true }, + { + "name": "offrampProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK offramp program: exists only to derive the allowed offramp PDA", + "and the authority PDA." + ] + }, + { + "name": "allowedOfframp", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK PDA of the router program verifying the signer is an allowed offramp.", + "If PDA does not exist, the router doesn't allow this offramp" + ] + }, { "name": "config", "isMut": true, @@ -464,6 +507,10 @@ { "name": "rampAuthority", "type": "publicKey" + }, + { + "name": "ccipRouter", + "type": "publicKey" } ] } @@ -897,7 +944,7 @@ ] }, { - "name": "RouterUpdated", + "name": "RampAuthorityUpdated", "fields": [ { "name": "oldAuthority", @@ -911,6 +958,21 @@ } ] }, + { + "name": "RouterUpdated", + "fields": [ + { + "name": "oldRouter", + "type": "publicKey", + "index": false + }, + { + "name": "newRouter", + "type": "publicKey", + "index": false + } + ] + }, { "name": "TokensConsumed", "fields": [ diff --git a/chains/solana/contracts/tests/ccip/ccip_router_test.go b/chains/solana/contracts/tests/ccip/ccip_router_test.go index cf31f8d35..23f61da69 100644 --- a/chains/solana/contracts/tests/ccip/ccip_router_test.go +++ b/chains/solana/contracts/tests/ccip/ccip_router_test.go @@ -227,6 +227,7 @@ func TestCCIPRouter(t *testing.T) { ixInit0, err := token_pool.NewInitializeInstruction( token_pool.BurnAndMint_PoolType, config.ExternalTokenPoolsSignerPDA, + config.CcipRouterProgram, token0.PoolConfig, token0.Mint.PublicKey(), token0.PoolSigner, @@ -238,6 +239,7 @@ func TestCCIPRouter(t *testing.T) { ixInit1, err := token_pool.NewInitializeInstruction( token_pool.BurnAndMint_PoolType, config.ExternalTokenPoolsSignerPDA, + config.CcipRouterProgram, token1.PoolConfig, token1.Mint.PublicKey(), token1.PoolSigner, @@ -569,6 +571,15 @@ func TestCCIPRouter(t *testing.T) { require.Equal(t, config.CcipRouterProgram, referenceAddresses.Router) }) + t.Run("Offramp permissions", func(t *testing.T) { + instruction, err := ccip_router.NewAddOfframpInstruction( + config.EvmChainSelector, config.CcipOfframpProgram, config.AllowedOfframpEvmPDA, config.RouterConfigPDA, legacyAdmin.PublicKey(), solana.SystemProgramID, + ).ValidateAndBuild() + require.NoError(t, err) + + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, legacyAdmin, config.DefaultCommitment) + }) + t.Run("When admin updates the solana chain selector it's updated", func(t *testing.T) { t.Run("CCIP Router", func(t *testing.T) { instruction, err := ccip_router.NewUpdateSvmChainSelectorInstruction( @@ -762,11 +773,9 @@ func TestCCIPRouter(t *testing.T) { 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 SVM -> SVM messages) - // Regardless of whether we allow SVM -> SVM in mainnet, it's easy to use for tests here // the router is the SVM onramp - var paddedCcipRouterProgram [64]byte - copy(paddedCcipRouterProgram[:], config.CcipRouterProgram[:]) + paddedCcipRouterProgram := common.ToPadded64Bytes(config.CcipRouterProgram.Bytes()) onRampConfig := [2][64]byte{paddedCcipRouterProgram, emptyAddress} t.Run("CCIP Router", func(t *testing.T) { @@ -2420,24 +2429,29 @@ func TestCCIPRouter(t *testing.T) { ///////////////////////////// t.Run("Token Pool Configuration", func(t *testing.T) { t.Run("RemoteConfig", func(t *testing.T) { - ix0, err := token_pool.NewInitChainRemoteConfigInstruction(config.EvmChainSelector, token0.Mint.PublicKey(), token_pool.RemoteConfig{ - // PoolAddresses: []token_pool.RemoteAddress{{Address: []byte{1, 2, 3}}}, - TokenAddress: token_pool.RemoteAddress{Address: []byte{1, 2, 3}}, - }, token0.PoolConfig, token0.Chain[config.EvmChainSelector], token0PoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild() - require.NoError(t, err) - ix1, err := token_pool.NewInitChainRemoteConfigInstruction(config.EvmChainSelector, token1.Mint.PublicKey(), token_pool.RemoteConfig{ - PoolAddresses: []token_pool.RemoteAddress{{Address: []byte{4, 5, 6}}}, - TokenAddress: token_pool.RemoteAddress{Address: []byte{4, 5, 6}}, - }, token1.PoolConfig, token1.Chain[config.EvmChainSelector], token1PoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild() - require.NoError(t, err) - testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix0, ix1}, token0PoolAdmin, config.DefaultCommitment, common.AddSigners(token1PoolAdmin)) + for _, selector := range []uint64{config.EvmChainSelector, config.SvmChainSelector} { + ix0, err := token_pool.NewInitChainRemoteConfigInstruction(selector, token0.Mint.PublicKey(), token_pool.RemoteConfig{ + // PoolAddresses: []token_pool.RemoteAddress{{Address: []byte{1, 2, 3}}}, + TokenAddress: token_pool.RemoteAddress{Address: []byte{1, 2, 3}}, + }, token0.PoolConfig, token0.Chain[selector], token0PoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild() + require.NoError(t, err) + ix1, err := token_pool.NewInitChainRemoteConfigInstruction(selector, token1.Mint.PublicKey(), token_pool.RemoteConfig{ + PoolAddresses: []token_pool.RemoteAddress{{Address: []byte{4, 5, 6}}}, + TokenAddress: token_pool.RemoteAddress{Address: []byte{4, 5, 6}}, + }, token1.PoolConfig, token1.Chain[selector], token1PoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild() + require.NoError(t, err) + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix0, ix1}, token0PoolAdmin, config.DefaultCommitment, common.AddSigners(token1PoolAdmin)) + } }) t.Run("AppendRemotePools", func(t *testing.T) { - ix, err := token_pool.NewAppendRemotePoolAddressesInstruction(config.EvmChainSelector, token0.Mint.PublicKey(), []token_pool.RemoteAddress{{Address: []byte{1, 2, 3}}}, + ixEvm, err := token_pool.NewAppendRemotePoolAddressesInstruction(config.EvmChainSelector, token0.Mint.PublicKey(), []token_pool.RemoteAddress{{Address: []byte{1, 2, 3}}}, token0.PoolConfig, token0.Chain[config.EvmChainSelector], token0PoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild() require.NoError(t, err) - testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, token0PoolAdmin, config.DefaultCommitment) + ixSvm, err := token_pool.NewAppendRemotePoolAddressesInstruction(config.SvmChainSelector, token0.Mint.PublicKey(), []token_pool.RemoteAddress{{Address: []byte{1, 2, 3}}}, + token0.PoolConfig, token0.Chain[config.SvmChainSelector], token0PoolAdmin.PublicKey(), solana.SystemProgramID).ValidateAndBuild() + require.NoError(t, err) + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ixEvm, ixSvm}, token0PoolAdmin, config.DefaultCommitment) }) t.Run("RateLimit", func(t *testing.T) { @@ -5226,6 +5240,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5322,6 +5338,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5416,6 +5434,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, unsupportedSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5498,6 +5518,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5538,6 +5560,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5585,6 +5609,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5672,6 +5698,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5706,6 +5734,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5807,6 +5837,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5892,6 +5924,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -5914,31 +5948,6 @@ func TestCCIPRouter(t *testing.T) { }) t.Run("token happy path", func(t *testing.T) { - // TODO this is a throw-away setup stage until we implement the offramp authorization logic in the pool - t.Run("Setup: set offramp as token pool ramp authority", func(t *testing.T) { - poolCases := []struct { - Name string - PoolConfig solana.PublicKey - Admin solana.PrivateKey - }{ - {"Token0", token0.PoolConfig, token0PoolAdmin}, - {"Token1", token1.PoolConfig, token1PoolAdmin}, - } - - for _, p := range poolCases { - t.Run(p.Name, func(t *testing.T) { - ix, err := token_pool.NewSetRampAuthorityInstruction( - config.OfframpTokenPoolsSignerPDA, - p.PoolConfig, - p.Admin.PublicKey(), - ).ValidateAndBuild() - require.NoError(t, err) - - testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, p.Admin, config.DefaultCommitment) - }) - } - }) - t.Run("single token", func(t *testing.T) { _, initSupply, err := tokens.TokenSupply(ctx, solanaGoClient, token0.Mint.PublicKey(), config.DefaultCommitment) require.NoError(t, err) @@ -6013,6 +6022,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -6132,6 +6143,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -6163,6 +6176,174 @@ func TestCCIPRouter(t *testing.T) { }) }) + t.Run("tokens other cases", func(t *testing.T) { + type setupArgs struct { + sourceChainSelector uint64 + offrampSourceChainPDA solana.PublicKey + onramp []byte + sequenceNumber uint64 // use 0 if not overriding the default (which only tracks EVM seq num) + } + type setupResult struct { + initSupply int + initBal int + message ccip_offramp.Any2SVMRampMessage + root [32]byte + rootPDA solana.PublicKey + transmitter solana.PrivateKey + } + testSetup := func(t *testing.T, args setupArgs) setupResult { + _, initSupply, err := tokens.TokenSupply(ctx, solanaGoClient, token0.Mint.PublicKey(), config.DefaultCommitment) + require.NoError(t, err) + _, initBal, err := tokens.TokenBalance(ctx, solanaGoClient, token0.User[config.ReceiverExternalExecutionConfigPDA], config.DefaultCommitment) + require.NoError(t, err) + + transmitter := getTransmitter() + + msgAccounts := []solana.PublicKey{config.CcipLogicReceiver, config.ReceiverExternalExecutionConfigPDA, config.ReceiverTargetAccountPDA, solana.SystemProgramID} + message, _ := testutils.CreateNextMessage(ctx, solanaGoClient, t, msgAccounts) + message.Header.SourceChainSelector = args.sourceChainSelector + message.OnRampAddress = args.onramp + if args.sequenceNumber != 0 { + message.Header.SequenceNumber = args.sequenceNumber + } + require.NoError(t, err) + message.TokenReceiver = config.ReceiverExternalExecutionConfigPDA + message.TokenAmounts = []ccip_offramp.Any2SVMTokenTransfer{{ + SourcePoolAddress: []byte{1, 2, 3}, + DestTokenAddress: token0.Mint.PublicKey(), + Amount: ccip_offramp.CrossChainAmount{LeBytes: tokens.ToLittleEndianU256(1)}, + }} + rootBytes, err := ccip.HashAnyToSVMMessage(message, args.onramp, msgAccounts) + require.NoError(t, err) + + fmt.Printf("message: %+v\n", message) // TODO remove + + root := [32]byte(rootBytes) + sequenceNumber := message.Header.SequenceNumber + + commitReport := ccip_offramp.CommitInput{ + MerkleRoot: ccip_offramp.MerkleRoot{ + SourceChainSelector: args.sourceChainSelector, + OnRampAddress: args.onramp, + MinSeqNr: sequenceNumber, + MaxSeqNr: sequenceNumber, + MerkleRoot: root, + }, + } + sigs, err := ccip.SignCommitReport(reportContext, commitReport, signers) + require.NoError(t, err) + rootPDA, err := state.FindOfframpCommitReportPDA(args.sourceChainSelector, root, config.CcipOfframpProgram) + require.NoError(t, err) + + instruction, err := ccip_offramp.NewCommitInstruction( + reportContext, + testutils.MustMarshalBorsh(t, commitReport), + sigs.Rs, + sigs.Ss, + sigs.RawVs, + config.OfframpConfigPDA, + config.OfframpReferenceAddressesPDA, + args.offrampSourceChainPDA, + rootPDA, + transmitter.PublicKey(), + solana.SystemProgramID, + solana.SysVarInstructionsPubkey, + config.OfframpBillingSignerPDA, + config.FeeQuoterProgram, + config.FqConfigPDA, + ).ValidateAndBuild() + require.NoError(t, err) + tx := testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, common.AddComputeUnitLimit(300_000)) + event := ccip.EventCommitReportAccepted{} + require.NoError(t, common.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents)) + + return setupResult{ + initSupply, + initBal, + message, + root, + rootPDA, + transmitter, + } + } + + t.Run("Router authorization of offramp for lanes", func(t *testing.T) { + // Check SVM is an accepted source chain + svmOnramp := common.ToPadded64Bytes(config.CcipRouterProgram[:]) + var sourceChain ccip_offramp.SourceChain + require.NoError(t, common.GetAccountDataBorshInto(ctx, solanaGoClient, config.OfframpSvmSourceChainPDA, config.DefaultCommitment, &sourceChain)) + require.Contains(t, sourceChain.Config.OnRamp, svmOnramp) + fmt.Printf("sourceChain: %+v\n", sourceChain) // TODO remove + + // Check offramp program is not registered in router as valid offramp for SVM<>SVM lane + testutils.AssertClosedAccount(ctx, t, solanaGoClient, config.AllowedOfframpSvmPDA, config.DefaultCommitment) + + setup := testSetup(t, setupArgs{ + sourceChainSelector: config.SvmChainSelector, + offrampSourceChainPDA: config.OfframpSvmSourceChainPDA, + onramp: config.CcipRouterProgram.Bytes(), + sequenceNumber: 1, // most utils assume EVM, but this is the first message from SVM + }) + + executionReport := ccip_offramp.ExecutionReportSingleChain{ + SourceChainSelector: config.SvmChainSelector, + Message: setup.message, + OffchainTokenData: [][]byte{{}}, + Root: setup.root, + Proofs: [][32]uint8{}, + } + raw := ccip_offramp.NewExecuteInstruction( + testutils.MustMarshalBorsh(t, executionReport), + reportContext, + []byte{4}, + config.OfframpConfigPDA, + config.OfframpReferenceAddressesPDA, + config.OfframpSvmSourceChainPDA, + setup.rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpSvmPDA, + config.OfframpExternalExecutionConfigPDA, + setup.transmitter.PublicKey(), + solana.SystemProgramID, + solana.SysVarInstructionsPubkey, + config.OfframpTokenPoolsSignerPDA, + ) + raw.AccountMetaSlice = append( + raw.AccountMetaSlice, + solana.NewAccountMeta(config.CcipLogicReceiver, false, false), + solana.NewAccountMeta(config.ReceiverExternalExecutionConfigPDA, true, false), + solana.NewAccountMeta(config.ReceiverTargetAccountPDA, true, false), + solana.NewAccountMeta(solana.SystemProgramID, false, false), + ) + + tokenMetas, addressTables, err := tokens.ParseTokenLookupTable(ctx, solanaGoClient, token0, token0.User[config.ReceiverExternalExecutionConfigPDA]) + require.NoError(t, err) + tokenMetas[1] = solana.Meta(token0.Billing[config.SvmChainSelector]) // overwrite field that our TokenPool utils assume will be for EVM + tokenMetas[2] = solana.Meta(token0.Chain[config.SvmChainSelector]).WRITE() // overwrite field that our TokenPool utils assume will be for EVM + raw.AccountMetaSlice = append(raw.AccountMetaSlice, tokenMetas...) + executeIx, err := raw.ValidateAndBuild() + require.NoError(t, err) + + // It fails here as the offramp isn't registered in the router for that lane + testutils.SendAndFailWithLookupTables(ctx, t, solanaGoClient, []solana.Instruction{executeIx}, setup.transmitter, config.DefaultCommitment, addressTables, []string{ccip_offramp.InvalidInputs_CcipOfframpError.String()}) + + // Allow that offramp for SVM + allowIx, err := ccip_router.NewAddOfframpInstruction( + config.SvmChainSelector, + config.CcipOfframpProgram, + config.AllowedOfframpSvmPDA, + config.RouterConfigPDA, + ccipAdmin.PublicKey(), + solana.SystemProgramID, + ).ValidateAndBuild() + require.NoError(t, err) + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{allowIx}, ccipAdmin, config.DefaultCommitment) + + // Now the execute should succeed + testutils.SendAndConfirmWithLookupTables(ctx, t, solanaGoClient, []solana.Instruction{executeIx}, setup.transmitter, config.DefaultCommitment, addressTables) + }) + }) + t.Run("OffRamp Manual Execution: when executing a non-committed report, it fails", func(t *testing.T) { message, root := testutils.CreateNextMessage(ctx, solanaGoClient, t, []solana.PublicKey{}) rootPDA, err := state.FindOfframpCommitReportPDA(config.EvmChainSelector, root, config.CcipRouterProgram) @@ -6182,6 +6363,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, user.PublicKey(), solana.SystemProgramID, @@ -6268,6 +6451,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, user.PublicKey(), solana.SystemProgramID, @@ -6305,6 +6490,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -6350,6 +6537,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, user.PublicKey(), solana.SystemProgramID, @@ -6402,6 +6591,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -6441,115 +6632,6 @@ func TestCCIPRouter(t *testing.T) { }) }) - // TODO: With the split of the offramp, it should be supported to run Offramp.execute -> Receiver.ccipReceive -> Router.ccipSend - // 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 := state.FindNoncePDA(config.EvmChainSelector, config.ReceiverExternalExecutionConfigPDA, config.CcipRouterProgram) - // require.NoError(t, err) - - // msgAccounts := []solana.PublicKey{ - // config.CcipLogicReceiver, - // config.ReceiverExternalExecutionConfigPDA, - // config.ReceiverTargetAccountPDA, - // solana.SystemProgramID, - // config.CcipRouterProgram, - // config.RouterConfigPDA, - // config.ReceiverExternalExecutionConfigPDA, - // config.EvmSourceChainStatePDA, - // receiverContractEvmPDA, - // solana.SystemProgramID} - - // message, _ := testutils.CreateNextMessage(ctx, solanaGoClient, t, msgAccounts) - - // // To make the message go through the validations we need to specify the correct bitmap in the order - // // of the remaining accounts (writable accounts at positions 1, 5, 6 and 7, i.e. ReceiverTargetAccountPDA, - // // ReceiverExternalExecutionConfigPDA, EVMSourceChainStatePDA and receiverContractEVMPDA) - // message.ExtraArgs.IsWritableBitmap = ccip.GenerateBitMapForIndexes([]int{0, 1, 5, 6, 7}) - // hash, err := ccip.HashAnyToSVMMessage(message, config.OnRampAddress, msgAccounts) - // require.NoError(t, err) - // root := [32]byte(hash) - - // sourceChainSelector := config.EvmChainSelector - // commitReport := ccip_offramp.CommitInput{ - // MerkleRoot: ccip_offramp.MerkleRoot{ - // SourceChainSelector: sourceChainSelector, - // OnRampAddress: config.OnRampAddress, - // MinSeqNr: message.Header.SequenceNumber, - // MaxSeqNr: message.Header.SequenceNumber, - // MerkleRoot: root, - // }, - // } - // sigs, err := ccip.SignCommitReport(reportContext, commitReport, signers) - // require.NoError(t, err) - // rootPDA, _ := state.FindOfframpCommitReportPDA(config.EvmChainSelector, root, config.CcipOfframpProgram) - - // instruction, err := ccip_offramp.NewCommitInstruction( - // reportContext, - // testutils.MustMarshalBorsh(t, commitReport), - // sigs.Rs, - // sigs.Ss, - // sigs.RawVs, - // config.OfframpConfigPDA, - // config.OfframpReferenceAddressesPDA, - // config.OfframpEvmSourceChainPDA, - // rootPDA, - // transmitter.PublicKey(), - // solana.SystemProgramID, - // solana.SysVarInstructionsPubkey, - // config.BillingSignerPDA, - // config.FeeQuoterProgram, - // config.FqConfigPDA, - // ).ValidateAndBuild() - // require.NoError(t, err) - // tx := testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{instruction}, transmitter, config.DefaultCommitment, common.AddComputeUnitLimit(300_000)) - // event := ccip.EventCommitReportAccepted{} - // require.NoError(t, common.ParseEvent(tx.Meta.LogMessages, "CommitReportAccepted", &event, config.PrintEvents)) - - // executionReport := ccip_offramp.ExecutionReportSingleChain{ - // SourceChainSelector: sourceChainSelector, - // Message: message, - // Root: root, - // Proofs: [][32]uint8{}, // single leaf merkle tree - // } - // raw := ccip_offramp.NewExecuteInstruction( - // testutils.MustMarshalBorsh(t, executionReport), - // reportContext, - // []byte{}, - // config.OfframpConfigPDA, - // config.OfframpReferenceAddressesPDA, - // config.OfframpEvmSourceChainPDA, - // rootPDA, - // config.OfframpExternalExecutionConfigPDA, - // transmitter.PublicKey(), - // solana.SystemProgramID, - // solana.SysVarInstructionsPubkey, - // config.OfframpTokenPoolsSignerPDA, - // ) - // raw.AccountMetaSlice = append( - // raw.AccountMetaSlice, - // solana.NewAccountMeta(config.CcipLogicReceiver, 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.EvmSourceChainStatePDA, true, false), - // solana.NewAccountMeta(receiverContractEvmPDA, true, false), - // solana.NewAccountMeta(solana.SystemProgramID, false, false), - // ) - // instruction, err = raw.ValidateAndBuild() - // require.NoError(t, err) - - // testutils.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() @@ -6626,6 +6708,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, transmitter.PublicKey(), solana.SystemProgramID, @@ -6657,6 +6741,8 @@ func TestCCIPRouter(t *testing.T) { config.OfframpReferenceAddressesPDA, config.OfframpEvmSourceChainPDA, rootPDA, + config.CcipOfframpProgram, + config.AllowedOfframpEvmPDA, config.OfframpExternalExecutionConfigPDA, legacyAdmin.PublicKey(), solana.SystemProgramID, diff --git a/chains/solana/contracts/tests/ccip/tokenpool_test.go b/chains/solana/contracts/tests/ccip/tokenpool_test.go index 5ead42d33..6fcfae3ed 100644 --- a/chains/solana/contracts/tests/ccip/tokenpool_test.go +++ b/chains/solana/contracts/tests/ccip/tokenpool_test.go @@ -6,29 +6,50 @@ import ( "testing" "time" + 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/testutils" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/test_ccip_invalid_receiver" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/test_ccip_receiver" "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/token_pool" "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/state" "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/tokens" ) func TestTokenPool(t *testing.T) { - t.Parallel() - token_pool.SetProgramID(config.CcipTokenPoolProgram) + ccip_router.SetProgramID(config.CcipRouterProgram) + + // acting as program wrapped by a token pool + test_ccip_receiver.SetProgramID(config.CcipLogicReceiver) + + // acting as "dumb" offramp that just proxies the pool, + // required for authnz in the pool but we don't want to test offramp internals here + test_ccip_invalid_receiver.SetProgramID(config.CcipInvalidReceiverProgram) + + dumbOfframp := config.CcipInvalidReceiverProgram + dumbOfframpPoolSigner, _, _ := solana.FindProgramAddress([][]byte{[]byte("external_token_pools_signer")}, dumbOfframp) admin, err := solana.NewRandomPrivateKey() require.NoError(t, err) anotherAdmin, err := solana.NewRandomPrivateKey() require.NoError(t, err) + + user, err := solana.NewRandomPrivateKey() + require.NoError(t, err) ctx := tests.Context(t) + allowedOfframpPDA, err := state.FindAllowedOfframpPDA(config.EvmChainSelector, dumbOfframp, config.CcipRouterProgram) + require.NoError(t, err) + solanaGoClient := testutils.DeployAllPrograms(t, testutils.PathToAnchorConfig, admin) getBalance := func(account solana.PublicKey) string { balanceRes, err := solanaGoClient.GetTokenAccountBalance(ctx, account, config.DefaultCommitment) @@ -40,7 +61,54 @@ func TestTokenPool(t *testing.T) { remoteToken := token_pool.RemoteAddress{Address: []byte{4, 5, 6}} t.Run("setup:funding", func(t *testing.T) { - testutils.FundAccounts(ctx, []solana.PrivateKey{admin, anotherAdmin}, solanaGoClient, t) + testutils.FundAccounts(ctx, []solana.PrivateKey{admin, anotherAdmin, user}, solanaGoClient, t) + }) + + t.Run("setup:router", func(t *testing.T) { + // 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())) + + feeAggregator := solana.MustPrivateKeyFromBase58("4mKsN4bLEPTQerRRCMALWMFKnkP1xiaC3rYCzcmEmgCu5yrf2eDCPH3jHbsaAg1giKKFwrxk9oUzVxHLYokS1QhN") + linkTokenMint := solana.MustPrivateKeyFromBase58("2e6af6HmHgxmrv5dLVSqAzerPrLsjEJyyRATvjiBLPpahFv3wdE2NQqaHWjtb8WdVLrvoLchNLoHBr4KVC1GAxBC") + + ix, err := ccip_router.NewInitializeInstruction( + 1, + feeAggregator.PublicKey(), + config.FeeQuoterProgram, + linkTokenMint.PublicKey(), + config.RouterConfigPDA, + admin.PublicKey(), + solana.SystemProgramID, + config.CcipRouterProgram, + programData.Address, + config.ExternalTokenPoolsSignerPDA, + ).ValidateAndBuild() + require.NoError(t, err) + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment) + }) + + t.Run("setup:allowed offramp", func(t *testing.T) { + ix, err := ccip_router.NewAddOfframpInstruction( + config.EvmChainSelector, + dumbOfframp, + allowedOfframpPDA, + config.RouterConfigPDA, + admin.PublicKey(), + solana.SystemProgramID, + ).ValidateAndBuild() + require.NoError(t, err) + + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, admin, config.DefaultCommitment) }) // test functionality with token & token-2022 standards @@ -101,7 +169,14 @@ func TestTokenPool(t *testing.T) { 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() + poolInitI, err := token_pool.NewInitializeInstruction(poolType, + admin.PublicKey(), + config.CcipRouterProgram, + poolConfig, + mint, + poolSigner, + admin.PublicKey(), + solana.SystemProgramID).ValidateAndBuild() require.NoError(t, err) // make pool mint_authority for token (required for burn/mint) @@ -219,16 +294,30 @@ func TestTokenPool(t *testing.T) { 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: tokens.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() + rmI, err := test_ccip_invalid_receiver.NewPoolProxyReleaseOrMintInstruction( + test_ccip_invalid_receiver.ReleaseOrMintInV1{ + LocalToken: mint, + SourcePoolAddress: remotePool.Address, + Amount: tokens.ToLittleEndianU256(amount * 1e9), // scale to proper decimals + Receiver: admin.PublicKey(), + RemoteChainSelector: config.EvmChainSelector, + }, + config.CcipTokenPoolProgram, + dumbOfframpPoolSigner, + dumbOfframp, + allowedOfframpPDA, + poolConfig, + v.tokenProgram, + mint, + poolSigner, + poolTokenAccount, + p.Chain[config.EvmChainSelector], + p.User[admin.PublicKey()], + ).ValidateAndBuild() + require.NoError(t, err) - res := testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{rmI}, admin, config.DefaultCommitment) + res := testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{rmI}, user, config.DefaultCommitment) require.NotNil(t, res) event := tokens.EventMintRelease{} @@ -305,43 +394,91 @@ func TestTokenPool(t *testing.T) { t.Parallel() // exceed capacity of bucket - rmI, err := token_pool.NewReleaseOrMintTokensInstruction(token_pool.ReleaseOrMintInV1{ - LocalToken: mint, - SourcePoolAddress: remotePool, - Amount: tokens.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() + rmI, err := test_ccip_invalid_receiver.NewPoolProxyReleaseOrMintInstruction( + test_ccip_invalid_receiver.ReleaseOrMintInV1{ + LocalToken: mint, + SourcePoolAddress: remotePool.Address, + Amount: tokens.ToLittleEndianU256(amount * amount * 1e9), + Receiver: admin.PublicKey(), + RemoteChainSelector: config.EvmChainSelector, + }, + config.CcipTokenPoolProgram, + dumbOfframpPoolSigner, + dumbOfframp, + allowedOfframpPDA, + poolConfig, + v.tokenProgram, + mint, + poolSigner, + poolTokenAccount, + p.Chain[config.EvmChainSelector], + p.User[admin.PublicKey()], + ).ValidateAndBuild() require.NoError(t, err) - testutils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{rmI}, admin, config.DefaultCommitment, []string{"max capacity exceeded"}) + testutils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{rmI}, user, 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 := tokens.TokenTransferChecked(amount, decimals, v.tokenProgram, p.User[admin.PublicKey()], mint, poolTokenAccount, admin.PublicKey(), solana.PublicKeySlice{}) // ensure pool is funded + transferI, err := tokens.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: tokens.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() + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{transferI}, admin, config.DefaultCommitment) + rmI, err = test_ccip_invalid_receiver.NewPoolProxyReleaseOrMintInstruction( + test_ccip_invalid_receiver.ReleaseOrMintInV1{ + LocalToken: mint, + SourcePoolAddress: remotePool.Address, + Amount: tokens.ToLittleEndianU256(amount * 1e9), + Receiver: admin.PublicKey(), + RemoteChainSelector: config.EvmChainSelector, + }, + config.CcipTokenPoolProgram, + dumbOfframpPoolSigner, + dumbOfframp, + allowedOfframpPDA, + poolConfig, + v.tokenProgram, + mint, + poolSigner, + poolTokenAccount, + p.Chain[config.EvmChainSelector], + p.User[admin.PublicKey()], + ).ValidateAndBuild() require.NoError(t, err) - testutils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{transferI, rmI, rmI}, admin, config.DefaultCommitment, []string{"rate limit reached"}) + testutils.SendAndFailWith(ctx, t, solanaGoClient, []solana.Instruction{rmI, rmI}, user, 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: tokens.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() + rmI, err = test_ccip_invalid_receiver.NewPoolProxyReleaseOrMintInstruction( + test_ccip_invalid_receiver.ReleaseOrMintInV1{ + LocalToken: mint, + SourcePoolAddress: remotePool.Address, + Amount: tokens.ToLittleEndianU256(1e9), + Receiver: admin.PublicKey(), + RemoteChainSelector: config.EvmChainSelector, + }, + config.CcipTokenPoolProgram, + dumbOfframpPoolSigner, + dumbOfframp, + allowedOfframpPDA, + poolConfig, + v.tokenProgram, + mint, + poolSigner, + poolTokenAccount, + p.Chain[config.EvmChainSelector], + p.User[admin.PublicKey()], + ).ValidateAndBuild() require.NoError(t, err) - testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{transferI, rmI}, admin, config.DefaultCommitment) + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{rmI}, user, config.DefaultCommitment) }) }) @@ -360,7 +497,7 @@ func TestTokenPool(t *testing.T) { require.Equal(t, config.EvmChainSelector, eventDelete.ChainSelector) eventRouter := tokens.EventRouterUpdated{} - require.NoError(t, common.ParseEvent(res.Meta.LogMessages, "RouterUpdated", &eventRouter, config.PrintEvents)) + require.NoError(t, common.ParseEvent(res.Meta.LogMessages, "RampAuthorityUpdated", &eventRouter, config.PrintEvents)) require.Equal(t, config.ExternalTokenPoolsSignerPDA, eventRouter.NewAuthority) require.Equal(t, admin.PublicKey(), eventRouter.OldAuthority) }) @@ -388,12 +525,24 @@ func TestTokenPool(t *testing.T) { 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() + poolInitI, err := token_pool.NewInitializeInstruction(token_pool.Wrapped_PoolType, + admin.PublicKey(), + config.CcipRouterProgram, + p.PoolConfig, + mint, + p.PoolSigner, + admin.PublicKey(), + solana.SystemProgramID).ValidateAndBuild() + require.NoError(t, err) + + // create admin receiver token account + var createR solana.Instruction + createR, p.User[admin.PublicKey()], err = tokens.CreateAssociatedTokenAccount(solana.TokenProgramID, mint, admin.PublicKey(), admin.PublicKey()) require.NoError(t, err) // create pool token account - var createI solana.Instruction - createI, p.PoolTokenAccount, err = tokens.CreateAssociatedTokenAccount(solana.TokenProgramID, mint, p.PoolSigner, admin.PublicKey()) + var createP solana.Instruction + createP, p.PoolTokenAccount, err = tokens.CreateAssociatedTokenAccount(solana.TokenProgramID, mint, p.PoolSigner, admin.PublicKey()) require.NoError(t, err) // initialize pool config @@ -408,7 +557,7 @@ func TestTokenPool(t *testing.T) { ).ValidateAndBuild() require.NoError(t, err) - res := testutils.SendAndConfirm(ctx, t, solanaGoClient, append(instructions, poolInitI, createI, configureI, appendI), admin, config.DefaultCommitment, common.AddSigners(p.Mint)) + res := testutils.SendAndConfirm(ctx, t, solanaGoClient, append(instructions, poolInitI, createR, createP, configureI, appendI), admin, config.DefaultCommitment, common.AddSigners(p.Mint)) require.NotNil(t, res) }) @@ -424,18 +573,32 @@ func TestTokenPool(t *testing.T) { }) 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: tokens.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.CcipLogicReceiver, false, false)) + raw := test_ccip_invalid_receiver.NewPoolProxyReleaseOrMintInstruction( + test_ccip_invalid_receiver.ReleaseOrMintInV1{ + LocalToken: mint, + SourcePoolAddress: remotePool.Address, + Receiver: admin.PublicKey(), + RemoteChainSelector: config.EvmChainSelector, + Amount: tokens.ToLittleEndianU256(1), + }, + config.CcipTokenPoolProgram, + dumbOfframpPoolSigner, + dumbOfframp, + allowedOfframpPDA, + p.PoolConfig, + solana.TokenProgramID, + mint, + p.PoolSigner, + p.PoolTokenAccount, + p.Chain[config.EvmChainSelector], + p.User[admin.PublicKey()], + ) + + raw.AccountMetaSlice = append(raw.AccountMetaSlice, solana.Meta(config.CcipLogicReceiver)) rmI, err := raw.ValidateAndBuild() require.NoError(t, err) - res := testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{rmI}, admin, config.DefaultCommitment) + res := testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{rmI}, user, 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/ccip_config.go b/chains/solana/contracts/tests/config/ccip_config.go index 59f5dbd82..d30527f1f 100644 --- a/chains/solana/contracts/tests/config/ccip_config.go +++ b/chains/solana/contracts/tests/config/ccip_config.go @@ -48,6 +48,8 @@ var ( BillingSignerPDA, _, _ = state.FindFeeBillingSignerPDA(CcipRouterProgram) SvmDestChainStatePDA, _ = state.FindDestChainStatePDA(SvmChainSelector, CcipRouterProgram) EvmDestChainStatePDA, _ = state.FindDestChainStatePDA(EvmChainSelector, CcipRouterProgram) + AllowedOfframpEvmPDA, _ = state.FindAllowedOfframpPDA(EvmChainSelector, CcipOfframpProgram, CcipRouterProgram) + AllowedOfframpSvmPDA, _ = state.FindAllowedOfframpPDA(SvmChainSelector, CcipOfframpProgram, CcipRouterProgram) // fee quoter PDAs FqConfigPDA, _, _ = state.FindFqConfigPDA(FeeQuoterProgram) diff --git a/chains/solana/contracts/tests/txsizing_test.go b/chains/solana/contracts/tests/txsizing_test.go index 48838cf7c..3afe0610f 100644 --- a/chains/solana/contracts/tests/txsizing_test.go +++ b/chains/solana/contracts/tests/txsizing_test.go @@ -75,6 +75,7 @@ func TestTransactionSizing(t *testing.T) { "destChainConfig": mustRandomPubkey(), "arbMessagingSigner": mustRandomPubkey(), "tokenPoolSigner": mustRandomPubkey(), + "offramp": config.CcipOfframpProgram, } tokenTable := map[string]solana.PublicKey{ @@ -105,7 +106,7 @@ func TestTransactionSizing(t *testing.T) { require.NoError(t, err) l := len(bz) if failOnExcessPredicate(tables) { - require.LessOrEqual(t, l, MaxSolanaTxSize) + require.LessOrEqual(t, l, MaxSolanaTxSize, name) } remaining := MaxSolanaTxSize - l var warning string @@ -275,6 +276,8 @@ func TestTransactionSizing(t *testing.T) { tokenIndexes, offrampTable["config"], offrampTable["referenceAddresses"], + offrampTable["offramp"], + mustRandomPubkey(), // router's allowed_offramp (per offramp & per source chain) offrampTable["originChainConfig"], mustRandomPubkey(), // commit report PDA offrampTable["arbMessagingSigner"], @@ -358,7 +361,7 @@ func TestTransactionSizing(t *testing.T) { mustRandomPubkey(): maps.Values(offrampTable), tokenTable["poolLookupTable"]: maps.Values(tokenTable), }, - failOnExcessAlways, + failOnExcessOnlyWithTables, // without lookup tables, we already know it exceeds the max tx size }, } diff --git a/chains/solana/gobindings/ccip_offramp/Execute.go b/chains/solana/gobindings/ccip_offramp/Execute.go index 74a0036c9..28ddb99f5 100644 --- a/chains/solana/gobindings/ccip_offramp/Execute.go +++ b/chains/solana/gobindings/ccip_offramp/Execute.go @@ -43,22 +43,28 @@ type Execute struct { // // [3] = [WRITE] commitReport // - // [4] = [] externalExecutionConfig + // [4] = [] offramp // - // [5] = [WRITE, SIGNER] authority + // [5] = [] allowedOfframp + // ··········· CHECK PDA of the router program verifying the signer is an allowed offramp. + // ··········· If PDA does not exist, the router doesn't allow this offramp // - // [6] = [] systemProgram + // [6] = [] externalExecutionConfig // - // [7] = [] sysvarInstructions + // [7] = [WRITE, SIGNER] authority // - // [8] = [] tokenPoolsSigner + // [8] = [] systemProgram + // + // [9] = [] sysvarInstructions + // + // [10] = [] 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, 9), + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 11), } return nd } @@ -125,59 +131,85 @@ func (inst *Execute) GetCommitReportAccount() *ag_solanago.AccountMeta { return inst.AccountMetaSlice[3] } +// SetOfframpAccount sets the "offramp" account. +func (inst *Execute) SetOfframpAccount(offramp ag_solanago.PublicKey) *Execute { + inst.AccountMetaSlice[4] = ag_solanago.Meta(offramp) + return inst +} + +// GetOfframpAccount gets the "offramp" account. +func (inst *Execute) GetOfframpAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[4] +} + +// SetAllowedOfframpAccount sets the "allowedOfframp" account. +// CHECK PDA of the router program verifying the signer is an allowed offramp. +// If PDA does not exist, the router doesn't allow this offramp +func (inst *Execute) SetAllowedOfframpAccount(allowedOfframp ag_solanago.PublicKey) *Execute { + inst.AccountMetaSlice[5] = ag_solanago.Meta(allowedOfframp) + return inst +} + +// GetAllowedOfframpAccount gets the "allowedOfframp" account. +// CHECK PDA of the router program verifying the signer is an allowed offramp. +// If PDA does not exist, the router doesn't allow this offramp +func (inst *Execute) GetAllowedOfframpAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[5] +} + // SetExternalExecutionConfigAccount sets the "externalExecutionConfig" account. func (inst *Execute) SetExternalExecutionConfigAccount(externalExecutionConfig ag_solanago.PublicKey) *Execute { - inst.AccountMetaSlice[4] = ag_solanago.Meta(externalExecutionConfig) + inst.AccountMetaSlice[6] = ag_solanago.Meta(externalExecutionConfig) return inst } // GetExternalExecutionConfigAccount gets the "externalExecutionConfig" account. func (inst *Execute) GetExternalExecutionConfigAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[4] + return inst.AccountMetaSlice[6] } // SetAuthorityAccount sets the "authority" account. func (inst *Execute) SetAuthorityAccount(authority ag_solanago.PublicKey) *Execute { - inst.AccountMetaSlice[5] = ag_solanago.Meta(authority).WRITE().SIGNER() + inst.AccountMetaSlice[7] = ag_solanago.Meta(authority).WRITE().SIGNER() return inst } // GetAuthorityAccount gets the "authority" account. func (inst *Execute) GetAuthorityAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[5] + return inst.AccountMetaSlice[7] } // SetSystemProgramAccount sets the "systemProgram" account. func (inst *Execute) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Execute { - inst.AccountMetaSlice[6] = ag_solanago.Meta(systemProgram) + inst.AccountMetaSlice[8] = ag_solanago.Meta(systemProgram) return inst } // GetSystemProgramAccount gets the "systemProgram" account. func (inst *Execute) GetSystemProgramAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[6] + return inst.AccountMetaSlice[8] } // SetSysvarInstructionsAccount sets the "sysvarInstructions" account. func (inst *Execute) SetSysvarInstructionsAccount(sysvarInstructions ag_solanago.PublicKey) *Execute { - inst.AccountMetaSlice[7] = ag_solanago.Meta(sysvarInstructions) + inst.AccountMetaSlice[9] = ag_solanago.Meta(sysvarInstructions) return inst } // GetSysvarInstructionsAccount gets the "sysvarInstructions" account. func (inst *Execute) GetSysvarInstructionsAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[7] + return inst.AccountMetaSlice[9] } // SetTokenPoolsSignerAccount sets the "tokenPoolsSigner" account. func (inst *Execute) SetTokenPoolsSignerAccount(tokenPoolsSigner ag_solanago.PublicKey) *Execute { - inst.AccountMetaSlice[8] = ag_solanago.Meta(tokenPoolsSigner) + inst.AccountMetaSlice[10] = ag_solanago.Meta(tokenPoolsSigner) return inst } // GetTokenPoolsSignerAccount gets the "tokenPoolsSigner" account. func (inst *Execute) GetTokenPoolsSignerAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[8] + return inst.AccountMetaSlice[10] } func (inst Execute) Build() *Instruction { @@ -226,18 +258,24 @@ func (inst *Execute) Validate() error { return errors.New("accounts.CommitReport is not set") } if inst.AccountMetaSlice[4] == nil { - return errors.New("accounts.ExternalExecutionConfig is not set") + return errors.New("accounts.Offramp is not set") } if inst.AccountMetaSlice[5] == nil { - return errors.New("accounts.Authority is not set") + return errors.New("accounts.AllowedOfframp is not set") } if inst.AccountMetaSlice[6] == nil { - return errors.New("accounts.SystemProgram is not set") + return errors.New("accounts.ExternalExecutionConfig is not set") } if inst.AccountMetaSlice[7] == nil { - return errors.New("accounts.SysvarInstructions is not set") + return errors.New("accounts.Authority is not set") } if inst.AccountMetaSlice[8] == nil { + return errors.New("accounts.SystemProgram is not set") + } + if inst.AccountMetaSlice[9] == nil { + return errors.New("accounts.SysvarInstructions is not set") + } + if inst.AccountMetaSlice[10] == nil { return errors.New("accounts.TokenPoolsSigner is not set") } } @@ -260,16 +298,18 @@ func (inst *Execute) EncodeToTree(parent ag_treeout.Branches) { }) // Accounts of the instruction: - instructionBranch.Child("Accounts[len=9]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + instructionBranch.Child("Accounts[len=11]").ParentFunc(func(accountsBranch ag_treeout.Branches) { accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0])) accountsBranch.Child(ag_format.Meta(" referenceAddresses", inst.AccountMetaSlice[1])) accountsBranch.Child(ag_format.Meta(" sourceChain", inst.AccountMetaSlice[2])) accountsBranch.Child(ag_format.Meta(" commitReport", inst.AccountMetaSlice[3])) - accountsBranch.Child(ag_format.Meta("externalExecutionConfig", inst.AccountMetaSlice[4])) - accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[5])) - accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[6])) - accountsBranch.Child(ag_format.Meta(" sysvarInstructions", inst.AccountMetaSlice[7])) - accountsBranch.Child(ag_format.Meta(" tokenPoolsSigner", inst.AccountMetaSlice[8])) + accountsBranch.Child(ag_format.Meta(" offramp", inst.AccountMetaSlice[4])) + accountsBranch.Child(ag_format.Meta(" allowedOfframp", inst.AccountMetaSlice[5])) + accountsBranch.Child(ag_format.Meta("externalExecutionConfig", inst.AccountMetaSlice[6])) + accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[7])) + accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[8])) + accountsBranch.Child(ag_format.Meta(" sysvarInstructions", inst.AccountMetaSlice[9])) + accountsBranch.Child(ag_format.Meta(" tokenPoolsSigner", inst.AccountMetaSlice[10])) }) }) }) @@ -323,6 +363,8 @@ func NewExecuteInstruction( referenceAddresses ag_solanago.PublicKey, sourceChain ag_solanago.PublicKey, commitReport ag_solanago.PublicKey, + offramp ag_solanago.PublicKey, + allowedOfframp ag_solanago.PublicKey, externalExecutionConfig ag_solanago.PublicKey, authority ag_solanago.PublicKey, systemProgram ag_solanago.PublicKey, @@ -336,6 +378,8 @@ func NewExecuteInstruction( SetReferenceAddressesAccount(referenceAddresses). SetSourceChainAccount(sourceChain). SetCommitReportAccount(commitReport). + SetOfframpAccount(offramp). + SetAllowedOfframpAccount(allowedOfframp). SetExternalExecutionConfigAccount(externalExecutionConfig). SetAuthorityAccount(authority). SetSystemProgramAccount(systemProgram). diff --git a/chains/solana/gobindings/ccip_offramp/ManuallyExecute.go b/chains/solana/gobindings/ccip_offramp/ManuallyExecute.go index a78d966e6..986df94a3 100644 --- a/chains/solana/gobindings/ccip_offramp/ManuallyExecute.go +++ b/chains/solana/gobindings/ccip_offramp/ManuallyExecute.go @@ -32,22 +32,28 @@ type ManuallyExecute struct { // // [3] = [WRITE] commitReport // - // [4] = [] externalExecutionConfig + // [4] = [] offramp // - // [5] = [WRITE, SIGNER] authority + // [5] = [] allowedOfframp + // ··········· CHECK PDA of the router program verifying the signer is an allowed offramp. + // ··········· If PDA does not exist, the router doesn't allow this offramp // - // [6] = [] systemProgram + // [6] = [] externalExecutionConfig // - // [7] = [] sysvarInstructions + // [7] = [WRITE, SIGNER] authority // - // [8] = [] tokenPoolsSigner + // [8] = [] systemProgram + // + // [9] = [] sysvarInstructions + // + // [10] = [] 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, 9), + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 11), } return nd } @@ -108,59 +114,85 @@ func (inst *ManuallyExecute) GetCommitReportAccount() *ag_solanago.AccountMeta { return inst.AccountMetaSlice[3] } +// SetOfframpAccount sets the "offramp" account. +func (inst *ManuallyExecute) SetOfframpAccount(offramp ag_solanago.PublicKey) *ManuallyExecute { + inst.AccountMetaSlice[4] = ag_solanago.Meta(offramp) + return inst +} + +// GetOfframpAccount gets the "offramp" account. +func (inst *ManuallyExecute) GetOfframpAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[4] +} + +// SetAllowedOfframpAccount sets the "allowedOfframp" account. +// CHECK PDA of the router program verifying the signer is an allowed offramp. +// If PDA does not exist, the router doesn't allow this offramp +func (inst *ManuallyExecute) SetAllowedOfframpAccount(allowedOfframp ag_solanago.PublicKey) *ManuallyExecute { + inst.AccountMetaSlice[5] = ag_solanago.Meta(allowedOfframp) + return inst +} + +// GetAllowedOfframpAccount gets the "allowedOfframp" account. +// CHECK PDA of the router program verifying the signer is an allowed offramp. +// If PDA does not exist, the router doesn't allow this offramp +func (inst *ManuallyExecute) GetAllowedOfframpAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[5] +} + // SetExternalExecutionConfigAccount sets the "externalExecutionConfig" account. func (inst *ManuallyExecute) SetExternalExecutionConfigAccount(externalExecutionConfig ag_solanago.PublicKey) *ManuallyExecute { - inst.AccountMetaSlice[4] = ag_solanago.Meta(externalExecutionConfig) + inst.AccountMetaSlice[6] = ag_solanago.Meta(externalExecutionConfig) return inst } // GetExternalExecutionConfigAccount gets the "externalExecutionConfig" account. func (inst *ManuallyExecute) GetExternalExecutionConfigAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[4] + return inst.AccountMetaSlice[6] } // SetAuthorityAccount sets the "authority" account. func (inst *ManuallyExecute) SetAuthorityAccount(authority ag_solanago.PublicKey) *ManuallyExecute { - inst.AccountMetaSlice[5] = ag_solanago.Meta(authority).WRITE().SIGNER() + inst.AccountMetaSlice[7] = ag_solanago.Meta(authority).WRITE().SIGNER() return inst } // GetAuthorityAccount gets the "authority" account. func (inst *ManuallyExecute) GetAuthorityAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[5] + return inst.AccountMetaSlice[7] } // SetSystemProgramAccount sets the "systemProgram" account. func (inst *ManuallyExecute) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *ManuallyExecute { - inst.AccountMetaSlice[6] = ag_solanago.Meta(systemProgram) + inst.AccountMetaSlice[8] = ag_solanago.Meta(systemProgram) return inst } // GetSystemProgramAccount gets the "systemProgram" account. func (inst *ManuallyExecute) GetSystemProgramAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[6] + return inst.AccountMetaSlice[8] } // SetSysvarInstructionsAccount sets the "sysvarInstructions" account. func (inst *ManuallyExecute) SetSysvarInstructionsAccount(sysvarInstructions ag_solanago.PublicKey) *ManuallyExecute { - inst.AccountMetaSlice[7] = ag_solanago.Meta(sysvarInstructions) + inst.AccountMetaSlice[9] = ag_solanago.Meta(sysvarInstructions) return inst } // GetSysvarInstructionsAccount gets the "sysvarInstructions" account. func (inst *ManuallyExecute) GetSysvarInstructionsAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[7] + return inst.AccountMetaSlice[9] } // SetTokenPoolsSignerAccount sets the "tokenPoolsSigner" account. func (inst *ManuallyExecute) SetTokenPoolsSignerAccount(tokenPoolsSigner ag_solanago.PublicKey) *ManuallyExecute { - inst.AccountMetaSlice[8] = ag_solanago.Meta(tokenPoolsSigner) + inst.AccountMetaSlice[10] = ag_solanago.Meta(tokenPoolsSigner) return inst } // GetTokenPoolsSignerAccount gets the "tokenPoolsSigner" account. func (inst *ManuallyExecute) GetTokenPoolsSignerAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[8] + return inst.AccountMetaSlice[10] } func (inst ManuallyExecute) Build() *Instruction { @@ -206,18 +238,24 @@ func (inst *ManuallyExecute) Validate() error { return errors.New("accounts.CommitReport is not set") } if inst.AccountMetaSlice[4] == nil { - return errors.New("accounts.ExternalExecutionConfig is not set") + return errors.New("accounts.Offramp is not set") } if inst.AccountMetaSlice[5] == nil { - return errors.New("accounts.Authority is not set") + return errors.New("accounts.AllowedOfframp is not set") } if inst.AccountMetaSlice[6] == nil { - return errors.New("accounts.SystemProgram is not set") + return errors.New("accounts.ExternalExecutionConfig is not set") } if inst.AccountMetaSlice[7] == nil { - return errors.New("accounts.SysvarInstructions is not set") + return errors.New("accounts.Authority is not set") } if inst.AccountMetaSlice[8] == nil { + return errors.New("accounts.SystemProgram is not set") + } + if inst.AccountMetaSlice[9] == nil { + return errors.New("accounts.SysvarInstructions is not set") + } + if inst.AccountMetaSlice[10] == nil { return errors.New("accounts.TokenPoolsSigner is not set") } } @@ -239,16 +277,18 @@ func (inst *ManuallyExecute) EncodeToTree(parent ag_treeout.Branches) { }) // Accounts of the instruction: - instructionBranch.Child("Accounts[len=9]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + instructionBranch.Child("Accounts[len=11]").ParentFunc(func(accountsBranch ag_treeout.Branches) { accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[0])) accountsBranch.Child(ag_format.Meta(" referenceAddresses", inst.AccountMetaSlice[1])) accountsBranch.Child(ag_format.Meta(" sourceChain", inst.AccountMetaSlice[2])) accountsBranch.Child(ag_format.Meta(" commitReport", inst.AccountMetaSlice[3])) - accountsBranch.Child(ag_format.Meta("externalExecutionConfig", inst.AccountMetaSlice[4])) - accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[5])) - accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[6])) - accountsBranch.Child(ag_format.Meta(" sysvarInstructions", inst.AccountMetaSlice[7])) - accountsBranch.Child(ag_format.Meta(" tokenPoolsSigner", inst.AccountMetaSlice[8])) + accountsBranch.Child(ag_format.Meta(" offramp", inst.AccountMetaSlice[4])) + accountsBranch.Child(ag_format.Meta(" allowedOfframp", inst.AccountMetaSlice[5])) + accountsBranch.Child(ag_format.Meta("externalExecutionConfig", inst.AccountMetaSlice[6])) + accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[7])) + accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice[8])) + accountsBranch.Child(ag_format.Meta(" sysvarInstructions", inst.AccountMetaSlice[9])) + accountsBranch.Child(ag_format.Meta(" tokenPoolsSigner", inst.AccountMetaSlice[10])) }) }) }) @@ -291,6 +331,8 @@ func NewManuallyExecuteInstruction( referenceAddresses ag_solanago.PublicKey, sourceChain ag_solanago.PublicKey, commitReport ag_solanago.PublicKey, + offramp ag_solanago.PublicKey, + allowedOfframp ag_solanago.PublicKey, externalExecutionConfig ag_solanago.PublicKey, authority ag_solanago.PublicKey, systemProgram ag_solanago.PublicKey, @@ -303,6 +345,8 @@ func NewManuallyExecuteInstruction( SetReferenceAddressesAccount(referenceAddresses). SetSourceChainAccount(sourceChain). SetCommitReportAccount(commitReport). + SetOfframpAccount(offramp). + SetAllowedOfframpAccount(allowedOfframp). SetExternalExecutionConfigAccount(externalExecutionConfig). SetAuthorityAccount(authority). SetSystemProgramAccount(systemProgram). diff --git a/chains/solana/gobindings/test_ccip_invalid_receiver/PoolProxyReleaseOrMint.go b/chains/solana/gobindings/test_ccip_invalid_receiver/PoolProxyReleaseOrMint.go new file mode 100644 index 000000000..8750bb763 --- /dev/null +++ b/chains/solana/gobindings/test_ccip_invalid_receiver/PoolProxyReleaseOrMint.go @@ -0,0 +1,341 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package test_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" +) + +// PoolProxyReleaseOrMint is the `poolProxyReleaseOrMint` instruction. +type PoolProxyReleaseOrMint struct { + ReleaseOrMint *ReleaseOrMintInV1 + + // [0] = [] testPool + // ··········· CHECK + // + // [1] = [] cpiSigner + // ··········· CHECK + // + // [2] = [] offrampProgram + // + // [3] = [] allowedOfframp + // ··········· CHECK + // + // [4] = [WRITE] config + // ··········· CHECK + // + // [5] = [] tokenProgram + // ··········· CHECK + // + // [6] = [WRITE] mint + // + // [7] = [] poolSigner + // ··········· CHECK + // + // [8] = [WRITE] poolTokenAccount + // + // [9] = [WRITE] chainConfig + // ··········· CHECK + // + // [10] = [WRITE] receiverTokenAccount + // ··········· CHECK + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewPoolProxyReleaseOrMintInstructionBuilder creates a new `PoolProxyReleaseOrMint` instruction builder. +func NewPoolProxyReleaseOrMintInstructionBuilder() *PoolProxyReleaseOrMint { + nd := &PoolProxyReleaseOrMint{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 11), + } + return nd +} + +// SetReleaseOrMint sets the "releaseOrMint" parameter. +func (inst *PoolProxyReleaseOrMint) SetReleaseOrMint(releaseOrMint ReleaseOrMintInV1) *PoolProxyReleaseOrMint { + inst.ReleaseOrMint = &releaseOrMint + return inst +} + +// SetTestPoolAccount sets the "testPool" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) SetTestPoolAccount(testPool ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[0] = ag_solanago.Meta(testPool) + return inst +} + +// GetTestPoolAccount gets the "testPool" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) GetTestPoolAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// SetCpiSignerAccount sets the "cpiSigner" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) SetCpiSignerAccount(cpiSigner ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[1] = ag_solanago.Meta(cpiSigner) + return inst +} + +// GetCpiSignerAccount gets the "cpiSigner" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) GetCpiSignerAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// SetOfframpProgramAccount sets the "offrampProgram" account. +func (inst *PoolProxyReleaseOrMint) SetOfframpProgramAccount(offrampProgram ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[2] = ag_solanago.Meta(offrampProgram) + return inst +} + +// GetOfframpProgramAccount gets the "offrampProgram" account. +func (inst *PoolProxyReleaseOrMint) GetOfframpProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +// SetAllowedOfframpAccount sets the "allowedOfframp" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) SetAllowedOfframpAccount(allowedOfframp ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[3] = ag_solanago.Meta(allowedOfframp) + return inst +} + +// GetAllowedOfframpAccount gets the "allowedOfframp" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) GetAllowedOfframpAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[3] +} + +// SetConfigAccount sets the "config" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) SetConfigAccount(config ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[4] = ag_solanago.Meta(config).WRITE() + return inst +} + +// GetConfigAccount gets the "config" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) GetConfigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[4] +} + +// SetTokenProgramAccount sets the "tokenProgram" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[5] = ag_solanago.Meta(tokenProgram) + return inst +} + +// GetTokenProgramAccount gets the "tokenProgram" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) GetTokenProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[5] +} + +// SetMintAccount sets the "mint" account. +func (inst *PoolProxyReleaseOrMint) SetMintAccount(mint ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[6] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +func (inst *PoolProxyReleaseOrMint) GetMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[6] +} + +// SetPoolSignerAccount sets the "poolSigner" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) SetPoolSignerAccount(poolSigner ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[7] = ag_solanago.Meta(poolSigner) + return inst +} + +// GetPoolSignerAccount gets the "poolSigner" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) GetPoolSignerAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[7] +} + +// SetPoolTokenAccountAccount sets the "poolTokenAccount" account. +func (inst *PoolProxyReleaseOrMint) SetPoolTokenAccountAccount(poolTokenAccount ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[8] = ag_solanago.Meta(poolTokenAccount).WRITE() + return inst +} + +// GetPoolTokenAccountAccount gets the "poolTokenAccount" account. +func (inst *PoolProxyReleaseOrMint) GetPoolTokenAccountAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[8] +} + +// SetChainConfigAccount sets the "chainConfig" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) SetChainConfigAccount(chainConfig ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[9] = ag_solanago.Meta(chainConfig).WRITE() + return inst +} + +// GetChainConfigAccount gets the "chainConfig" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) GetChainConfigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[9] +} + +// SetReceiverTokenAccountAccount sets the "receiverTokenAccount" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) SetReceiverTokenAccountAccount(receiverTokenAccount ag_solanago.PublicKey) *PoolProxyReleaseOrMint { + inst.AccountMetaSlice[10] = ag_solanago.Meta(receiverTokenAccount).WRITE() + return inst +} + +// GetReceiverTokenAccountAccount gets the "receiverTokenAccount" account. +// CHECK +func (inst *PoolProxyReleaseOrMint) GetReceiverTokenAccountAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[10] +} + +func (inst PoolProxyReleaseOrMint) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_PoolProxyReleaseOrMint, + }} +} + +// 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 PoolProxyReleaseOrMint) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *PoolProxyReleaseOrMint) 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.TestPool is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.CpiSigner is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.OfframpProgram is not set") + } + if inst.AccountMetaSlice[3] == nil { + return errors.New("accounts.AllowedOfframp is not set") + } + if inst.AccountMetaSlice[4] == nil { + return errors.New("accounts.Config is not set") + } + if inst.AccountMetaSlice[5] == nil { + return errors.New("accounts.TokenProgram is not set") + } + if inst.AccountMetaSlice[6] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.AccountMetaSlice[7] == nil { + return errors.New("accounts.PoolSigner is not set") + } + if inst.AccountMetaSlice[8] == nil { + return errors.New("accounts.PoolTokenAccount is not set") + } + if inst.AccountMetaSlice[9] == nil { + return errors.New("accounts.ChainConfig is not set") + } + if inst.AccountMetaSlice[10] == nil { + return errors.New("accounts.ReceiverTokenAccount is not set") + } + } + return nil +} + +func (inst *PoolProxyReleaseOrMint) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("PoolProxyReleaseOrMint")). + // + 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=11]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" testPool", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" cpiSigner", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("offrampProgram", inst.AccountMetaSlice[2])) + accountsBranch.Child(ag_format.Meta("allowedOfframp", inst.AccountMetaSlice[3])) + accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[4])) + accountsBranch.Child(ag_format.Meta(" tokenProgram", inst.AccountMetaSlice[5])) + accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[6])) + accountsBranch.Child(ag_format.Meta(" poolSigner", inst.AccountMetaSlice[7])) + accountsBranch.Child(ag_format.Meta(" poolToken", inst.AccountMetaSlice[8])) + accountsBranch.Child(ag_format.Meta(" chainConfig", inst.AccountMetaSlice[9])) + accountsBranch.Child(ag_format.Meta(" receiverToken", inst.AccountMetaSlice[10])) + }) + }) + }) +} + +func (obj PoolProxyReleaseOrMint) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `ReleaseOrMint` param: + err = encoder.Encode(obj.ReleaseOrMint) + if err != nil { + return err + } + return nil +} +func (obj *PoolProxyReleaseOrMint) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `ReleaseOrMint`: + err = decoder.Decode(&obj.ReleaseOrMint) + if err != nil { + return err + } + return nil +} + +// NewPoolProxyReleaseOrMintInstruction declares a new PoolProxyReleaseOrMint instruction with the provided parameters and accounts. +func NewPoolProxyReleaseOrMintInstruction( + // Parameters: + releaseOrMint ReleaseOrMintInV1, + // Accounts: + testPool ag_solanago.PublicKey, + cpiSigner ag_solanago.PublicKey, + offrampProgram ag_solanago.PublicKey, + allowedOfframp 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) *PoolProxyReleaseOrMint { + return NewPoolProxyReleaseOrMintInstructionBuilder(). + SetReleaseOrMint(releaseOrMint). + SetTestPoolAccount(testPool). + SetCpiSignerAccount(cpiSigner). + SetOfframpProgramAccount(offrampProgram). + SetAllowedOfframpAccount(allowedOfframp). + SetConfigAccount(config). + SetTokenProgramAccount(tokenProgram). + SetMintAccount(mint). + SetPoolSignerAccount(poolSigner). + SetPoolTokenAccountAccount(poolTokenAccount). + SetChainConfigAccount(chainConfig). + SetReceiverTokenAccountAccount(receiverTokenAccount) +} diff --git a/chains/solana/gobindings/test_ccip_invalid_receiver/PoolProxyReleaseOrMint_test.go b/chains/solana/gobindings/test_ccip_invalid_receiver/PoolProxyReleaseOrMint_test.go new file mode 100644 index 000000000..190747e3f --- /dev/null +++ b/chains/solana/gobindings/test_ccip_invalid_receiver/PoolProxyReleaseOrMint_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package test_ccip_invalid_receiver + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_PoolProxyReleaseOrMint(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("PoolProxyReleaseOrMint"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(PoolProxyReleaseOrMint) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(PoolProxyReleaseOrMint) + 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/test_ccip_invalid_receiver/instructions.go b/chains/solana/gobindings/test_ccip_invalid_receiver/instructions.go index 46d68f72a..449f064e4 100644 --- a/chains/solana/gobindings/test_ccip_invalid_receiver/instructions.go +++ b/chains/solana/gobindings/test_ccip_invalid_receiver/instructions.go @@ -29,6 +29,8 @@ func init() { var ( Instruction_CcipReceive = ag_binary.TypeID([8]byte{11, 244, 9, 249, 44, 83, 47, 245}) + + Instruction_PoolProxyReleaseOrMint = ag_binary.TypeID([8]byte{22, 95, 117, 49, 2, 121, 100, 188}) ) // InstructionIDToName returns the name of the instruction given its ID. @@ -36,6 +38,8 @@ func InstructionIDToName(id ag_binary.TypeID) string { switch id { case Instruction_CcipReceive: return "CcipReceive" + case Instruction_PoolProxyReleaseOrMint: + return "PoolProxyReleaseOrMint" default: return "" } @@ -59,6 +63,9 @@ var InstructionImplDef = ag_binary.NewVariantDefinition( { "ccip_receive", (*CcipReceive)(nil), }, + { + "pool_proxy_release_or_mint", (*PoolProxyReleaseOrMint)(nil), + }, }, ) diff --git a/chains/solana/gobindings/test_ccip_invalid_receiver/types.go b/chains/solana/gobindings/test_ccip_invalid_receiver/types.go index 6cab2f5c0..2eabfc47b 100644 --- a/chains/solana/gobindings/test_ccip_invalid_receiver/types.go +++ b/chains/solana/gobindings/test_ccip_invalid_receiver/types.go @@ -1,3 +1,112 @@ // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. package test_ccip_invalid_receiver + +import ( + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" +) + +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/token_pool/Initialize.go b/chains/solana/gobindings/token_pool/Initialize.go index a90472d6e..16ffaa606 100644 --- a/chains/solana/gobindings/token_pool/Initialize.go +++ b/chains/solana/gobindings/token_pool/Initialize.go @@ -14,6 +14,7 @@ import ( type Initialize struct { PoolType *PoolType RampAuthority *ag_solanago.PublicKey + CcipRouter *ag_solanago.PublicKey // [0] = [WRITE] config // @@ -47,6 +48,12 @@ func (inst *Initialize) SetRampAuthority(rampAuthority ag_solanago.PublicKey) *I return inst } +// SetCcipRouter sets the "ccipRouter" parameter. +func (inst *Initialize) SetCcipRouter(ccipRouter ag_solanago.PublicKey) *Initialize { + inst.CcipRouter = &ccipRouter + return inst +} + // SetConfigAccount sets the "config" account. func (inst *Initialize) SetConfigAccount(config ag_solanago.PublicKey) *Initialize { inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE() @@ -128,6 +135,9 @@ func (inst *Initialize) Validate() error { if inst.RampAuthority == nil { return errors.New("RampAuthority parameter is not set") } + if inst.CcipRouter == nil { + return errors.New("CcipRouter parameter is not set") + } } // Check whether all (required) accounts are set: @@ -160,9 +170,10 @@ func (inst *Initialize) EncodeToTree(parent ag_treeout.Branches) { ParentFunc(func(instructionBranch ag_treeout.Branches) { // Parameters of the instruction: - instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + instructionBranch.Child("Params[len=3]").ParentFunc(func(paramsBranch ag_treeout.Branches) { paramsBranch.Child(ag_format.Param(" PoolType", *inst.PoolType)) paramsBranch.Child(ag_format.Param("RampAuthority", *inst.RampAuthority)) + paramsBranch.Child(ag_format.Param(" CcipRouter", *inst.CcipRouter)) }) // Accounts of the instruction: @@ -188,6 +199,11 @@ func (obj Initialize) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) if err != nil { return err } + // Serialize `CcipRouter` param: + err = encoder.Encode(obj.CcipRouter) + if err != nil { + return err + } return nil } func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { @@ -201,6 +217,11 @@ func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err err if err != nil { return err } + // Deserialize `CcipRouter`: + err = decoder.Decode(&obj.CcipRouter) + if err != nil { + return err + } return nil } @@ -209,6 +230,7 @@ func NewInitializeInstruction( // Parameters: poolType PoolType, rampAuthority ag_solanago.PublicKey, + ccipRouter ag_solanago.PublicKey, // Accounts: config ag_solanago.PublicKey, mint ag_solanago.PublicKey, @@ -218,6 +240,7 @@ func NewInitializeInstruction( return NewInitializeInstructionBuilder(). SetPoolType(poolType). SetRampAuthority(rampAuthority). + SetCcipRouter(ccipRouter). SetConfigAccount(config). SetMintAccount(mint). SetPoolSignerAccount(poolSigner). diff --git a/chains/solana/gobindings/token_pool/ReleaseOrMintTokens.go b/chains/solana/gobindings/token_pool/ReleaseOrMintTokens.go index 65e24721b..c757e891d 100644 --- a/chains/solana/gobindings/token_pool/ReleaseOrMintTokens.go +++ b/chains/solana/gobindings/token_pool/ReleaseOrMintTokens.go @@ -16,26 +16,34 @@ type ReleaseOrMintTokens struct { // [0] = [SIGNER] authority // - // [1] = [WRITE] config + // [1] = [] offrampProgram + // ··········· CHECK offramp program: exists only to derive the allowed offramp PDA + // ··········· and the authority PDA. // - // [2] = [] tokenProgram + // [2] = [] allowedOfframp + // ··········· CHECK PDA of the router program verifying the signer is an allowed offramp. + // ··········· If PDA does not exist, the router doesn't allow this offramp // - // [3] = [WRITE] mint + // [3] = [WRITE] config // - // [4] = [] poolSigner + // [4] = [] tokenProgram // - // [5] = [WRITE] poolTokenAccount + // [5] = [WRITE] mint // - // [6] = [WRITE] chainConfig + // [6] = [] poolSigner // - // [7] = [WRITE] receiverTokenAccount + // [7] = [WRITE] poolTokenAccount + // + // [8] = [WRITE] chainConfig + // + // [9] = [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), + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 10), } return nd } @@ -57,81 +65,111 @@ func (inst *ReleaseOrMintTokens) GetAuthorityAccount() *ag_solanago.AccountMeta return inst.AccountMetaSlice[0] } +// SetOfframpProgramAccount sets the "offrampProgram" account. +// CHECK offramp program: exists only to derive the allowed offramp PDA +// and the authority PDA. +func (inst *ReleaseOrMintTokens) SetOfframpProgramAccount(offrampProgram ag_solanago.PublicKey) *ReleaseOrMintTokens { + inst.AccountMetaSlice[1] = ag_solanago.Meta(offrampProgram) + return inst +} + +// GetOfframpProgramAccount gets the "offrampProgram" account. +// CHECK offramp program: exists only to derive the allowed offramp PDA +// and the authority PDA. +func (inst *ReleaseOrMintTokens) GetOfframpProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// SetAllowedOfframpAccount sets the "allowedOfframp" account. +// CHECK PDA of the router program verifying the signer is an allowed offramp. +// If PDA does not exist, the router doesn't allow this offramp +func (inst *ReleaseOrMintTokens) SetAllowedOfframpAccount(allowedOfframp ag_solanago.PublicKey) *ReleaseOrMintTokens { + inst.AccountMetaSlice[2] = ag_solanago.Meta(allowedOfframp) + return inst +} + +// GetAllowedOfframpAccount gets the "allowedOfframp" account. +// CHECK PDA of the router program verifying the signer is an allowed offramp. +// If PDA does not exist, the router doesn't allow this offramp +func (inst *ReleaseOrMintTokens) GetAllowedOfframpAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + // SetConfigAccount sets the "config" account. func (inst *ReleaseOrMintTokens) SetConfigAccount(config ag_solanago.PublicKey) *ReleaseOrMintTokens { - inst.AccountMetaSlice[1] = ag_solanago.Meta(config).WRITE() + inst.AccountMetaSlice[3] = ag_solanago.Meta(config).WRITE() return inst } // GetConfigAccount gets the "config" account. func (inst *ReleaseOrMintTokens) GetConfigAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[1] + return inst.AccountMetaSlice[3] } // SetTokenProgramAccount sets the "tokenProgram" account. func (inst *ReleaseOrMintTokens) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *ReleaseOrMintTokens { - inst.AccountMetaSlice[2] = ag_solanago.Meta(tokenProgram) + inst.AccountMetaSlice[4] = ag_solanago.Meta(tokenProgram) return inst } // GetTokenProgramAccount gets the "tokenProgram" account. func (inst *ReleaseOrMintTokens) GetTokenProgramAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[2] + return inst.AccountMetaSlice[4] } // SetMintAccount sets the "mint" account. func (inst *ReleaseOrMintTokens) SetMintAccount(mint ag_solanago.PublicKey) *ReleaseOrMintTokens { - inst.AccountMetaSlice[3] = ag_solanago.Meta(mint).WRITE() + inst.AccountMetaSlice[5] = ag_solanago.Meta(mint).WRITE() return inst } // GetMintAccount gets the "mint" account. func (inst *ReleaseOrMintTokens) GetMintAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[3] + return inst.AccountMetaSlice[5] } // SetPoolSignerAccount sets the "poolSigner" account. func (inst *ReleaseOrMintTokens) SetPoolSignerAccount(poolSigner ag_solanago.PublicKey) *ReleaseOrMintTokens { - inst.AccountMetaSlice[4] = ag_solanago.Meta(poolSigner) + inst.AccountMetaSlice[6] = ag_solanago.Meta(poolSigner) return inst } // GetPoolSignerAccount gets the "poolSigner" account. func (inst *ReleaseOrMintTokens) GetPoolSignerAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[4] + return inst.AccountMetaSlice[6] } // SetPoolTokenAccountAccount sets the "poolTokenAccount" account. func (inst *ReleaseOrMintTokens) SetPoolTokenAccountAccount(poolTokenAccount ag_solanago.PublicKey) *ReleaseOrMintTokens { - inst.AccountMetaSlice[5] = ag_solanago.Meta(poolTokenAccount).WRITE() + inst.AccountMetaSlice[7] = ag_solanago.Meta(poolTokenAccount).WRITE() return inst } // GetPoolTokenAccountAccount gets the "poolTokenAccount" account. func (inst *ReleaseOrMintTokens) GetPoolTokenAccountAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[5] + return inst.AccountMetaSlice[7] } // SetChainConfigAccount sets the "chainConfig" account. func (inst *ReleaseOrMintTokens) SetChainConfigAccount(chainConfig ag_solanago.PublicKey) *ReleaseOrMintTokens { - inst.AccountMetaSlice[6] = ag_solanago.Meta(chainConfig).WRITE() + inst.AccountMetaSlice[8] = ag_solanago.Meta(chainConfig).WRITE() return inst } // GetChainConfigAccount gets the "chainConfig" account. func (inst *ReleaseOrMintTokens) GetChainConfigAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[6] + return inst.AccountMetaSlice[8] } // SetReceiverTokenAccountAccount sets the "receiverTokenAccount" account. func (inst *ReleaseOrMintTokens) SetReceiverTokenAccountAccount(receiverTokenAccount ag_solanago.PublicKey) *ReleaseOrMintTokens { - inst.AccountMetaSlice[7] = ag_solanago.Meta(receiverTokenAccount).WRITE() + inst.AccountMetaSlice[9] = ag_solanago.Meta(receiverTokenAccount).WRITE() return inst } // GetReceiverTokenAccountAccount gets the "receiverTokenAccount" account. func (inst *ReleaseOrMintTokens) GetReceiverTokenAccountAccount() *ag_solanago.AccountMeta { - return inst.AccountMetaSlice[7] + return inst.AccountMetaSlice[9] } func (inst ReleaseOrMintTokens) Build() *Instruction { @@ -165,24 +203,30 @@ func (inst *ReleaseOrMintTokens) Validate() error { return errors.New("accounts.Authority is not set") } if inst.AccountMetaSlice[1] == nil { - return errors.New("accounts.Config is not set") + return errors.New("accounts.OfframpProgram is not set") } if inst.AccountMetaSlice[2] == nil { - return errors.New("accounts.TokenProgram is not set") + return errors.New("accounts.AllowedOfframp is not set") } if inst.AccountMetaSlice[3] == nil { - return errors.New("accounts.Mint is not set") + return errors.New("accounts.Config is not set") } if inst.AccountMetaSlice[4] == nil { - return errors.New("accounts.PoolSigner is not set") + return errors.New("accounts.TokenProgram is not set") } if inst.AccountMetaSlice[5] == nil { - return errors.New("accounts.PoolTokenAccount is not set") + return errors.New("accounts.Mint is not set") } if inst.AccountMetaSlice[6] == nil { - return errors.New("accounts.ChainConfig is not set") + return errors.New("accounts.PoolSigner is not set") } if inst.AccountMetaSlice[7] == nil { + return errors.New("accounts.PoolTokenAccount is not set") + } + if inst.AccountMetaSlice[8] == nil { + return errors.New("accounts.ChainConfig is not set") + } + if inst.AccountMetaSlice[9] == nil { return errors.New("accounts.ReceiverTokenAccount is not set") } } @@ -203,15 +247,17 @@ func (inst *ReleaseOrMintTokens) EncodeToTree(parent ag_treeout.Branches) { }) // 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])) + instructionBranch.Child("Accounts[len=10]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" authority", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("offrampProgram", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("allowedOfframp", inst.AccountMetaSlice[2])) + accountsBranch.Child(ag_format.Meta(" config", inst.AccountMetaSlice[3])) + accountsBranch.Child(ag_format.Meta(" tokenProgram", inst.AccountMetaSlice[4])) + accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[5])) + accountsBranch.Child(ag_format.Meta(" poolSigner", inst.AccountMetaSlice[6])) + accountsBranch.Child(ag_format.Meta(" poolToken", inst.AccountMetaSlice[7])) + accountsBranch.Child(ag_format.Meta(" chainConfig", inst.AccountMetaSlice[8])) + accountsBranch.Child(ag_format.Meta(" receiverToken", inst.AccountMetaSlice[9])) }) }) }) @@ -240,6 +286,8 @@ func NewReleaseOrMintTokensInstruction( releaseOrMint ReleaseOrMintInV1, // Accounts: authority ag_solanago.PublicKey, + offrampProgram ag_solanago.PublicKey, + allowedOfframp ag_solanago.PublicKey, config ag_solanago.PublicKey, tokenProgram ag_solanago.PublicKey, mint ag_solanago.PublicKey, @@ -250,6 +298,8 @@ func NewReleaseOrMintTokensInstruction( return NewReleaseOrMintTokensInstructionBuilder(). SetReleaseOrMint(releaseOrMint). SetAuthorityAccount(authority). + SetOfframpProgramAccount(offrampProgram). + SetAllowedOfframpAccount(allowedOfframp). SetConfigAccount(config). SetTokenProgramAccount(tokenProgram). SetMintAccount(mint). diff --git a/chains/solana/gobindings/token_pool/SetRouter.go b/chains/solana/gobindings/token_pool/SetRouter.go new file mode 100644 index 000000000..f8ef54c3d --- /dev/null +++ b/chains/solana/gobindings/token_pool/SetRouter.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" +) + +// SetRouter is the `setRouter` instruction. +type SetRouter struct { + NewRouter *ag_solanago.PublicKey + + // [0] = [WRITE] config + // + // [1] = [SIGNER] authority + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewSetRouterInstructionBuilder creates a new `SetRouter` instruction builder. +func NewSetRouterInstructionBuilder() *SetRouter { + nd := &SetRouter{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), + } + return nd +} + +// SetNewRouter sets the "newRouter" parameter. +func (inst *SetRouter) SetNewRouter(newRouter ag_solanago.PublicKey) *SetRouter { + inst.NewRouter = &newRouter + return inst +} + +// SetConfigAccount sets the "config" account. +func (inst *SetRouter) SetConfigAccount(config ag_solanago.PublicKey) *SetRouter { + inst.AccountMetaSlice[0] = ag_solanago.Meta(config).WRITE() + return inst +} + +// GetConfigAccount gets the "config" account. +func (inst *SetRouter) GetConfigAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// SetAuthorityAccount sets the "authority" account. +func (inst *SetRouter) SetAuthorityAccount(authority ag_solanago.PublicKey) *SetRouter { + inst.AccountMetaSlice[1] = ag_solanago.Meta(authority).SIGNER() + return inst +} + +// GetAuthorityAccount gets the "authority" account. +func (inst *SetRouter) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +func (inst SetRouter) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_SetRouter, + }} +} + +// 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 SetRouter) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *SetRouter) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.NewRouter == nil { + return errors.New("NewRouter 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 *SetRouter) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("SetRouter")). + // + 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("NewRouter", *inst.NewRouter)) + }) + + // 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 SetRouter) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `NewRouter` param: + err = encoder.Encode(obj.NewRouter) + if err != nil { + return err + } + return nil +} +func (obj *SetRouter) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `NewRouter`: + err = decoder.Decode(&obj.NewRouter) + if err != nil { + return err + } + return nil +} + +// NewSetRouterInstruction declares a new SetRouter instruction with the provided parameters and accounts. +func NewSetRouterInstruction( + // Parameters: + newRouter ag_solanago.PublicKey, + // Accounts: + config ag_solanago.PublicKey, + authority ag_solanago.PublicKey) *SetRouter { + return NewSetRouterInstructionBuilder(). + SetNewRouter(newRouter). + SetConfigAccount(config). + SetAuthorityAccount(authority) +} diff --git a/chains/solana/gobindings/token_pool/SetRouter_test.go b/chains/solana/gobindings/token_pool/SetRouter_test.go new file mode 100644 index 000000000..9b68cbd78 --- /dev/null +++ b/chains/solana/gobindings/token_pool/SetRouter_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_SetRouter(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("SetRouter"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(SetRouter) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(SetRouter) + 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 index b37db9701..4adbbd077 100644 --- a/chains/solana/gobindings/token_pool/accounts.go +++ b/chains/solana/gobindings/token_pool/accounts.go @@ -19,6 +19,7 @@ type Config struct { Owner ag_solanago.PublicKey ProposedOwner ag_solanago.PublicKey RampAuthority ag_solanago.PublicKey + CcipRouter ag_solanago.PublicKey } var ConfigDiscriminator = [8]byte{155, 12, 170, 224, 30, 250, 204, 130} @@ -79,6 +80,11 @@ func (obj Config) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { if err != nil { return err } + // Serialize `CcipRouter` param: + err = encoder.Encode(obj.CcipRouter) + if err != nil { + return err + } return nil } @@ -146,6 +152,11 @@ func (obj *Config) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) if err != nil { return err } + // Deserialize `CcipRouter`: + err = decoder.Decode(&obj.CcipRouter) + 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 index a27b50ef6..3489d151d 100644 --- a/chains/solana/gobindings/token_pool/instructions.go +++ b/chains/solana/gobindings/token_pool/instructions.go @@ -36,6 +36,8 @@ var ( Instruction_SetRampAuthority = ag_binary.TypeID([8]byte{181, 180, 204, 162, 156, 188, 239, 153}) + Instruction_SetRouter = ag_binary.TypeID([8]byte{236, 248, 107, 200, 151, 160, 44, 250}) + Instruction_InitChainRemoteConfig = ag_binary.TypeID([8]byte{21, 150, 133, 36, 2, 116, 199, 129}) Instruction_EditChainRemoteConfig = ag_binary.TypeID([8]byte{149, 112, 186, 72, 116, 217, 159, 175}) @@ -62,6 +64,8 @@ func InstructionIDToName(id ag_binary.TypeID) string { return "AcceptOwnership" case Instruction_SetRampAuthority: return "SetRampAuthority" + case Instruction_SetRouter: + return "SetRouter" case Instruction_InitChainRemoteConfig: return "InitChainRemoteConfig" case Instruction_EditChainRemoteConfig: @@ -108,6 +112,9 @@ var InstructionImplDef = ag_binary.NewVariantDefinition( { "set_ramp_authority", (*SetRampAuthority)(nil), }, + { + "set_router", (*SetRouter)(nil), + }, { "init_chain_remote_config", (*InitChainRemoteConfig)(nil), }, diff --git a/chains/solana/utils/common/common.go b/chains/solana/utils/common/common.go index c4a6d6d89..56b6e6595 100644 --- a/chains/solana/utils/common/common.go +++ b/chains/solana/utils/common/common.go @@ -27,6 +27,14 @@ func Uint64ToLE(chain uint64) []byte { return chainLE } +func ToPadded64Bytes(input []byte) (result [64]byte) { + if len(input) > 64 { + panic("input is too long") + } + copy(result[:], input[:]) + return result +} + func To28BytesLE(value uint64) [28]byte { le := make([]byte, 28) binary.LittleEndian.PutUint64(le, value) diff --git a/chains/solana/utils/tokens/tokenpool.go b/chains/solana/utils/tokens/tokenpool.go index 533127761..f1452553e 100644 --- a/chains/solana/utils/tokens/tokenpool.go +++ b/chains/solana/utils/tokens/tokenpool.go @@ -67,11 +67,19 @@ func NewTokenPool(program solana.PublicKey) (TokenPool, error) { return TokenPool{}, err } // preload with defined config.EvmChainSelector - chainPDA, _, err := TokenPoolChainConfigPDA(config.EvmChainSelector, mint.PublicKey(), config.CcipTokenPoolProgram) + evmChainPDA, _, err := TokenPoolChainConfigPDA(config.EvmChainSelector, mint.PublicKey(), config.CcipTokenPoolProgram) if err != nil { return TokenPool{}, err } - billingPDA, _, err := state.FindFqPerChainPerTokenConfigPDA(config.EvmChainSelector, mint.PublicKey(), config.FeeQuoterProgram) + svmChainPDA, _, err := TokenPoolChainConfigPDA(config.SvmChainSelector, mint.PublicKey(), config.CcipTokenPoolProgram) + if err != nil { + return TokenPool{}, err + } + evmBillingPDA, _, err := state.FindFqPerChainPerTokenConfigPDA(config.EvmChainSelector, mint.PublicKey(), config.FeeQuoterProgram) + if err != nil { + return TokenPool{}, err + } + svmBillingPDA, _, err := state.FindFqPerChainPerTokenConfigPDA(config.SvmChainSelector, mint.PublicKey(), config.FeeQuoterProgram) if err != nil { return TokenPool{}, err } @@ -92,8 +100,10 @@ func NewTokenPool(program solana.PublicKey) (TokenPool, error) { Chain: map[uint64]solana.PublicKey{}, Billing: map[uint64]solana.PublicKey{}, } - p.Chain[config.EvmChainSelector] = chainPDA - p.Billing[config.EvmChainSelector] = billingPDA + p.Chain[config.EvmChainSelector] = evmChainPDA + p.Chain[config.SvmChainSelector] = svmChainPDA + p.Billing[config.EvmChainSelector] = evmBillingPDA + p.Billing[config.SvmChainSelector] = svmBillingPDA p.PoolConfig, err = TokenPoolConfigAddress(p.Mint.PublicKey(), config.CcipTokenPoolProgram) if err != nil { return TokenPool{}, err