Skip to content
Merged
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
8 changes: 6 additions & 2 deletions programs/svm-spoke/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ pub enum CustomError {
InvalidRemoteSender,
#[msg("Invalid Merkle proof!")]
InvalidMerkleProof,
#[msg("Account not found!")]
AccountNotFound,
#[msg("Fills are currently paused!")]
FillsArePaused,
#[msg("Invalid chain id!")]
Expand All @@ -77,4 +75,10 @@ pub enum CustomError {
InvalidFillDeadline,
#[msg("Overflow writing to parameters account!")]
ParamsWriteOverflow,
#[msg("Invalid refund address!")]
InvalidRefund,
#[msg("Zero relayer refund claim!")]
ZeroRefundClaim,
#[msg("Invalid claim initializer!")]
InvalidClaimInitializer,
}
8 changes: 8 additions & 0 deletions programs/svm-spoke/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,13 @@ pub struct ExecutedRelayerRefundRoot {
pub leaf_id: u32,
pub l2_token_address: Pubkey,
pub refund_addresses: Vec<Pubkey>,
pub deferred_refunds: bool, // TODO: update EVM implementation to add this field.
pub caller: Pubkey,
}

#[event]
pub struct ClaimedRelayerRefund {
pub l2_token_address: Pubkey,
pub claim_amount: u64,
pub refund_address: Pubkey,
}
72 changes: 47 additions & 25 deletions programs/svm-spoke/src/instructions/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
constants::DISCRIMINATOR_SIZE,
error::CustomError,
event::ExecutedRelayerRefundRoot,
state::{ExecuteRelayerRefundLeafParams, RootBundle, State, TransferLiability},
state::{ExecuteRelayerRefundLeafParams, RefundAccount, RootBundle, State, TransferLiability},
utils::{is_claimed, set_claimed, verify_merkle_proof},
};

Expand Down Expand Up @@ -104,9 +104,12 @@ impl RelayerRefundLeaf {
}
}

pub fn execute_relayer_refund_leaf<'info>(
ctx: Context<'_, '_, '_, 'info, ExecuteRelayerRefundLeaf<'info>>,
) -> Result<()> {
pub fn execute_relayer_refund_leaf<'c, 'info>(
ctx: Context<'_, '_, 'c, 'info, ExecuteRelayerRefundLeaf<'info>>,
) -> Result<()>
where
'c: 'info,
{
Comment on lines +110 to +112
Copy link
Member

Choose a reason for hiding this comment

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

can you help me understand this bit of syntax a bit better? I also see it in the lib.rs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added the constraint that lifetime for remaining accounts ('c) is at least as long as 'info for accounts that are referenced by this account array. I think its needed because of manual remaining account processing in try_from_remaining_account method. Without these lifetime constraints the compiler errors.

Copy link
Member

Choose a reason for hiding this comment

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

o that's interesting. ye, I've had other lifetime issues while implementing other things and was able to address it through other syntax but this is more complex here.

// Get pre-loaded instruction parameters.
let instruction_params = &ctx.accounts.instruction_params;
let root_bundle_id = instruction_params.root_bundle_id;
Expand Down Expand Up @@ -148,30 +151,48 @@ pub fn execute_relayer_refund_leaf<'info>(
let seeds = &[b"state", state_seed_bytes.as_ref(), &[ctx.bumps.state]];
let signer_seeds = &[&seeds[..]];

// Will include in the emitted event at the end if there are any claim accounts.
let mut deferred_refunds = false;

for (i, amount) in relayer_refund_leaf.refund_amounts.iter().enumerate() {
let refund_account = relayer_refund_leaf.refund_accounts[i];
let amount = *amount as u64;

// 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.
let refund_account_info = ctx
.remaining_accounts
.iter()
.find(|account| account.key == &refund_account)
.cloned()
.ok_or(CustomError::AccountNotFound)?;

let transfer_accounts = TransferChecked {
from: ctx.accounts.vault.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
to: refund_account_info.to_account_info(),
authority: ctx.accounts.state.to_account_info(),
};
let cpi_context = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
signer_seeds,
);
transfer_checked(cpi_context, amount, ctx.accounts.mint.decimals)?;
// Refund account holds either a regular token account or a claim account. This checks all required constraints.
let refund_account = RefundAccount::try_from_remaining_account(
ctx.remaining_accounts,
i,
&relayer_refund_leaf.refund_accounts[i],
&ctx.accounts.mint.key(),
&ctx.accounts.token_program.key(),
)?;

match refund_account {
// Valid token account was passed, transfer the refund atomically.
RefundAccount::TokenAccount(token_account) => {
let transfer_accounts = TransferChecked {
from: ctx.accounts.vault.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
to: token_account.to_account_info(),
authority: ctx.accounts.state.to_account_info(),
};
let cpi_context = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
signer_seeds,
);
transfer_checked(cpi_context, amount, ctx.accounts.mint.decimals)?;
}
// Valid claim account was passed, increment the claim account amount.
RefundAccount::ClaimAccount(mut claim_account) => {
claim_account.amount += amount;

// Indicate in the event at the end that some refunds have been deferred.
deferred_refunds = true;

// Persist the updated claim account (Anchor handles this only for static accounts).
claim_account.exit(ctx.program_id)?;
}
}
}

if relayer_refund_leaf.amount_to_return > 0 {
Expand All @@ -187,6 +208,7 @@ pub fn execute_relayer_refund_leaf<'info>(
leaf_id: relayer_refund_leaf.leaf_id,
l2_token_address: ctx.accounts.mint.key(),
refund_addresses: relayer_refund_leaf.refund_accounts,
deferred_refunds,
caller: ctx.accounts.signer.key(),
});

