Skip to content
Open
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
6 changes: 6 additions & 0 deletions programs/svm-spoke/V5_ADAPTER_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ For the configured Spoke program, `v5_deposit_delegate` derives
`8DWnJFMBTSDYWsUUSqna9tx9LJbU1yUfq7jTiPJDf8sX` with bump 252. Builders must use this PDA as both the approval
target and the supplied deposit delegate account.

The V5-specific [transfer helper](src/v5/transfer.rs) selects the deposit or fill delegate's constant address, seed and
bump as one internal combination. It checks the supplied authority against that address and signs the token CPI with
its canonical seeds, without searching for the PDA again. Both combinations are tested against derivation under the
configured Spoke program ID; program-ID changes must update those constants together. No caller-supplied bump is
accepted.

The `v5_fill_delegate` PDA derives `D27f3mVXRL6N3bgja49UWLQu7kt57sy1aZYy7ZEwdxn1` with bump 252. Builders must use it
as both the approval target and the supplied fill delegate account for every fill, including self-transfers.
Both deposits and fills rely on the token program to validate delegate authority and allowance during
Expand Down
10 changes: 4 additions & 6 deletions programs/svm-spoke/src/instructions/v5_adapter/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ use anchor_spl::{
};

use crate::{
constants::{
GATEWAY_PROGRAM_ID, MAX_EXCLUSIVITY_PERIOD_SECONDS, V5_DEPOSIT_DELEGATE, V5_DEPOSIT_DELEGATE_SEED,
V5_MAGIC_PREFIX,
},
constants::{GATEWAY_PROGRAM_ID, MAX_EXCLUSIVITY_PERIOD_SECONDS, V5_DEPOSIT_DELEGATE, V5_MAGIC_PREFIX},
error::CommonError,
event::FundsDeposited,
utils::{get_current_time, transfer_from},
utils::get_current_time,
v5::{
accounts::find_v5_account,
codec::{decode_strict, resolve_v5_input_amount, AcrossDepositInput, GatewayContextV1},
jit::{derive_v5_deposit_id, resolve_v5_deposit_modifications},
transfer::{transfer_from, V5TransferDelegate},
},
};

Expand Down Expand Up @@ -65,7 +63,7 @@ pub(super) fn execute_v5_deposit<'info>(
accounts.token_program,
input_amount,
accounts.mint_decimals,
V5_DEPOSIT_DELEGATE_SEED,
V5TransferDelegate::Deposit,
)?;

emit_cpi!(FundsDeposited {
Expand Down
7 changes: 4 additions & 3 deletions programs/svm-spoke/src/instructions/v5_adapter/fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ use anchor_lang::{prelude::*, solana_program::keccak};
use anchor_spl::{associated_token::get_associated_token_address_with_program_id, token_interface::TransferChecked};

use crate::{
constants::{V5_FILL_DELEGATE, V5_FILL_DELEGATE_SEED, V5_MAGIC_PREFIX},
constants::{V5_FILL_DELEGATE, V5_MAGIC_PREFIX},
error::{CommonError, V5Error},
event::{FillType, FilledRelay, RelayExecutionEventInfo},
utils::{get_current_time, get_relay_hash, transfer_from},
utils::{get_current_time, get_relay_hash},
v5::{
accounts::find_v5_account,
codec::{decode_strict, GatewayContextV1, V5FillInput, V5FillJit},
fill_status::{create_v5_fill_status_account, V5FillStatusPdas},
transfer::{transfer_from, V5TransferDelegate},
},
};

Expand Down Expand Up @@ -72,7 +73,7 @@ pub(super) fn execute_v5_fill<'info>(
accounts.token_program,
relay.output_amount,
accounts.mint_decimals,
V5_FILL_DELEGATE_SEED,
V5TransferDelegate::Fill,
)?;

// Update the fill status and rent-reclaim metadata; V5 stores its payer PDA as the rent recipient.
Expand Down
2 changes: 0 additions & 2 deletions programs/svm-spoke/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ pub mod bitmap_utils;
pub mod cctp_utils;
pub mod merkle_proof_utils;
pub mod testable_utils;
pub mod transfer_utils;

pub use bitmap_utils::*;
pub use cctp_utils::*;
pub use merkle_proof_utils::*;
pub use testable_utils::*;
pub use transfer_utils::*;
22 changes: 0 additions & 22 deletions programs/svm-spoke/src/utils/transfer_utils.rs

This file was deleted.

3 changes: 2 additions & 1 deletion programs/svm-spoke/src/v5/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Wire, cryptographic, account-validation, and fill-status foundations for the Gateway-facing V5 adapter.
//! Wire, cryptographic, account-validation, transfer, and fill-status foundations for the Gateway-facing V5 adapter.
//!
//! `V5` identifies the Across protocol generation, while `V1` on a context or input variant identifies that
//! structure's SVM wire-schema revision. Those schemas evolve independently: append a new input variant for a changed
Expand All @@ -8,6 +8,7 @@ pub mod accounts;
pub mod codec;
pub mod fill_status;
pub mod jit;
pub(crate) mod transfer;

#[cfg(test)]
mod tests;
82 changes: 82 additions & 0 deletions programs/svm-spoke/src/v5/transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! Transfers authorized by the V5 adapter's fixed deposit and fill delegates.

use crate::{
constants::{
V5_DEPOSIT_DELEGATE, V5_DEPOSIT_DELEGATE_BUMP, V5_DEPOSIT_DELEGATE_SEED, V5_FILL_DELEGATE,
V5_FILL_DELEGATE_BUMP, V5_FILL_DELEGATE_SEED,
},
error::SvmError,
};
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{transfer_checked, TransferChecked};

/// Select a trusted address/seed/bump combination; callers cannot mix delegate components.
#[derive(Clone, Copy)]
pub(crate) enum V5TransferDelegate {
Deposit,
Fill,
}

impl V5TransferDelegate {
fn pda(self) -> (Pubkey, &'static [u8], u8) {
match self {
Self::Deposit => (V5_DEPOSIT_DELEGATE, V5_DEPOSIT_DELEGATE_SEED, V5_DEPOSIT_DELEGATE_BUMP),
Self::Fill => (V5_FILL_DELEGATE, V5_FILL_DELEGATE_SEED, V5_FILL_DELEGATE_BUMP),
}
}
}

pub(crate) fn transfer_from<'info>(
accounts: TransferChecked<'info>,
token_program: AccountInfo<'info>,
amount: u64,
mint_decimals: u8,
delegate: V5TransferDelegate,
) -> Result<()> {
let (delegate, delegate_seed, bump) = delegate.pda();
if delegate != accounts.authority.key() {
return err!(SvmError::InvalidDelegatePda);
}

let bump_seed = [bump];
let signer_seeds: &[&[u8]] = &[delegate_seed, &bump_seed];
let signer_seeds = [signer_seeds];

transfer_checked(CpiContext::new_with_signer(token_program, accounts, &signer_seeds), amount, mint_decimals)
}
Comment on lines +20 to +46

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What about something like

