Skip to content

[Solana] Execution buffer contract for large TX #897

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions chains/solana/contracts/Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ test_ccip_receiver = "EvhgrPhTDt4LcSPS2kfJgH6T6XWZ6wT3X9ncDGLT1vui"
test_token_pool = "JuCcZ4smxAYv9QHJ36jshA7pA3FuQ3vQeWLUeAtZduJ"
timelock = "DoajfR5tK24xVw51fWcawUZWhAXD8yrBJVacc13neVQA"
ping_pong_demo = "PPbZmYFf5SPAM9Jhm9mNmYoCwT7icPYVKAfJoMCQovU"
execution_buffer = "Buff7ufrtmskFnHtGd9LXaWSAjX6wAMQ2q2s2WSWoSGS"

[registry]
url = "https://anchor.projectserum.com"
Expand Down
11 changes: 11 additions & 0 deletions chains/solana/contracts/Cargo.lock

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

1 change: 1 addition & 0 deletions chains/solana/contracts/programs/ccip-common/src/seed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub const CURSES: &[u8] = b"curses";
pub const DEST_CHAIN_STATE: &[u8] = b"dest_chain_state";
pub const NONCE: &[u8] = b"nonce";
pub const ALLOWED_OFFRAMP: &[u8] = b"allowed_offramp";
pub const EXECUTION_BUFFER: &[u8] = b"execution_buffer";

// arbitrary messaging signer
pub const EXTERNAL_EXECUTION_CONFIG: &[u8] = b"external_execution_config";
Expand Down
23 changes: 23 additions & 0 deletions chains/solana/contracts/programs/execution-buffer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "execution_buffer"
version = "0.1.0-dev"
description = "Created with Anchor"
edition = "2021"

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

[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" }
bytemuck = "1.7"
ccip_common = {path = "../ccip-common"}
ccip_offramp = {path = "../ccip-offramp", features = ["cpi"]}
113 changes: 113 additions & 0 deletions chains/solana/contracts/programs/execution-buffer/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use crate::state::{BufferId, BufferedReport};
use anchor_lang::prelude::*;
use ccip_common::seed;

pub const ANCHOR_DISCRIMINATOR: usize = 8; // size in bytes

#[derive(Accounts)]
#[instruction(buffer_id: BufferId, data: Vec<u8>)]
pub struct AppendExecutionReportData<'info> {
#[account(
mut,
seeds = [seed::EXECUTION_BUFFER, authority.key().as_ref(), &buffer_id.bytes],
bump,
realloc = ANCHOR_DISCRIMINATOR + BufferedReport::INIT_SPACE + buffered_report.raw_report_data.len() + data.len(),
realloc::payer = authority,
realloc::zero = false
)]
pub buffered_report: Account<'info, BufferedReport>,

#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
#[instruction(buffer_id: BufferId)]
pub struct InitializeExecutionReportBuffer<'info> {
#[account(
init,
seeds = [seed::EXECUTION_BUFFER, authority.key().as_ref(), &buffer_id.bytes],
bump,
space = ANCHOR_DISCRIMINATOR + BufferedReport::INIT_SPACE,
payer = authority,
)]
pub buffered_report: Account<'info, BufferedReport>,

#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
#[instruction(buffer_id: BufferId)]
pub struct CloseBuffer<'info> {
#[account(
mut,
seeds = [seed::EXECUTION_BUFFER, authority.key().as_ref(), &buffer_id.bytes],
bump,
close = authority,
)]
pub buffered_report: Account<'info, BufferedReport>,

#[account(mut)]
pub authority: Signer<'info>,
}

#[derive(Accounts)]
#[instruction(buffer_id: BufferId, _token_indices: Vec<u8>)]
pub struct ExecuteContext<'info> {
#[account(
mut,
seeds = [seed::EXECUTION_BUFFER, authority.key().as_ref(), &buffer_id.bytes],
bump,
close = authority,
)]
pub buffered_report: Account<'info, BufferedReport>,
// ------------------------
// Accounts for offramp CPI: All validations are done by the offramp
/// CHECK: validated during CPI
pub config: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub reference_addresses: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub source_chain: UncheckedAccount<'info>,
/// CHECK: validated during CPI
#[account(mut)]
pub commit_report: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub offramp: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub allowed_offramp: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub rmn_remote: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub rmn_remote_curses: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub rmn_remote_config: UncheckedAccount<'info>,
/// CHECK: validated during CPI
pub sysvar_instructions: UncheckedAccount<'info>,