Expand Down
2 changes: 2 additions & 0 deletions programs/svm-spoke/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod deposit;
mod fill;
mod handle_receive_message;
mod instruction_params;
mod refund_claims;
mod slow_fill;
mod testable;
mod token_bridge;
Expand All @@ -14,6 +15,7 @@ pub use deposit::*;
pub use fill::*;
pub use handle_receive_message::*;
pub use instruction_params::*;
pub use refund_claims::*;
pub use slow_fill::*;
pub use testable::*;
pub use token_bridge::*;
128 changes: 128 additions & 0 deletions programs/svm-spoke/src/instructions/refund_claims.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{
transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked,
};

use crate::{
constants::DISCRIMINATOR_SIZE,
error::CustomError,
event::ClaimedRelayerRefund,
state::{ClaimAccount, State},
};

#[derive(Accounts)]
#[instruction(mint: Pubkey, token_account: Pubkey)]
pub struct InitializeClaimAccount<'info> {
#[account(mut)]
pub signer: Signer<'info>,

#[account(
init,
payer = signer,
space = DISCRIMINATOR_SIZE + ClaimAccount::INIT_SPACE,
seeds = [b"claim_account", mint.as_ref(), token_account.as_ref()],
bump
)]
pub claim_account: Account<'info, ClaimAccount>,

pub system_program: Program<'info, System>,
}

pub fn initialize_claim_account(
ctx: Context<InitializeClaimAccount>,
mint: Pubkey,
token_account: Pubkey,
) -> Result<()> {
// Store the initializer so only it can receive lamports from closing the account upon claiming the refund.
ctx.accounts.claim_account.initializer = ctx.accounts.signer.key();

Ok(())
}

#[event_cpi]
#[derive(Accounts)]
pub struct ClaimRelayerRefund<'info> {
#[account(mut)]
pub signer: Signer<'info>,

/// CHECK: We don't need any additional checks as long as this is the same account that initialized the claim account.
#[account(
mut,
address = claim_account.initializer @ CustomError::InvalidClaimInitializer
)]
pub initializer: UncheckedAccount<'info>,

