Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

solana: example ccip sender #539

Merged
merged 12 commits into from
Feb 7, 2025
1 change: 1 addition & 0 deletions chains/solana/contracts/Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mcm = "6UmMZr5MEqiKWD5jqTJd1WCR5kT8oZuFYBLJFi1o6GQX"
timelock = "LoCoNsJFuhTkSQjfdDfn3yuwqhSYoPujmviRHVCzsqn"
fee_quoter = "FeeVB9Q77QvyaENRL1i77BjW6cTkaWwNLjNbZg9JHqpw"
token_pool = "GRvFSLwR7szpjgNEZbGe4HtxfJYXqySXuuRUAJDpu4WH"
example_ccip_sender = "CcipSender111111111111111111111111111111111"
example_ccip_receiver = "CcipReceiver1111111111111111111111111111111"
test_ccip_receiver = "CtEVnHsQzhTNWav8skikiV2oF6Xx7r7uGGa8eCDQtTjH"
test_ccip_invalid_receiver = "9Vjda3WU2gsJgE4VdU6QuDw8rfHLyigfFyWs3XDPNUn8"
Expand Down
11 changes: 10 additions & 1 deletion chains/solana/contracts/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn ccip_send<'info>(
dest_chain_selector: u64,
message: SVM2AnyMessage,
token_indexes: Vec<u8>,
) -> Result<()> {
) -> Result<[u8; 32]> {
// The Config Account stores the default values for the Router, the SVM Chain Selector, the Default Gas Limit and the Default Allow Out Of Order Execution and Admin Ownership
let config = ctx.accounts.config.load()?;

Expand Down Expand Up @@ -212,7 +212,7 @@ pub fn ccip_send<'info>(
message: new_message,
});

Ok(())
Ok(*message_id)
}

mod helpers {
Expand Down
2 changes: 1 addition & 1 deletion chains/solana/contracts/programs/ccip-router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ pub mod ccip_router {
dest_chain_selector: u64,
message: SVM2AnyMessage,
token_indexes: Vec<u8>,
) -> Result<()> {
) -> Result<[u8; 32]> {
v1::onramp::ccip_send(ctx, dest_chain_selector, message, token_indexes)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
idel-build = ["anchor-lang/idl-build"]

[dependencies]
solana-program = "1.17.25" # pin solana to 1.17
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ pub struct CcipReceive<'info> {
bump,
)]
pub approved_sender: Account<'info, ApprovedSender>, // if PDA does not exist, the message sender and/or source chain are not approved
#[account(
seeds = [b"state"],
bump,
)]
pub state: Account<'info, BaseState>,
}

Expand Down
22 changes: 22 additions & 0 deletions chains/solana/contracts/programs/example-ccip-sender/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "example_ccip_sender"
version = "0.1.0-dev"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "example_ccip_sender"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []

[dependencies]
solana-program = "1.17.25" # pin solana to 1.17
anchor-lang = { version = "0.29.0", features = [] }
anchor-spl = "0.29.0"
fee_quoter = { version = "0.1.0-dev", path = "../fee-quoter", features = ["no-entrypoint"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []
243 changes: 243 additions & 0 deletions chains/solana/contracts/programs/example-ccip-sender/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{Mint, TokenAccount};

use crate::{BaseState, CcipSenderError, RemoteChainConfig, CCIP_SENDER, CHAIN_CONFIG_SEED};

const ANCHOR_DISCRIMINATOR: usize = 8;

#[derive(Accounts, Debug)]
pub struct Initialize<'info> {
#[account(
init,
seeds = [b"state"],
bump,
payer = authority,
space = ANCHOR_DISCRIMINATOR + BaseState::INIT_SPACE,
)]
pub state: Account<'info, BaseState>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts, Debug)]
#[instruction(chain_selector: u64)]
pub struct CcipSend<'info> {
#[account(
seeds = [b"state"],
bump,
)]
pub state: Account<'info, BaseState>,
#[account(
seeds = [CHAIN_CONFIG_SEED, chain_selector.to_le_bytes().as_ref()],
bump,
)]
pub chain_config: Account<'info, RemoteChainConfig>,
#[account(
seeds = [CCIP_SENDER],
bump,
)]
#[account(mut)]
/// CHECK: sender CPI caller
pub ccip_sender: UncheckedAccount<'info>,
#[account(
mut,
associated_token::authority = authority.key(),
associated_token::mint = ccip_fee_token_mint.key(),
associated_token::token_program = ccip_fee_token_program.key(),
)]
pub authority_fee_token_ata: InterfaceAccount<'info, TokenAccount>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,

