Skip to content

Commit c338c54

Browse files
committed
Merge branch 'master' into svm-dev
2 parents 5a54ddb + 503332f commit c338c54

File tree

12 files changed

+1009
-164
lines changed

12 files changed

+1009
-164
lines changed

programs/svm-spoke/src/error.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ pub enum CustomError {
5353
InvalidRemoteSender,
5454
#[msg("Invalid Merkle proof!")]
5555
InvalidMerkleProof,
56-
#[msg("Account not found!")]
57-
AccountNotFound,
5856
#[msg("Fills are currently paused!")]
5957
FillsArePaused,
6058
#[msg("Invalid chain id!")]
@@ -63,6 +61,8 @@ pub enum CustomError {
6361
InvalidMint,
6462
#[msg("Leaf already claimed!")]
6563
ClaimedMerkleLeaf,
64+
#[msg("Invalid Merkle leaf!")]
65+
InvalidMerkleLeaf,
6666
#[msg("Exceeded pending bridge amount to HubPool!")]
6767
ExceededPendingBridgeAmount,
6868
#[msg("Deposits are currently paused!")]
@@ -75,4 +75,10 @@ pub enum CustomError {
7575
InvalidFillDeadline,
7676
#[msg("Overflow writing to parameters account!")]
7777
ParamsWriteOverflow,
78+
#[msg("Invalid refund address!")]
79+
InvalidRefund,
80+
#[msg("Zero relayer refund claim!")]
81+
ZeroRefundClaim,
82+
#[msg("Invalid claim initializer!")]
83+
InvalidClaimInitializer,
7884
}

programs/svm-spoke/src/event.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,13 @@ pub struct ExecutedRelayerRefundRoot {
103103
pub leaf_id: u32,
104104
pub l2_token_address: Pubkey,
105105
pub refund_addresses: Vec<Pubkey>,
106+
pub deferred_refunds: bool, // TODO: update EVM implementation to add this field.
106107
pub caller: Pubkey,
107108
}
109+
110+
#[event]
111+
pub struct ClaimedRelayerRefund {
112+
pub l2_token_address: Pubkey,
113+
pub claim_amount: u64,
114+
pub refund_address: Pubkey,
115+
}

programs/svm-spoke/src/instructions/bundle.rs

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
constants::DISCRIMINATOR_SIZE,
88
error::CustomError,
99
event::ExecutedRelayerRefundRoot,
10-
state::{ExecuteRelayerRefundLeafParams, RootBundle, State, TransferLiability},
10+
state::{ExecuteRelayerRefundLeafParams, RefundAccount, RootBundle, State, TransferLiability},
1111
utils::{is_claimed, set_claimed, verify_merkle_proof},
1212
};
1313

@@ -104,9 +104,12 @@ impl RelayerRefundLeaf {
104104
}
105105
}
106106

