Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ pub fn assemble_and_submit_misbehaviour<'info>(
);

let chunk_count = chunk_count as usize;

require!(
chunk_count > 0 && chunk_count <= ctx.remaining_accounts.len(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

chunk count should never be less than remaining accounts, it's ok if it's equal - means we have no preverifies

ErrorCode::InvalidChunkCount
);

let submitter = ctx.accounts.submitter.key();

let misbehaviour_bytes = assemble_chunks(&ctx, submitter, chunk_count)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,122 @@ fn test_assemble_and_submit_misbehaviour_client_already_frozen() {
mollusk.process_and_validate_instruction(&instruction, &test_accounts.accounts, &checks);
}

/// Test that `chunk_count` exceeding `remaining_accounts` fails with `InvalidChunkCount`
#[test]
fn test_assemble_and_submit_misbehaviour_chunk_count_exceeds_remaining_accounts() {
let chain_id = "test-chain";
let height_1 = 90;
let height_2 = 95;
let submitter = Pubkey::new_unique();

let misbehaviour_bytes = create_mock_misbehaviour_bytes(100, 100, true);

// Setup test accounts with chunks
let test_accounts = setup_test_accounts(TestSetupConfig {
chain_id,
height_1,
height_2,
submitter,
client_frozen: false,
with_valid_consensus_states: true,
with_chunks: true,
misbehaviour_bytes: &misbehaviour_bytes,
});

// Remember how many chunk PDAs we actually have
let actual_chunk_count = test_accounts.chunk_pdas.len();

// Create instruction with inflated chunk_count
let (access_manager_pda, _) =
solana_ibc_types::access_manager::AccessManager::pda(access_manager::ID);

let instruction_data = crate::instruction::AssembleAndSubmitMisbehaviour {
chunk_count: (actual_chunk_count + 5) as u8, // Claim more chunks than provided
trusted_height_1: height_1,
trusted_height_2: height_2,
};

let mut account_metas = vec![
AccountMeta::new(test_accounts.client_state_pda, false),
AccountMeta::new_readonly(test_accounts.app_state_pda, false),
AccountMeta::new_readonly(access_manager_pda, false),
AccountMeta::new_readonly(test_accounts.trusted_consensus_state_1_pda, false),
AccountMeta::new_readonly(test_accounts.trusted_consensus_state_2_pda, false),
AccountMeta::new(test_accounts.submitter, true),
AccountMeta::new_readonly(anchor_lang::solana_program::sysvar::instructions::ID, false),
];

// Only add the actual chunk accounts (fewer than claimed)
for chunk_pda in &test_accounts.chunk_pdas {
account_metas.push(AccountMeta::new(*chunk_pda, false));
}

let instruction = Instruction {
program_id: crate::ID,
accounts: account_metas,
data: instruction_data.data(),
};

let mollusk = Mollusk::new(&crate::ID, PROGRAM_BINARY_PATH);
let checks = vec![Check::err(
anchor_lang::error::Error::from(ErrorCode::InvalidChunkCount).into(),
)];
mollusk.process_and_validate_instruction(&instruction, &test_accounts.accounts, &checks);
}

/// Test that `chunk_count` = 0 fails with `InvalidChunkCount`
#[test]
fn test_assemble_and_submit_misbehaviour_zero_chunk_count_rejected() {
let chain_id = "test-chain";
let height_1 = 90;
let height_2 = 95;
let submitter = Pubkey::new_unique();

// Setup without chunks
let test_accounts = setup_test_accounts(TestSetupConfig {
chain_id,
height_1,
height_2,
submitter,
client_frozen: false,
with_valid_consensus_states: true,
with_chunks: false,
misbehaviour_bytes: &[],
});

let (access_manager_pda, _) =
solana_ibc_types::access_manager::AccessManager::pda(access_manager::ID);

// Create instruction with chunk_count = 0
let instruction_data = crate::instruction::AssembleAndSubmitMisbehaviour {
chunk_count: 0, // Zero chunks - invalid
trusted_height_1: height_1,
trusted_height_2: height_2,
};

let account_metas = vec![
AccountMeta::new(test_accounts.client_state_pda, false),
AccountMeta::new_readonly(test_accounts.app_state_pda, false),
AccountMeta::new_readonly(access_manager_pda, false),
AccountMeta::new_readonly(test_accounts.trusted_consensus_state_1_pda, false),
AccountMeta::new_readonly(test_accounts.trusted_consensus_state_2_pda, false),
AccountMeta::new(test_accounts.submitter, true),
AccountMeta::new_readonly(anchor_lang::solana_program::sysvar::instructions::ID, false),
];

let instruction = Instruction {
program_id: crate::ID,
accounts: account_metas,
data: instruction_data.data(),
};

let mollusk = Mollusk::new(&crate::ID, PROGRAM_BINARY_PATH);
let checks = vec![Check::err(
anchor_lang::error::Error::from(ErrorCode::InvalidChunkCount).into(),
)];
mollusk.process_and_validate_instruction(&instruction, &test_accounts.accounts, &checks);
}

#[test]
fn test_assemble_and_submit_misbehaviour_wrong_chunk_pda() {
let chain_id = "test-chain";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ pub fn assemble_and_update_client<'info>(

let chunk_count = chunk_count as usize;

require!(
chunk_count > 0 && chunk_count <= ctx.remaining_accounts.len(),
ErrorCode::InvalidChunkCount
);

let header_bytes = assemble_chunks(&ctx, target_height, chunk_count)?;

let result = process_header_update(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,197 @@ fn test_assemble_updates_latest_height() {
}
}

/// Test that `chunk_count` exceeding `remaining_accounts` fails with `InvalidChunkCount`
#[test]
fn test_assemble_chunk_count_exceeds_remaining_accounts() {
let mollusk = setup_mollusk();

let chain_id = "test-chain";
let target_height = 100u64;
let trusted_height = 90u64;
let submitter = Pubkey::new_unique();

// Create only 2 chunk accounts
let (_, chunks) = create_test_header_and_chunks(2);

let client_state_pda = derive_client_state_pda();
let (consensus_state_pda, _) = Pubkey::find_program_address(
&[
crate::state::ConsensusStateStore::SEED,
&target_height.to_le_bytes(),
],
&crate::ID,
);

// Only create PDAs for 2 chunks
let chunk_pdas = get_chunk_pdas(&submitter, target_height, 2);

let (access_manager_pda, _) =
solana_ibc_types::access_manager::AccessManager::pda(access_manager::ID);
let (app_state_pda, app_state_account) = create_app_state_account(access_manager::ID);

let trusted_consensus_pda = derive_consensus_state_pda(trusted_height);

// Create instruction claiming 5 chunks but only provide 2 accounts
let mut account_metas = vec![
AccountMeta::new(client_state_pda, false),
AccountMeta::new_readonly(app_state_pda, false),
AccountMeta::new_readonly(access_manager_pda, false),
AccountMeta::new_readonly(trusted_consensus_pda, false),
AccountMeta::new(consensus_state_pda, false),
AccountMeta::new(submitter, true),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(anchor_lang::solana_program::sysvar::instructions::ID, false),
];

// Only add 2 chunk accounts to remaining_accounts
for chunk_pda in &chunk_pdas {
account_metas.push(AccountMeta::new(*chunk_pda, false));
}

// But claim chunk_count = 5 (more than provided)
let instruction = Instruction {
program_id: crate::ID,
accounts: account_metas,
data: crate::instruction::AssembleAndUpdateClient {
target_height,
chunk_count: 5, // Claiming 5 chunks but only 2 provided!
trusted_height,
}
.data(),
};

let (_, access_manager_account) =
crate::test_helpers::access_control::create_access_manager_account(
submitter,
vec![submitter],
);

let mut accounts = vec![
(app_state_pda, app_state_account),
(access_manager_pda, access_manager_account),
(
client_state_pda,
create_client_state_account(chain_id, trusted_height),
),
(
trusted_consensus_pda,
create_consensus_state_account([0; 32], [0; 32], 0),
),
(consensus_state_pda, Account::default()),
(submitter, create_submitter_account(10_000_000_000)),
keyed_account_for_system_program(),
];

// Add only 2 chunk accounts
for (i, chunk_pda) in chunk_pdas.iter().enumerate() {
accounts.push((*chunk_pda, create_chunk_account(chunks[i].clone())));
}

// Add Clock sysvar
accounts.push(create_clock_account(0));

// Add instructions sysvar
accounts.push((
anchor_lang::solana_program::sysvar::instructions::ID,
crate::test_helpers::create_instructions_sysvar_account(),
));

let result = mollusk.process_instruction(&instruction, &accounts);

// Should fail with InvalidChunkCount because chunk_count (5) > remaining_accounts.len() (2)
assert_error_code(
result,
ErrorCode::InvalidChunkCount,
"chunk_count exceeds remaining_accounts",
);
}

/// Test that `chunk_count` = 0 fails with `InvalidChunkCount`
#[test]
fn test_assemble_zero_chunk_count_rejected() {
let mollusk = setup_mollusk();

let chain_id = "test-chain";
let target_height = 100u64;
let trusted_height = 90u64;
let submitter = Pubkey::new_unique();

let client_state_pda = derive_client_state_pda();
let (consensus_state_pda, _) = Pubkey::find_program_address(
&[
crate::state::ConsensusStateStore::SEED,
&target_height.to_le_bytes(),
],
&crate::ID,
);

let (access_manager_pda, _) =
solana_ibc_types::access_manager::AccessManager::pda(access_manager::ID);
let (app_state_pda, app_state_account) = create_app_state_account(access_manager::ID);

let trusted_consensus_pda = derive_consensus_state_pda(trusted_height);

// Create instruction with chunk_count = 0
let account_metas = vec![
AccountMeta::new(client_state_pda, false),
AccountMeta::new_readonly(app_state_pda, false),
AccountMeta::new_readonly(access_manager_pda, false),
AccountMeta::new_readonly(trusted_consensus_pda, false),
AccountMeta::new(consensus_state_pda, false),
AccountMeta::new(submitter, true),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(anchor_lang::solana_program::sysvar::instructions::ID, false),
];

let instruction = Instruction {
program_id: crate::ID,
accounts: account_metas,
data: crate::instruction::AssembleAndUpdateClient {
target_height,
chunk_count: 0, // Zero chunks - invalid
trusted_height,
}
.data(),
};

let (_, access_manager_account) =
crate::test_helpers::access_control::create_access_manager_account(
submitter,
vec![submitter],
);

let mut accounts = vec![
(app_state_pda, app_state_account),
(access_manager_pda, access_manager_account),
(
client_state_pda,
create_client_state_account(chain_id, trusted_height),
),
(
trusted_consensus_pda,
create_consensus_state_account([0; 32], [0; 32], 0),
),
(consensus_state_pda, Account::default()),
(submitter, create_submitter_account(10_000_000_000)),
keyed_account_for_system_program(),
];

// Add Clock sysvar
accounts.push(create_clock_account(0));

// Add instructions sysvar
accounts.push((
anchor_lang::solana_program::sysvar::instructions::ID,
crate::test_helpers::create_instructions_sysvar_account(),
));

let result = mollusk.process_instruction(&instruction, &accounts);

// Should fail immediately with InvalidChunkCount because chunk_count must be > 0
assert_error_code(result, ErrorCode::InvalidChunkCount, "zero chunks rejected");
}

/// Test that header with invalid cryptographic proof fails during `update_client`
/// and triggers `UpdateClientFailed` error
#[test]
Expand Down
Loading