#[account(mut, seeds = [b"state", state.seed.to_le_bytes().as_ref()], bump)]
pub state: Account<'info, State>,

#[account(
mut,
associated_token::mint = mint,
associated_token::authority = state,
associated_token::token_program = token_program
)]
pub vault: InterfaceAccount<'info, TokenAccount>,

// Mint address has been checked when executing the relayer refund leaf and it is part of claim account derivation.
#[account(
mint::token_program = token_program,
)]
pub mint: InterfaceAccount<'info, Mint>,

// Token address has been checked when executing the relayer refund leaf and it is part of claim account derivation.
#[account(
mut,
token::mint = mint,
token::token_program = token_program
)]
pub token_account: InterfaceAccount<'info, TokenAccount>,

#[account(
mut,
close = initializer,
seeds = [b"claim_account", mint.key().as_ref(), token_account.key().as_ref()],
bump
)]
pub claim_account: Account<'info, ClaimAccount>,

pub token_program: Interface<'info, TokenInterface>,
}

pub fn claim_relayer_refund(ctx: Context<ClaimRelayerRefund>) -> Result<()> {
// Ensure the claim account holds a non-zero amount.
let claim_amount = ctx.accounts.claim_account.amount;
if claim_amount == 0 {
return err!(CustomError::ZeroRefundClaim);
}

// Reset the claim amount.
ctx.accounts.claim_account.amount = 0;

// Derive the signer seeds for the state required for the transfer form vault.
let state_seed_bytes = ctx.accounts.state.seed.to_le_bytes();
let seeds = &[b"state", state_seed_bytes.as_ref(), &[ctx.bumps.state]];
let signer_seeds = &[&seeds[..]];

// Transfer the claim amount from the vault to the relayer token account.
let transfer_accounts = TransferChecked {
from: ctx.accounts.vault.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.state.to_account_info(),
};
let cpi_context = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
signer_seeds,
);
transfer_checked(cpi_context, claim_amount, ctx.accounts.mint.decimals)?;

// Emit the ClaimedRelayerRefund event.
emit_cpi!(ClaimedRelayerRefund {
l2_token_address: ctx.accounts.mint.key(),
claim_amount,
refund_address: ctx.accounts.token_account.key(),
});

Ok(())
}
21 changes: 18 additions & 3 deletions programs/svm-spoke/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@ pub mod svm_spoke {
instructions::relay_root_bundle(ctx, relayer_refund_root, slow_relay_root)
}

pub fn execute_relayer_refund_leaf<'info>(
ctx: Context<'_, '_, '_, 'info, ExecuteRelayerRefundLeaf<'info>>,
) -> Result<()> {
pub fn execute_relayer_refund_leaf<'c, 'info>(
ctx: Context<'_, '_, 'c, 'info, ExecuteRelayerRefundLeaf<'info>>,
) -> Result<()>
where
'c: 'info,
{
instructions::execute_relayer_refund_leaf(ctx)
}

Expand Down Expand Up @@ -204,4 +207,16 @@ pub mod svm_spoke {
pub fn close_instruction_params(ctx: Context<CloseInstructionParams>) -> Result<()> {
instructions::close_instruction_params(ctx)
}

pub fn initialize_claim_account(
ctx: Context<InitializeClaimAccount>,
mint: Pubkey,
token_account: Pubkey,
) -> Result<()> {
instructions::initialize_claim_account(ctx, mint, token_account)
}

pub fn claim_relayer_refund(ctx: Context<ClaimRelayerRefund>) -> Result<()> {
instructions::claim_relayer_refund(ctx)
}
}
2 changes: 2 additions & 0 deletions programs/svm-spoke/src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
pub mod fill;
pub mod instruction_params;
pub mod refund_account;
pub mod root_bundle;
pub mod route;
pub mod state;
pub mod transfer_liability;

pub use fill::*;
pub use instruction_params::*;
pub use refund_account::*;
pub use root_bundle::*;
pub use route::*;
pub use state::*;
Expand Down
Loading
Loading