Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 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 @@ -75,4 +73,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,
}
15 changes: 15 additions & 0 deletions programs/svm-spoke/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,18 @@ pub struct ExecutedRelayerRefundRoot {
pub refund_addresses: Vec<Pubkey>,
pub caller: Pubkey,
}

#[event]
pub struct DeferredRelayerRefunds {
pub chain_id: u64,
pub root_bundle_id: u32,
pub leaf_id: u32,
pub l2_token_address: Pubkey,
}

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

Expand Down Expand Up @@ -106,9 +106,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 @@ -146,30 +149,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 emit 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;

// Emit DeferredRelayerRefunds event at the end.
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 @@ -188,5 +209,16 @@ pub fn execute_relayer_refund_leaf<'info>(
caller: ctx.accounts.signer.key(),
});

// Emit the DeferredRelayerRefunds event if any claim accounts were passed instead of token accounts. We cannot emit
// individual accounts and amounts as that would run out of memory for larger leaves.
if deferred_refunds {
emit_cpi!(DeferredRelayerRefunds {
Copy link
Member

Choose a reason for hiding this comment

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

I think it's ok to emit all this but it's not really needed, right? we really just want to know that it happened, but care less about all the other props give we have ExecutedRelayerRefundRoot.

an alternative structure could simply be to add a bool to ExecutedRelayerRefundRoot that indicates if deferred_refunds is set so we don't need a. new event?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, if we can change interface of ExecutedRelayerRefundRoot this would be much easier to avoid separate emit_cpi

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pushed the commit to include the bool in ExecutedRelayerRefundRoot

chain_id: relayer_refund_leaf.chain_id,
root_bundle_id,
leaf_id: relayer_refund_leaf.leaf_id,
l2_token_address: ctx.accounts.mint.key(),
});
}

Ok(())
}
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