107-
pub fn execute_relayer_refund_leaf<'info>(
108-
ctx: Context<'_, '_, '_, 'info, ExecuteRelayerRefundLeaf<'info>>,
109-
) -> Result<()> {
107+
pub fn execute_relayer_refund_leaf<'c, 'info>(
108+
ctx: Context<'_, '_, 'c, 'info, ExecuteRelayerRefundLeaf<'info>>,
109+
) -> Result<()>
110+
where
111+
'c: 'info,
112+
{
110113
// Get pre-loaded instruction parameters.
111114
let instruction_params = &ctx.accounts.instruction_params;
112115
let root_bundle_id = instruction_params.root_bundle_id;
@@ -138,36 +141,58 @@ pub fn execute_relayer_refund_leaf<'info>(
138141
// TODO: execute remaining parts of leaf structure such as amountToReturn.
139142
// TODO: emit events.
140143

144+
if relayer_refund_leaf.refund_accounts.len() != relayer_refund_leaf.refund_amounts.len() {
145+
return err!(CustomError::InvalidMerkleLeaf);
146+
}
147+
141148
// Derive the signer seeds for the state. The vault owns the state PDA so we need to derive this to create the
142149
// signer seeds to execute the CPI transfer from the vault to the refund recipient.
143150
let state_seed_bytes = state.seed.to_le_bytes();
144151
let seeds = &[b"state", state_seed_bytes.as_ref(), &[ctx.bumps.state]];
145152
let signer_seeds = &[&seeds[..]];
146153

154+
// Will include in the emitted event at the end if there are any claim accounts.
155+
let mut deferred_refunds = false;
156+
147157
for (i, amount) in relayer_refund_leaf.refund_amounts.iter().enumerate() {
148-
let refund_account = relayer_refund_leaf.refund_accounts[i];
149158
let amount = *amount as u64;
150159

151-
// TODO: we might be able to just use the refund_account and improve this block but it's not clear yet if that's possible.
152-
let refund_account_info = ctx
153-
.remaining_accounts
154-
.iter()
155-
.find(|account| account.key == &refund_account)
156-
.cloned()
157-
.ok_or(CustomError::AccountNotFound)?;
158-
159-
let transfer_accounts = TransferChecked {
160-
from: ctx.accounts.vault.to_account_info(),
161-
mint: ctx.accounts.mint.to_account_info(),
162-
to: refund_account_info.to_account_info(),
163-
authority: ctx.accounts.state.to_account_info(),
164-
};
165-
let cpi_context = CpiContext::new_with_signer(
166-
ctx.accounts.token_program.to_account_info(),
167-
transfer_accounts,
168-
signer_seeds,
169-
);
170-
transfer_checked(cpi_context, amount, ctx.accounts.mint.decimals)?;
160+
// Refund account holds either a regular token account or a claim account. This checks all required constraints.
161+
let refund_account = RefundAccount::try_from_remaining_account(
162+
ctx.remaining_accounts,
163+
i,
164+
&relayer_refund_leaf.refund_accounts[i],
165+
&ctx.accounts.mint.key(),
166+
&ctx.accounts.token_program.key(),
167+
)?;
168+
169+
match refund_account {
170+
// Valid token account was passed, transfer the refund atomically.
171+
RefundAccount::TokenAccount(token_account) => {
172+
let transfer_accounts = TransferChecked {
173+
from: ctx.accounts.vault.to_account_info(),
174+
mint: ctx.accounts.mint.to_account_info(),
175+
to: token_account.to_account_info(),
176+
authority: ctx.accounts.state.to_account_info(),
177+
};
178+
let cpi_context = CpiContext::new_with_signer(
179+
ctx.accounts.token_program.to_account_info(),
180+
transfer_accounts,
181+
signer_seeds,
182+
);
183+
transfer_checked(cpi_context, amount, ctx.accounts.mint.decimals)?;
184+
}
185+
// Valid claim account was passed, increment the claim account amount.
186+
RefundAccount::ClaimAccount(mut claim_account) => {
187+
claim_account.amount += amount;
188+
189+
// Indicate in the event at the end that some refunds have been deferred.
190+
deferred_refunds = true;
191+
192+
// Persist the updated claim account (Anchor handles this only for static accounts).
193+
claim_account.exit(ctx.program_id)?;
194+
}
195+
}
171196
}
172197

173198
if relayer_refund_leaf.amount_to_return > 0 {
@@ -183,6 +208,7 @@ pub fn execute_relayer_refund_leaf<'info>(
183208
leaf_id: relayer_refund_leaf.leaf_id,
184209
l2_token_address: ctx.accounts.mint.key(),
185210
refund_addresses: relayer_refund_leaf.refund_accounts,
211+
deferred_refunds,
186212
caller: ctx.accounts.signer.key(),
187213
});
188214

programs/svm-spoke/src/instructions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod deposit;
44
mod fill;
55
mod handle_receive_message;
66
mod instruction_params;
7+
mod refund_claims;
78
mod slow_fill;
89
mod testable;
910
mod token_bridge;
@@ -14,6 +15,7 @@ pub use deposit::*;
1415
pub use fill::*;
1516
pub use handle_receive_message::*;
1617
pub use instruction_params::*;
18+
pub use refund_claims::*;
1719
pub use slow_fill::*;
1820
pub use testable::*;
1921
pub use token_bridge::*;
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use anchor_lang::prelude::*;
2+
use anchor_spl::token_interface::{
3+
transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked,
4+
};
5+
6+
use crate::{
7+
constants::DISCRIMINATOR_SIZE,
8+
error::CustomError,
9+
event::ClaimedRelayerRefund,
10+
state::{ClaimAccount, State},
11+
};
12+
13+
#[derive(Accounts)]
14+
#[instruction(mint: Pubkey, token_account: Pubkey)]
15+
pub struct InitializeClaimAccount<'info> {
16+
#[account(mut)]
17+
pub signer: Signer<'info>,
18+
19+
#[account(
20+
init,
21+
payer = signer,
22+
space = DISCRIMINATOR_SIZE + ClaimAccount::INIT_SPACE,
23+
seeds = [b"claim_account", mint.as_ref(), token_account.as_ref()],
24+
bump
25+
)]
26+
pub claim_account: Account<'info, ClaimAccount>,
27+
28+
pub system_program: Program<'info, System>,
29+
}
30+
31+
pub fn initialize_claim_account(
32+
ctx: Context<InitializeClaimAccount>,
33+
mint: Pubkey,
34+
token_account: Pubkey,
35+
) -> Result<()> {
36+
// Store the initializer so only it can receive lamports from closing the account upon claiming the refund.
37+
ctx.accounts.claim_account.initializer = ctx.accounts.signer.key();
38+
39+
Ok(())
40+
}
41+
42+
#[event_cpi]
43+
#[derive(Accounts)]
44+
pub struct ClaimRelayerRefund<'info> {
45+
#[account(mut)]
46+
pub signer: Signer<'info>,
47+
48+
/// CHECK: We don't need any additional checks as long as this is the same account that initialized the claim account.
49+
#[account(
50+
mut,
51+
address = claim_account.initializer @ CustomError::InvalidClaimInitializer
52+
)]
53+
pub initializer: UncheckedAccount<'info>,
54+
55+
#[account(mut, seeds = [b"state", state.seed.to_le_bytes().as_ref()], bump)]
56+
pub state: Account<'info, State>,
57+
58+
#[account(
59+
mut,
60+
associated_token::mint = mint,
61+
associated_token::authority = state,
62+
associated_token::token_program = token_program
63+
)]
64+
pub vault: InterfaceAccount<'info, TokenAccount>,
65+
66+
// Mint address has been checked when executing the relayer refund leaf and it is part of claim account derivation.
67+
#[account(
68+
mint::token_program = token_program,
69+
)]
70+
pub mint: InterfaceAccount<'info, Mint>,
71+
72+
// Token address has been checked when executing the relayer refund leaf and it is part of claim account derivation.
73+
#[account(
74+
mut,
75+
token::mint = mint,
76+
token::token_program = token_program
77+
)]
78+
pub token_account: InterfaceAccount<'info, TokenAccount>,
79+
80+
#[account(
81+
mut,
82+
close = initializer,
83+
seeds = [b"claim_account", mint.key().as_ref(), token_account.key().as_ref()],
84+
bump
85+
)]
86+
pub claim_account: Account<'info, ClaimAccount>,
87+
88+
pub token_program: Interface<'info, TokenInterface>,
89+
}
90+
91+
pub fn claim_relayer_refund(ctx: Context<ClaimRelayerRefund>) -> Result<()> {
92+
// Ensure the claim account holds a non-zero amount.
93+
let claim_amount = ctx.accounts.claim_account.amount;
94+
if claim_amount == 0 {
95+
return err!(CustomError::ZeroRefundClaim);
96+
}
97+
98+
// Reset the claim amount.
99+
ctx.accounts.claim_account.amount = 0;
100+
101+
// Derive the signer seeds for the state required for the transfer form vault.
102+
let state_seed_bytes = ctx.accounts.state.seed.to_le_bytes();
103+
let seeds = &[b"state", state_seed_bytes.as_ref(), &[ctx.bumps.state]];
104+
let signer_seeds = &[&seeds[..]];
105+
106+
// Transfer the claim amount from the vault to the relayer token account.
107+
let transfer_accounts = TransferChecked {
108+
from: ctx.accounts.vault.to_account_info(),
109+
mint: ctx.accounts.mint.to_account_info(),
110+
to: ctx.accounts.token_account.to_account_info(),
111+
authority: ctx.accounts.state.to_account_info(),
112+
};
113+
let cpi_context = CpiContext::new_with_signer(
114+
ctx.accounts.token_program.to_account_info(),
115+
transfer_accounts,
116+
signer_seeds,
117+
);
118+
transfer_checked(cpi_context, claim_amount, ctx.accounts.mint.decimals)?;
119+
120+
// Emit the ClaimedRelayerRefund event.
121+
emit_cpi!(ClaimedRelayerRefund {
122+
l2_token_address: ctx.accounts.mint.key(),
123+
claim_amount,
124+
refund_address: ctx.accounts.token_account.key(),
125+
});
126+
127+
Ok(())
128+
}

