Skip to content

Commit 436cd08

Browse files
committed
solana: clean up place initial offer
update helpers and fix existing instructions
1 parent 851c533 commit 436cd08

File tree

9 files changed

+422
-431
lines changed

9 files changed

+422
-431
lines changed

solana/modules/matching-engine-testing/tests/test_scenarios/make_offer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ pub async fn test_place_initial_offer_fails_if_fast_market_order_not_created() {
364364
fast_market_order_address: OverwriteCurrentState::Some(fake_fast_market_order_address),
365365
expected_error: Some(ExpectedError {
366366
instruction_index: 0,
367-
error_code: u32::from(ErrorCode::ConstraintOwner),
367+
error_code: u32::from(ErrorCode::AccountDiscriminatorMismatch), // TODO: Revisit?
368368
error_string: "Fast market order account owner is invalid".to_string(),
369369
}),
370370
..PlaceInitialOfferInstructionConfig::default()

solana/programs/matching-engine/src/fallback/processor/close_fast_market_order.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anchor_lang::prelude::*;
22
use solana_program::instruction::Instruction;
33

4-
use crate::{error::MatchingEngineError, state::FastMarketOrder};
4+
use crate::error::MatchingEngineError;
55

66
pub struct CloseFastMarketOrderAccounts<'ix> {
77
/// The fast market order account to be closed.
@@ -43,13 +43,9 @@ pub fn process(accounts: &[AccountInfo]) -> Result<()> {
4343
// We need to check the refund recipient account against what we know as the
4444
// refund recipient encoded in the fast market order account.
4545
let fast_market_order_info = &accounts[0];
46-
let refund_recipient_info = &accounts[1];
47-
48-
let fast_market_order_data = &fast_market_order_info.data.borrow()[..];
46+
let fast_market_order = super::helpers::try_fast_market_order_account(fast_market_order_info)?;
4947

50-
// NOTE: We do not need to verify that the owner of this account is this
51-
// program because the lamport transfer will fail otherwise.
52-
let fast_market_order = FastMarketOrder::try_read(fast_market_order_data)?;
48+
let refund_recipient_info = &accounts[1];
5349

5450
// Check that the refund recipient provided in this instruction is the one
5551
// encoded in the fast market order account.

solana/programs/matching-engine/src/fallback/processor/execute_order.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,9 @@ pub fn handle_execute_order_shim(accounts: &[AccountInfo]) -> Result<()> {
195195
// Do checks
196196
// ------------------------------------------------------------------------------------------------
197197

198-
let fast_market_order_data = &fast_market_order_account.data.borrow()[..];
199-
let fast_market_order_zero_copy = FastMarketOrderState::try_read(fast_market_order_data)?;
198+
let fast_market_order_zero_copy =
199+
super::helpers::try_fast_market_order_account(fast_market_order_account)?;
200+
200201
// Bind value for compiler (needed for pda seeds)
201202
let active_auction_key = active_auction_account.key();
202203

@@ -222,7 +223,7 @@ pub fn handle_execute_order_shim(accounts: &[AccountInfo]) -> Result<()> {
222223
};
223224

224225
// Check custodian owner
225-
check_custodian_owner_is_program_id(custodian_account)?;
226+
super::helpers::require_owned_by_this_program(custodian_account, "custodian")?;
226227

227228
// Check custodian deserialises into a checked custodian account
228229
let _checked_custodian = Custodian::try_deserialize(&mut &custodian_account.data.borrow()[..])?;
@@ -471,7 +472,6 @@ pub fn handle_execute_order_shim(accounts: &[AccountInfo]) -> Result<()> {
471472
.saturating_add(active_auction_info.security_deposit)
472473
.saturating_sub(user_reward);
473474

474-
475475
let penalized = penalty > 0;
476476

477477
if penalized && active_auction_best_offer_token_account.key() != executor_token_account.key() {
@@ -503,7 +503,7 @@ pub fn handle_execute_order_shim(accounts: &[AccountInfo]) -> Result<()> {
503503
init_auction_fee,
504504
)
505505
.unwrap();
506-
506+
507507
invoke_signed_unchecked(&transfer_ix, accounts, &[auction_signer_seeds])?;
508508
// Because the initial offer token was paid this fee, we account for it here.
509509
remaining_custodied_amount =

solana/programs/matching-engine/src/fallback/processor/helpers.rs

Lines changed: 188 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
use anchor_lang::prelude::*;
1+
use std::cell::Ref;
2+
3+
use anchor_lang::{prelude::*, Discriminator};
24
use anchor_spl::token::spl_token;
35
use solana_program::{
46
entrypoint::ProgramResult,
57
instruction::{AccountMeta, Instruction},
8+
keccak,
69
program::invoke_signed_unchecked,
710
program_pack::Pack,
811
system_instruction,
912
};
1013

11-
use crate::ID;
14+
use crate::{
15+
error::MatchingEngineError,
16+
state::{AuctionConfig, Custodian, FastMarketOrder, MessageProtocol, RouterEndpoint},
17+
ID,
18+
};
1219

1320
#[inline(always)]
1421
pub fn require_min_account_infos_len(accounts: &[AccountInfo], at_least_len: usize) -> Result<()> {
@@ -20,11 +27,119 @@ pub fn require_min_account_infos_len(accounts: &[AccountInfo], at_least_len: usi
2027
}
2128

2229
#[inline(always)]
23-
pub fn check_custodian_owner_is_program_id(custodian: &AccountInfo) -> Result<()> {
24-
require_eq!(custodian.owner, &ID, ErrorCode::ConstraintOwner);
30+
pub fn require_owned_by_this_program(account: &AccountInfo, account_name: &str) -> Result<()> {
31+
if account.owner != &ID {
32+
return Err(ErrorCode::ConstraintOwner.into())
33+
.map_err(|e: Error| e.with_account_name(account_name));
34+
}
35+
2536
Ok(())
2637
}
2738

39+
#[inline(always)]
40+
pub fn try_custodian_account(
41+
custodian_info: &AccountInfo,
42+
check_if_paused: bool,
43+
) -> Result<Box<Custodian>> {
44+
super::helpers::require_owned_by_this_program(custodian_info, "custodian")?;
45+
46+
let custodian =
47+
Custodian::try_deserialize(&mut &custodian_info.data.borrow()[..]).map(Box::new)?;
48+
49+
// Make sure the custodian is not paused.
50+
if check_if_paused && custodian.paused {
51+
return Err(MatchingEngineError::Paused.into());
52+
}
53+
54+
Ok(custodian)
55+
}
56+
57+
#[inline(always)]
58+
pub fn try_auction_config_account(
59+
auction_config_info: &AccountInfo,
60+
expected_config_id: Option<u32>,
61+
) -> Result<Box<AuctionConfig>> {
62+
super::helpers::require_owned_by_this_program(auction_config_info, "auction_config")?;
63+
64+
let auction_config =
65+
AuctionConfig::try_deserialize(&mut &auction_config_info.data.borrow()[..])
66+
.map(Box::new)?;
67+
68+
// Make sure the custodian is not paused.
69+
if let Some(expected_config_id) = expected_config_id {
70+
if auction_config.id != expected_config_id {
71+
msg!("Auction config id is invalid");
72+
return Err(ErrorCode::ConstraintRaw.into())
73+
.map_err(|e: Error| e.with_account_name("auction_config"));
74+
}
75+
}
76+
77+
Ok(auction_config)
78+
}
79+
80+
#[inline(always)]
81+
pub fn try_live_endpoint_account(
82+
endpoint_info: &AccountInfo,
83+
endpoint_name: &str,
84+
) -> Result<Box<RouterEndpoint>> {
85+
super::helpers::require_owned_by_this_program(endpoint_info, endpoint_name)?;
86+
87+
let endpoint =
88+
RouterEndpoint::try_deserialize(&mut &endpoint_info.data.borrow()[..]).map(Box::new)?;
89+
90+
if endpoint.protocol == MessageProtocol::None {
91+
return Err(MatchingEngineError::EndpointDisabled.into());
92+
}
93+
94+
Ok(endpoint)
95+
}
96+
97+
#[inline(always)]
98+
pub fn try_live_endpoint_accounts_path(
99+
from_endpoint_info: &AccountInfo,
100+
to_endpoint_info: &AccountInfo,
101+
) -> Result<(Box<RouterEndpoint>, Box<RouterEndpoint>)> {
102+
let from_endpoint = try_live_endpoint_account(from_endpoint_info, "from_endpoint")?;
103+
let to_endpoint = try_live_endpoint_account(to_endpoint_info, "to_endpoint")?;
104+
105+
if from_endpoint.chain == to_endpoint.chain {
106+
return Err(MatchingEngineError::SameEndpoint.into());
107+
}
108+
109+
Ok((from_endpoint, to_endpoint))
110+
}
111+
112+
pub fn try_usdc_account<'a, 'b>(usdc_info: &'a AccountInfo<'b>) -> Result<&'a AccountInfo<'b>> {
113+
if usdc_info.key != &common::USDC_MINT {
114+
return Err(MatchingEngineError::InvalidMint.into())
115+
.map_err(|e: Error| e.with_account_name("usdc"));
116+
}
117+
118+
Ok(usdc_info)
119+
}
120+
121+
/// Read from an account info
122+
pub fn try_fast_market_order_account<'a>(
123+
fast_market_order_info: &'a AccountInfo,
124+
) -> Result<Ref<'a, FastMarketOrder>> {
125+
let data = fast_market_order_info.data.borrow();
126+
127+
if data.len() < 8 {
128+
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
129+
}
130+
131+
if &data[0..8] != &FastMarketOrder::DISCRIMINATOR {
132+
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
133+
}
134+
135+
// TODO: Move up?
136+
super::helpers::require_owned_by_this_program(fast_market_order_info, "fast_market_order")?;
137+
138+
Ok(Ref::map(data, |data| {
139+
bytemuck::from_bytes(&data[8..8 + std::mem::size_of::<FastMarketOrder>()])
140+
}))
141+
}
142+
28143
pub fn create_account_reliably(
29144
payer_key: &Pubkey,
30145
account_key: &Pubkey,
@@ -180,3 +295,72 @@ pub fn create_usdc_token_account_reliably(
180295

181296
solana_program::program::invoke_signed_unchecked(&init_token_account_ix, accounts, &[])
182297
}
298+
299+
/// VaaMessageBodyHeader for the digest calculation
300+
///
301+
/// This is the header of the vaa message body. It is used to calculate the
302+
/// digest of the fast market order.
303+
#[derive(Debug)]
304+
pub struct VaaMessageBodyHeader {
305+
pub consistency_level: u8,
306+
pub timestamp: u32,
307+
pub sequence: u64,
308+
pub emitter_chain: u16,
309+
pub emitter_address: [u8; 32],
310+
}
311+
312+
impl VaaMessageBodyHeader {
313+
// TODO: Remove
314+
pub fn new(
315+
consistency_level: u8,
316+
timestamp: u32,
317+
sequence: u64,
318+
emitter_chain: u16,
319+
emitter_address: [u8; 32],
320+
) -> Self {
321+
Self {
322+
consistency_level,
323+
timestamp,
324+
sequence,
325+
emitter_chain,
326+
emitter_address,
327+
}
328+
}
329+
330+
/// This function creates both the message body for the fast market order, including the payload.
331+
pub fn message_body(&self, fast_market_order: &FastMarketOrder) -> Vec<u8> {
332+
let mut message_body = vec![];
333+
message_body.extend_from_slice(&self.timestamp.to_be_bytes());
334+
message_body.extend_from_slice(&[0, 0, 0, 0]); // 0 nonce
335+
message_body.extend_from_slice(&self.emitter_chain.to_be_bytes());
336+
message_body.extend_from_slice(&self.emitter_address);
337+
message_body.extend_from_slice(&self.sequence.to_be_bytes());
338+
message_body.extend_from_slice(&[self.consistency_level]);
339+
message_body.push(11_u8);
340+
message_body.extend_from_slice(&fast_market_order.amount_in.to_be_bytes());
341+
message_body.extend_from_slice(&fast_market_order.min_amount_out.to_be_bytes());
342+
message_body.extend_from_slice(&fast_market_order.target_chain.to_be_bytes());
343+
message_body.extend_from_slice(&fast_market_order.redeemer);
344+
message_body.extend_from_slice(&fast_market_order.sender);
345+
message_body.extend_from_slice(&fast_market_order.refund_address);
346+
message_body.extend_from_slice(&fast_market_order.max_fee.to_be_bytes());
347+
message_body.extend_from_slice(&fast_market_order.init_auction_fee.to_be_bytes());
348+
message_body.extend_from_slice(&fast_market_order.deadline.to_be_bytes());
349+
message_body.extend_from_slice(&fast_market_order.redeemer_message_length.to_be_bytes());
350+
if fast_market_order.redeemer_message_length > 0 {
351+
message_body.extend_from_slice(
352+
&fast_market_order.redeemer_message
353+
[..usize::from(fast_market_order.redeemer_message_length)],
354+
);
355+
}
356+
message_body
357+
}
358+
359+
/// The digest is the hash of the message hash.
360+
pub fn digest(&self, fast_market_order: &FastMarketOrder) -> keccak::Hash {
361+
wormhole_svm_definitions::compute_keccak_digest(
362+
keccak::hashv(&[&self.message_body(fast_market_order)]),
363+
None,
364+
)
365+
}
366+
}

solana/programs/matching-engine/src/fallback/processor/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
pub mod burn_and_post;
22
pub mod close_fast_market_order;
3+
// TODO: Rename module to "execute_order_cctp".
34
pub mod execute_order;
45
pub mod helpers;
56
pub mod initialize_fast_market_order;
7+
// TODO: Rename module to "place_initial_offer_cctp".
68
pub mod place_initial_offer;
79
pub mod prepare_order_response;
810
pub mod process_instruction;

0 commit comments

Comments
 (0)