#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
// remaining accounts
// [receiver_program, external_execution_signer, receiver_account, ...user specified accounts from message data for arbitrary messaging]
// +
// [
// ccip_offramp_pools_signer - derivable PDA [seed::EXTERNAL_TOKEN_POOL, pool_program], seeds::program=offramp (not in lookup table)
// user/sender token account (must be associated token account - derivable PDA [wallet_addr, token_program, mint])
// per chain per token config (ccip: billing, ccip admin controlled - derivable PDA [chain_selector, mint])
// pool chain config (pool: custom configs that may include rate limits & remote chain configs, pool admin controlled - derivable [chain_selector, mint])
// token pool lookup table
// token registry PDA
// pool program
// pool config
// pool token account (must be associated token account - derivable PDA [wallet_addr, token_program, mint])
// pool signer
// token program
// token mint
// ccip_router_pools_signer - derivable PDA [seed::EXTERNAL_TOKEN_POOL, pool_program], seeds::program=router (present in lookup table)
// ...additional accounts for pool config
// ] x N tokens
}
70 changes: 70 additions & 0 deletions chains/solana/contracts/programs/execution-buffer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use anchor_lang::prelude::*;

mod context;
use crate::context::*;

mod state;
use crate::state::*;

declare_id!("Buff7ufrtmskFnHtGd9LXaWSAjX6wAMQ2q2s2WSWoSGS");

#[program]
pub mod execution_buffer {

use super::*;

pub fn manually_execute_buffered<'info>(
ctx: Context<'_, '_, 'info, 'info, ExecuteContext<'info>>,
_buffer_id: BufferId,
token_indexes: Vec<u8>,
) -> Result<()> {
let cpi_accounts = ccip_offramp::cpi::accounts::ExecuteReportContext {
config: ctx.accounts.config.to_account_info(),
reference_addresses: ctx.accounts.reference_addresses.to_account_info(),
source_chain: ctx.accounts.source_chain.to_account_info(),
commit_report: ctx.accounts.commit_report.to_account_info(),
offramp: ctx.accounts.offramp.to_account_info(),
allowed_offramp: ctx.accounts.allowed_offramp.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
sysvar_instructions: ctx.accounts.sysvar_instructions.to_account_info(),
rmn_remote: ctx.accounts.rmn_remote.to_account_info(),
rmn_remote_curses: ctx.accounts.rmn_remote_curses.to_account_info(),
rmn_remote_config: ctx.accounts.rmn_remote_config.to_account_info(),
};
let cpi_remaining_accounts = ctx.remaining_accounts.to_vec();
let cpi_context = CpiContext::new(ctx.accounts.offramp.to_account_info(), cpi_accounts)
.with_remaining_accounts(cpi_remaining_accounts);
ccip_offramp::cpi::manually_execute(
cpi_context,
ctx.accounts.buffered_report.raw_report_data.clone(),
token_indexes,
)
}

pub fn append_execution_report_data<'info>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks good overall, but I'm curious if we need to implement a hard cap for the report data size on-chain? While the CPI limits would eventually act as a constraint, I'm just asking out of curiosity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, not sure myself, what's the worst case scenario here? Even if we promise "infinite size TX", I would imagine that users experienced with Solana will expect something to break.

ctx: Context<'_, '_, 'info, 'info, AppendExecutionReportData>,
_buffer_id: BufferId,
data: Vec<u8>,
) -> Result<()> {
ctx.accounts
.buffered_report
.raw_report_data
.extend_from_slice(&data);
Ok(())
}

pub fn initialize_execution_report_buffer<'info>(
_ctx: Context<'_, '_, 'info, 'info, InitializeExecutionReportBuffer>,
_buffer_id: BufferId,
) -> Result<()> {
Ok(())
}

pub fn close_buffer<'info>(
_ctx: Context<'_, '_, 'info, 'info, CloseBuffer>,
_buffer_id: BufferId,
) -> Result<()> {
Ok(())
}
}
15 changes: 15 additions & 0 deletions chains/solana/contracts/programs/execution-buffer/src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use anchor_lang::prelude::*;

// Identifies a buffer. Has no specific meaning: it's only used
// by the caller to track which buffer is which in case of uploading several.
#[derive(Clone, Debug, AnchorDeserialize, AnchorSerialize, Eq, PartialEq)]
pub struct BufferId {
pub bytes: [u8; 32],
}

#[account]
#[derive(Debug, InitSpace)]
pub struct BufferedReport {
#[max_len(0)]
pub raw_report_data: Vec<u8>,
}
Loading
Loading