programs/svm-spoke/src/lib.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,12 @@ pub mod svm_spoke {
6060
instructions::relay_root_bundle(ctx, relayer_refund_root, slow_relay_root)
6161
}
6262

63-
pub fn execute_relayer_refund_leaf<'info>(
64-
ctx: Context<'_, '_, '_, 'info, ExecuteRelayerRefundLeaf<'info>>,
65-
) -> Result<()> {
63+
pub fn execute_relayer_refund_leaf<'c, 'info>(
64+
ctx: Context<'_, '_, 'c, 'info, ExecuteRelayerRefundLeaf<'info>>,
65+
) -> Result<()>
66+
where
67+
'c: 'info,
68+
{
6669
instructions::execute_relayer_refund_leaf(ctx)
6770
}
6871

@@ -204,4 +207,16 @@ pub mod svm_spoke {
204207
pub fn close_instruction_params(ctx: Context<CloseInstructionParams>) -> Result<()> {
205208
instructions::close_instruction_params(ctx)
206209
}
210+
211+
pub fn initialize_claim_account(
212+
ctx: Context<InitializeClaimAccount>,
213+
mint: Pubkey,
214+
token_account: Pubkey,
215+
) -> Result<()> {
216+
instructions::initialize_claim_account(ctx, mint, token_account)
217+
}
218+
219+
pub fn claim_relayer_refund(ctx: Context<ClaimRelayerRefund>) -> Result<()> {
220+
instructions::claim_relayer_refund(ctx)
221+
}
207222
}

programs/svm-spoke/src/state/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
pub mod fill;
22
pub mod instruction_params;
3+
pub mod refund_account;
34
pub mod root_bundle;
45
pub mod route;
56
pub mod state;
67
pub mod transfer_liability;
78

89
pub use fill::*;
910
pub use instruction_params::*;
11+
pub use refund_account::*;
1012
pub use root_bundle::*;
1113
pub use route::*;
1214
pub use state::*;

0 commit comments

Comments
 (0)