// ------------------------
// required ccip accounts - accounts are checked by the router
#[account(
address = state.router @ CcipSenderError::InvalidRouter,
)]
/// CHECK: used as router entry point for CPI
pub ccip_router: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub ccip_config: UncheckedAccount<'info>,
#[account(mut)]
/// CHECK: validated during CPI
pub ccip_dest_chain_state: UncheckedAccount<'info>,
#[account(mut)]
/// CHECK: validated during CPI
pub ccip_sender_nonce: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub ccip_fee_token_program: UncheckedAccount<'info>,
#[account(
owner = ccip_fee_token_program.key(),
)]
pub ccip_fee_token_mint: InterfaceAccount<'info, Mint>,
#[account(
mut,
associated_token::authority = ccip_sender.key(),
associated_token::mint = ccip_fee_token_mint.key(),
associated_token::token_program = ccip_fee_token_program.key(),
)]
pub ccip_fee_token_user_ata: InterfaceAccount<'info, TokenAccount>,
#[account(
mut,
associated_token::authority = ccip_fee_billing_signer.key(),
associated_token::mint = ccip_fee_token_mint.key(),
associated_token::token_program = ccip_fee_token_program.key(),
)]
pub ccip_fee_token_receiver: InterfaceAccount<'info, TokenAccount>,
/// CHECK: validated during CPI
pub ccip_fee_billing_signer: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub ccip_fee_quoter: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub ccip_fee_quoter_config: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub ccip_fee_quoter_dest_chain: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub ccip_fee_quoter_billing_token_config: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub ccip_fee_quoter_link_token_config: UncheckedAccount<'info>,
#[account(mut)]
/// CHECK: validated during CPI
pub ccip_token_pools_signer: UncheckedAccount<'info>,
}

#[derive(Accounts, Debug)]
pub struct UpdateConfig<'info> {
#[account(
mut,
seeds = [b"state"],
bump,
)]
pub state: Account<'info, BaseState>,
#[account(
address = state.owner @ CcipSenderError::OnlyOwner,
)]
pub authority: Signer<'info>,
}

#[derive(Accounts, Debug)]
#[instruction(chain_selector: u64, _recipient: Vec<u8>, extra_args_bytes: Vec<u8>)]
pub struct InitChainConfig<'info> {
#[account(
mut,
seeds = [b"state"],
bump,
)]
pub state: Account<'info, BaseState>,
#[account(
init,
seeds = [CHAIN_CONFIG_SEED, chain_selector.to_le_bytes().as_ref()],
bump,
space = ANCHOR_DISCRIMINATOR + RemoteChainConfig::INIT_SPACE + extra_args_bytes.len(),
payer = authority,
)]
pub chain_config: Account<'info, RemoteChainConfig>,
#[account(
mut,
address = state.owner @ CcipSenderError::OnlyOwner,
)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts, Debug)]
#[instruction(chain_selector: u64, _recipient: Vec<u8>, extra_args_bytes: Vec<u8>)]
pub struct UpdateChainConfig<'info> {
#[account(
mut,
seeds = [b"state"],
bump,
)]
pub state: Account<'info, BaseState>,
#[account(
mut,
seeds = [CHAIN_CONFIG_SEED, chain_selector.to_le_bytes().as_ref()],
bump,
realloc = ANCHOR_DISCRIMINATOR + RemoteChainConfig::INIT_SPACE + extra_args_bytes.len(),
realloc::payer = authority,
realloc::zero = true,
)]
pub chain_config: Account<'info, RemoteChainConfig>,
#[account(
mut,
address = state.owner @ CcipSenderError::OnlyOwner,
)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts, Debug)]
#[instruction(chain_selector: u64)]
pub struct RemoveChainConfig<'info> {
#[account(
mut,
seeds = [b"state"],
bump,
)]
pub state: Account<'info, BaseState>,
#[account(
mut,
seeds = [CHAIN_CONFIG_SEED, chain_selector.to_le_bytes().as_ref()],
bump,
close = authority,
)]
pub chain_config: Account<'info, RemoteChainConfig>,
#[account(
mut,
address = state.owner @ CcipSenderError::OnlyOwner,
)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts, Debug)]
pub struct AcceptOwnership<'info> {
#[account(
mut,
seeds = [b"state"],
bump,
)]
pub state: Account<'info, BaseState>,
#[account(
address = state.proposed_owner @ CcipSenderError::OnlyProposedOwner,
)]
pub authority: Signer<'info>,
}

#[derive(Accounts, Debug)]
pub struct WithdrawTokens<'info> {
#[account(
mut,
seeds = [b"state"],
bump,
)]
pub state: Account<'info, BaseState>,
#[account(
mut,
token::mint = mint,
token::authority = ccip_sender,
token::token_program = token_program,
)]
pub program_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(
mut,
token::mint = mint,
token::token_program = token_program,
)]
pub to_token_account: InterfaceAccount<'info, TokenAccount>,
pub mint: InterfaceAccount<'info, Mint>,
#[account(address = *mint.to_account_info().owner)]
/// CHECK: CPI to token program
pub token_program: AccountInfo<'info>,
#[account(
seeds = [CCIP_SENDER],
bump,
)]
/// CHECK: CPI signer for tokens
pub ccip_sender: UncheckedAccount<'info>,
#[account(
address = state.owner @ CcipSenderError::OnlyOwner,
)]
pub authority: Signer<'info>,
}
Loading
Loading