impl V5TransferDelegate {
    const fn address(self) -> Pubkey {
        match self {
            Self::Deposit => V5_DEPOSIT_DELEGATE,
            Self::Fill => V5_FILL_DELEGATE,
        }
    }

    const fn signer_seeds(self) -> [&'static [u8]; 2] {
        match self {
            Self::Deposit => [V5_DEPOSIT_DELEGATE_SEED, &[V5_DEPOSIT_DELEGATE_BUMP]],
            Self::Fill => [V5_FILL_DELEGATE_SEED, &[V5_FILL_DELEGATE_BUMP]],
        }
    }
}

pub(crate) fn transfer_from<'info>(
    accounts: TransferChecked<'info>,
    token_program: AccountInfo<'info>,
    amount: u64,
    mint_decimals: u8,
    delegate: V5TransferDelegate,
) -> Result<()> {
    require_keys_eq!(accounts.authority.key(), delegate.address(), SvmError::InvalidDelegatePda);
    transfer_checked(
        CpiContext::new_with_signer(token_program, accounts, &[&delegate.signer_seeds()]),
        amount,
        mint_decimals,
    )
}

Seems marginally cleaner


#[cfg(test)]
mod tests {
use super::*;

#[test]
fn transfer_delegate_variants_select_their_expected_pdas() {
assert_eq!(
V5TransferDelegate::Deposit.pda(),
(V5_DEPOSIT_DELEGATE, V5_DEPOSIT_DELEGATE_SEED, V5_DEPOSIT_DELEGATE_BUMP)
);
assert_eq!(V5TransferDelegate::Fill.pda(), (V5_FILL_DELEGATE, V5_FILL_DELEGATE_SEED, V5_FILL_DELEGATE_BUMP));
}

#[test]
fn wrong_transfer_authority_is_rejected_before_cpi() {
for delegate in [V5TransferDelegate::Deposit, V5TransferDelegate::Fill] {
// Include the other canonical delegate: a valid Spoke PDA is insufficient for the wrong operation.
for authority in [Pubkey::new_unique(), V5_DEPOSIT_DELEGATE, V5_FILL_DELEGATE] {
if authority == delegate.pda().0 {
continue;
}
let mut lamports = 0;
let owner = Pubkey::default();
let info = AccountInfo::new(&authority, false, false, &mut lamports, &mut [], &owner, false, 0);
let accounts = TransferChecked {
from: info.clone(),
mint: info.clone(),
to: info.clone(),
authority: info.clone(),
};
assert_eq!(transfer_from(accounts, info, 1, 6, delegate), Err(error!(SvmError::InvalidDelegatePda)));
}
}
}
}
10 changes: 7 additions & 3 deletions test/svm-gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ Every recorded bump is already canonical for its seeds; 255 simply means the fir
| ---------------- | ------------------------: | ------------------------: | --------------------: |
| legacy-deposit | 34,707 | 31,707 | 32,017 |
| legacy-fill | 41,021 | 38,021 | 38,331 |
| v5-deposit | 73,516 | 67,516 | 80,155 |
| v5-external-fill | 84,564 | 78,564 | 90,893 |
| v5-inplace-fill | 91,841 | 87,341 | 99,670 |
| v5-deposit | 67,469 | 61,469 | 74,108 |
| v5-external-fill | 78,518 | 72,518 | 84,847 |
| v5-inplace-fill | 85,795 | 81,295 | 93,624 |

To reproduce the normalization from each `baseline.json` row, define `d(bump) = 255 - bump` and subtract 1,500 times
the following sum from `execution`:
Expand Down Expand Up @@ -121,6 +121,10 @@ different `SVM_CU_OUTPUT` directories and compare `measurements` and program/run
slots and temporary paths in raw receipts naturally vary. Before committing an updated snapshot, run
`yarn prettier --write test/svm-gateway/cu/baseline.json`.

For the constant-delegate optimization's per-fixture CU comparison, see
[PR #1573](https://github.com/across-protocol/contracts/pull/1573)
([issue #1569](https://github.com/across-protocol/contracts/issues/1569)).

## Conformance suite

Run `yarn test-svm-gateway`. It builds Gateway, PrefundedAdapter and AuthorityRequirementPlanner from `GATEWAY_COMMIT` in
Expand Down
Loading
Loading