Skip to content

Commit 6202968

Browse files
authored
fix[c-01]: reconstruct destination message from source and params (#75)
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
1 parent b9dd893 commit 6202968

File tree

6 files changed

+99
-9
lines changed

6 files changed

+99
-9
lines changed

programs/sponsored-cctp-src-periphery/src/error.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,14 @@ pub enum SvmError {
3737
#[msg("Invalid recipient key")]
3838
InvalidRecipientKey,
3939
}
40+
41+
// CCTP BurnMessageV2 specific errors.
42+
#[error_code]
43+
pub enum CctpBurnMessageV2Error {
44+
#[msg("Malformed V2 burn message")]
45+
MalformedMessage,
46+
#[msg("Invalid message version")]
47+
InvalidMessageVersion,
48+
#[msg("Invalid message body version")]
49+
InvalidMessageBodyVersion,
50+
}

programs/sponsored-cctp-src-periphery/src/instructions/deposit.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use anchor_lang::{prelude::*, system_program};
22
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
33

4-
pub use crate::message_transmitter_v2::types::ReclaimEventAccountParams;
54
use crate::{
65
error::{CommonError, SvmError},
76
event::{AccruedRentFundLiability, CreatedEventAccount, SponsoredDepositForBurn},

programs/sponsored-cctp-src-periphery/src/instructions/maintenance.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use anchor_lang::{prelude::*, system_program};
22

3-
pub use crate::message_transmitter_v2::types::ReclaimEventAccountParams;
3+
pub use crate::message_transmitter_v2::types::ReclaimEventAccountParams as ReclaimEventAccountCctpV2Params;
44
use crate::{
55
error::SvmError,
66
event::{ReclaimedEventAccount, ReclaimedUsedNonceAccount, RepaidRentFundDebt},
7-
message_transmitter_v2::{self, program::MessageTransmitterV2},
7+
message_transmitter_v2::{self, accounts::MessageSent, program::MessageTransmitterV2},
88
state::{RentClaim, State, UsedNonce},
9-
utils::get_current_time,
9+
utils::{build_destination_message, get_current_time},
1010
};
1111

1212
#[event_cpi]
@@ -79,14 +79,26 @@ pub struct ReclaimEventAccount<'info> {
7979
#[account(mut)]
8080
pub message_transmitter: UncheckedAccount<'info>,
8181

82-
/// CHECK: MessageSent is checked in CCTP, must be the same account as in DepositForBurn.
8382
#[account(mut)]
84-
pub message_sent_event_data: UncheckedAccount<'info>,
83+
pub message_sent_event_data: Account<'info, MessageSent>,
8584

8685
pub message_transmitter_program: Program<'info, MessageTransmitterV2>,
8786
}
8887

88+
#[derive(AnchorSerialize, AnchorDeserialize)]
89+
pub struct ReclaimEventAccountParams {
90+
pub attestation: Vec<u8>,
91+
pub nonce: [u8; 32],
92+
pub finality_threshold_executed: [u8; 4],
93+
pub fee_executed: [u8; 32],
94+
pub expiration_block: [u8; 32],
95+
}
96+
8997
pub fn reclaim_event_account(ctx: Context<ReclaimEventAccount>, params: &ReclaimEventAccountParams) -> Result<()> {
98+
let destination_message = build_destination_message(&ctx.accounts.message_sent_event_data.message, params)?;
99+
let cctp_v2_params =
100+
ReclaimEventAccountCctpV2Params { attestation: params.attestation.clone(), destination_message };
101+
90102
let cpi_program = ctx.accounts.message_transmitter_program.to_account_info();
91103
let cpi_accounts = message_transmitter_v2::cpi::accounts::ReclaimEventAccount {
92104
payee: ctx.accounts.rent_fund.to_account_info(),
@@ -95,7 +107,7 @@ pub fn reclaim_event_account(ctx: Context<ReclaimEventAccount>, params: &Reclaim
95107
};
96108
let rent_fund_seeds: &[&[&[u8]]] = &[&[b"rent_fund", &[ctx.bumps.rent_fund]]];
97109
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, rent_fund_seeds);
98-
message_transmitter_v2::cpi::reclaim_event_account(cpi_ctx, params.clone())?;
110+
message_transmitter_v2::cpi::reclaim_event_account(cpi_ctx, cctp_v2_params)?;
99111

100112
emit_cpi!(ReclaimedEventAccount { message_sent_event_data: ctx.accounts.message_sent_event_data.key() });
101113

programs/sponsored-cctp-src-periphery/src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,16 @@ pub mod sponsored_cctp_src_periphery {
198198
/// Required Accounts:
199199
/// - rent_fund (SystemAccount, Writable): PDA to receive reclaimed lamports. Seed: ["rent_fund"].
200200
/// - message_transmitter (Unchecked, Mutable): CCTP MessageTransmitter account.
201-
/// - message_sent_event_data (Unchecked, Mutable): The event account created during `deposit_for_burn`.
201+
/// - message_sent_event_data (Account, Mutable): The `MessageSent` event account created during `deposit_for_burn`.
202202
/// - message_transmitter_program (Program): CCTPv2 MessageTransmitter program.
203203
///
204204
/// Parameters:
205-
/// - params: Parameters required by CCTP to reclaim the event account.
205+
/// - params: Parameters struct required to construct reclaim_event_account instruction on the CCTPv2.
206+
/// - attestation: Attestation obtained from the CCTP attestation service.
207+
/// - nonce: bytes32 from the attested destination message.
208+
/// - finality_threshold_executed: uint32 BE encoded from the attested destination message.
209+
/// - fee_executed: uint256 BE encoded from the attested destination message body.
210+
/// - expiration_block: uint256 BE encoded from the attested destination message body.
206211
///
207212
/// Notes:
208213
/// - This can only be called after the CCTP attestation service has processed the message and sufficient time has
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use anchor_lang::prelude::*;
2+
3+
use crate::{error::CctpBurnMessageV2Error, instructions::ReclaimEventAccountParams};
4+
5+
// Required CCTPv2 message constants from https://developers.circle.com/cctp/technical-guide#message-header
6+
const VERSION_INDEX: usize = 0;
7+
const VERSION_LEN: usize = 4; // uint32
8+
const SUPPORTED_VERSION: u32 = 1;
9+
const NONCE_INDEX: usize = 12;
10+
const NONCE_LEN: usize = 32; // bytes32
11+
const FINALITY_THRESHOLD_EXECUTED_INDEX: usize = 144;
12+
const FINALITY_THRESHOLD_EXECUTED_LEN: usize = 4; // uint32
13+
const MESSAGE_BODY_INDEX: usize = 148;
14+
15+
// Required CCTPv2 message body constants from https://developers.circle.com/cctp/technical-guide#message-body
16+
const BODY_VERSION_INDEX: usize = MESSAGE_BODY_INDEX + 0;
17+
const BODY_VERSION_LEN: usize = 4; // uint32
18+
const SUPPORTED_BODY_VERSION: u32 = 1;
19+
const FEE_EXECUTED_INDEX: usize = MESSAGE_BODY_INDEX + 164;
20+
const FEE_EXECUTED_LEN: usize = 32; // uint256
21+
const EXPIRATION_BLOCK_INDEX: usize = MESSAGE_BODY_INDEX + 196;
22+
const EXPIRATION_BLOCK_LEN: usize = 32; // uint256
23+
const HOOK_DATA_INDEX: usize = MESSAGE_BODY_INDEX + 228;
24+
25+
pub fn build_destination_message(source_message: &[u8], params: &ReclaimEventAccountParams) -> Result<Vec<u8>> {
26+
if source_message.len() < HOOK_DATA_INDEX {
27+
return err!(CctpBurnMessageV2Error::MalformedMessage);
28+
}
29+
30+
let message_version = u32::from_be_bytes(
31+
source_message[VERSION_INDEX..VERSION_INDEX + VERSION_LEN]
32+
.try_into()
33+
.unwrap(), // Safe as we check the length above.
34+
);
35+
if message_version != SUPPORTED_VERSION {
36+
return err!(CctpBurnMessageV2Error::InvalidMessageVersion);
37+
}
38+
39+
let message_body_version = u32::from_be_bytes(
40+
source_message[BODY_VERSION_INDEX..BODY_VERSION_INDEX + BODY_VERSION_LEN]
41+
.try_into()
42+
.unwrap(), // Safe as we check the length above.
43+
);
44+
if message_body_version != SUPPORTED_BODY_VERSION {
45+
return err!(CctpBurnMessageV2Error::InvalidMessageBodyVersion);
46+
}
47+
48+
let mut destination_message = source_message.to_vec();
49+
50+
// Overwrite parameters that are changed in the destination message.
51+
destination_message[NONCE_INDEX..NONCE_INDEX + NONCE_LEN].copy_from_slice(&params.nonce);
52+
destination_message
53+
[FINALITY_THRESHOLD_EXECUTED_INDEX..FINALITY_THRESHOLD_EXECUTED_INDEX + FINALITY_THRESHOLD_EXECUTED_LEN]
54+
.copy_from_slice(&params.finality_threshold_executed);
55+
destination_message[FEE_EXECUTED_INDEX..FEE_EXECUTED_INDEX + FEE_EXECUTED_LEN]
56+
.copy_from_slice(&params.fee_executed);
57+
destination_message[EXPIRATION_BLOCK_INDEX..EXPIRATION_BLOCK_INDEX + EXPIRATION_BLOCK_LEN]
58+
.copy_from_slice(&params.expiration_block);
59+
60+
Ok(destination_message)
61+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
pub mod cctp_v2_message;
12
pub mod signer;
23
pub mod sponsored_cctp_quote;
34
pub mod testable_utils;
45

6+
pub use cctp_v2_message::*;
57
pub use signer::*;
68
pub use sponsored_cctp_quote::*;
79
pub use testable_utils::*;

0 commit comments

Comments
 (0)