1use {
2 crate::{
3 error::ErrorCode,
4 state::*,
5 SubmitBid,
6 SubmitBidArgs,
7 },
8 anchor_lang::{
9 prelude::*,
10 solana_program::{
11 instruction::Instruction,
12 serialize_utils::read_u16,
13 sysvar::instructions::load_instruction_at_checked,
14 },
15 system_program::{
16 transfer,
17 Transfer,
18 },
19 Discriminator,
20 },
21};
22
23pub fn validate_fee_split(split: u64) -> Result<()> {
24 if split > FEE_SPLIT_PRECISION {
25 return err!(ErrorCode::FeeSplitLargerThanPrecision);
26 }
27 Ok(())
28}
29
30pub fn transfer_lamports(from: &AccountInfo, to: &AccountInfo, amount: u64) -> Result<()> {
31 **from.try_borrow_mut_lamports()? -= amount;
32 **to.try_borrow_mut_lamports()? += amount;
33 Ok(())
34}
35
36pub fn transfer_lamports_cpi<'info>(
37 from: &AccountInfo<'info>,
38 to: &AccountInfo<'info>,
39 amount: u64,
40 system_program: AccountInfo<'info>,
41) -> Result<()> {
42 let cpi_accounts = Transfer {
43 from: from.clone(),
44 to: to.clone(),
45 };
46
47 transfer(CpiContext::new(system_program, cpi_accounts), amount)?;
48
49 Ok(())
50}
51
52pub fn check_fee_hits_min_rent(account: &AccountInfo, fee: u64) -> Result<()> {
53 let balance = account.lamports();
54 let rent = Rent::get()?.minimum_balance(account.data_len());
55 if balance
56 .checked_add(fee)
57 .ok_or(ProgramError::ArithmeticOverflow)?
58 < rent
59 {
60 return err!(ErrorCode::InsufficientRent);
61 }
62
63 Ok(())
64}
65
66pub struct PermissionInfo<'info> {
67 pub permission: Pubkey,
68 pub router: Pubkey,
69 pub config_router: AccountInfo<'info>,
70 pub express_relay_metadata: AccountInfo<'info>,
71}
72
73pub fn get_matching_submit_bid_instructions(
76 sysvar_instructions: AccountInfo,
77 permission_info: Option<&PermissionInfo>,
78) -> Result<Vec<Instruction>> {
79 let num_instructions = read_u16(&mut 0, &sysvar_instructions.data.borrow())
80 .map_err(|_| ProgramError::InvalidInstructionData)?;
81 let mut matching_instructions = Vec::new();
82 for index in 0..num_instructions {
83 let ix = load_instruction_at_checked(index.into(), &sysvar_instructions)?;
84
85 if ix.program_id != crate::id() {
86 continue;
87 }
88 if &ix.data[0..8] != crate::instruction::SubmitBid::DISCRIMINATOR {
89 continue;
90 }
91
92 if let Some(permission_info) = permission_info {
93 if ix.accounts[2].pubkey != permission_info.permission {
94 continue;
95 }
96
97 if ix.accounts[3].pubkey != permission_info.router {
98 continue;
99 }
100
101 if ix.accounts[4].pubkey != permission_info.config_router.key() {
102 continue;
103 }
104
105 if ix.accounts[5].pubkey != permission_info.express_relay_metadata.key() {
106 continue;
107 }
108 }
109
110 matching_instructions.push(ix);
111 }
112
113 Ok(matching_instructions)
114}
115
116pub fn extract_bid_from_submit_bid_ix(submit_bid_ix: &Instruction) -> Result<u64> {
118 let submit_bid_args = SubmitBidArgs::try_from_slice(
119 &submit_bid_ix.data[crate::instruction::SubmitBid::DISCRIMINATOR.len()..],
120 )
121 .map_err(|_| ProgramError::BorshIoError("Failed to deserialize SubmitBidArgs".to_string()))?;
122 Ok(submit_bid_args.bid_amount)
123}
124
125fn perform_fee_split_router(bid_amount: u64, split_router: u64) -> Result<u64> {
127 let fee_router = bid_amount
128 .checked_mul(split_router)
129 .ok_or(ProgramError::ArithmeticOverflow)?
130 / FEE_SPLIT_PRECISION;
131 if fee_router > bid_amount {
132 return err!(ErrorCode::FeesHigherThanBid);
134 }
135 Ok(fee_router)
136}
137
138pub fn perform_fee_splits(
141 bid_amount: u64,
142 split_router: u64,
143 split_relayer: u64,
144) -> Result<(u64, u64)> {
145 let fee_router = perform_fee_split_router(bid_amount, split_router)?;
146 let fee_relayer = bid_amount
149 .saturating_sub(fee_router)
150 .checked_mul(split_relayer)
151 .ok_or(ProgramError::ArithmeticOverflow)?
152 / FEE_SPLIT_PRECISION;
153
154 if fee_relayer
155 .checked_add(fee_router)
156 .ok_or(ProgramError::ArithmeticOverflow)?
157 > bid_amount
158 {
159 return err!(ErrorCode::FeesHigherThanBid);
161 }
162
163 Ok((fee_router, fee_relayer))
164}
165
166pub fn inspect_permissions_in_tx(
170 sysvar_instructions: UncheckedAccount,
171 permission_info: PermissionInfo,
172) -> Result<(u16, u64)> {
173 let matching_ixs = get_matching_submit_bid_instructions(
174 sysvar_instructions.to_account_info(),
175 Some(&permission_info),
176 )?;
177 let n_ixs = matching_ixs.len() as u16;
178
179 let mut total_fees = 0u64;
180 let data_config_router = &mut &**permission_info.config_router.try_borrow_data()?;
181 let split_router = match ConfigRouter::try_deserialize(data_config_router) {
182 Ok(config_router) => config_router.split,
183 Err(_) => {
184 let data_express_relay_metadata =
185 &mut &**permission_info.express_relay_metadata.try_borrow_data()?;
186 let express_relay_metadata =
187 ExpressRelayMetadata::try_deserialize(data_express_relay_metadata)
188 .map_err(|_| ProgramError::InvalidAccountData)?;
189 express_relay_metadata.split_router_default
190 }
191 };
192 for ix in matching_ixs {
193 let bid = extract_bid_from_submit_bid_ix(&ix)?;
194 total_fees = total_fees
195 .checked_add(perform_fee_split_router(bid, split_router)?)
196 .ok_or(ProgramError::ArithmeticOverflow)?;
197 }
198
199 Ok((n_ixs, total_fees))
200}
201
202pub fn handle_bid_payment(ctx: Context<SubmitBid>, bid_amount: u64) -> Result<()> {
203 let searcher = &ctx.accounts.searcher;
204 let rent_searcher = Rent::get()?.minimum_balance(searcher.to_account_info().data_len());
205 if bid_amount
206 .checked_add(rent_searcher)
207 .ok_or(ProgramError::ArithmeticOverflow)?
208 > searcher.lamports()
209 {
210 return err!(ErrorCode::InsufficientSearcherFunds);
211 }
212
213 let express_relay_metadata = &ctx.accounts.express_relay_metadata;
214 let split_relayer = express_relay_metadata.split_relayer;
215 let split_router_default = express_relay_metadata.split_router_default;
216
217 let config_router = &ctx.accounts.config_router;
218 let config_router_account_info = config_router.to_account_info();
219 let split_router: u64 = if config_router_account_info.data_len() > 0 {
222 let account_data = &mut &**config_router_account_info.try_borrow_data()?;
223 let config_router_data = ConfigRouter::try_deserialize(account_data)?;
224 config_router_data.split
225 } else {
226 split_router_default
227 };
228
229 let (fee_router, fee_relayer) = perform_fee_splits(bid_amount, split_router, split_relayer)?;
230
231 if fee_router > 0 {
232 check_fee_hits_min_rent(&ctx.accounts.router, fee_router)?;
233
234 transfer_lamports_cpi(
235 &searcher.to_account_info(),
236 &ctx.accounts.router.to_account_info(),
237 fee_router,
238 ctx.accounts.system_program.to_account_info(),
239 )?;
240 }
241 if fee_relayer > 0 {
242 check_fee_hits_min_rent(&ctx.accounts.fee_receiver_relayer, fee_relayer)?;
243
244 transfer_lamports_cpi(
245 &searcher.to_account_info(),
246 &ctx.accounts.fee_receiver_relayer.to_account_info(),
247 fee_relayer,
248 ctx.accounts.system_program.to_account_info(),
249 )?;
250 }
251
252 transfer_lamports_cpi(
253 &searcher.to_account_info(),
254 &express_relay_metadata.to_account_info(),
255 bid_amount
256 .saturating_sub(fee_router)
257 .saturating_sub(fee_relayer),
258 ctx.accounts.system_program.to_account_info(),
259 )?;
260
261 Ok(())
262}