Skip to content
Draft
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@across-protocol/contracts",
"version": "5.0.11",
"version": "5.0.12-beta.0",
"author": "UMA Team",
"license": "AGPL-3.0-only",
"repository": {
Expand Down
11 changes: 11 additions & 0 deletions programs/svm-spoke/src/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,14 @@ pub fn is_valid_associated_token_account(
&& token_account.key()
== get_associated_token_address_with_program_id(authority, &mint.key(), &token_program.key())
}

// Validates if the optional parameter presence is consistent with each other and counter to the instruction_params
// account presence.
pub fn has_valid_params_presence(params_presence: &[bool], account_present: bool) -> bool {
let Some((&first_present, remaining_presence)) = params_presence.split_first() else {
return false;
};

// All remaining param presence must match the first param presence and the account presence must not match it.
remaining_presence.iter().all(|&p| p == first_present) && account_present != first_present
}
2 changes: 2 additions & 0 deletions programs/svm-spoke/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ pub enum SvmError {
InvalidATACreationAccounts,
#[msg("Invalid delegate PDA!")]
InvalidDelegatePda,
#[msg("Inconsistent optional parameters!")]
InconsistentOptionalParameters,
}

// CCTP specific errors.
Expand Down
15 changes: 12 additions & 3 deletions programs/svm-spoke/src/instructions/fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use anchor_spl::{
use crate::{
common::RelayData,
constants::DISCRIMINATOR_SIZE,
constraints::is_relay_hash_valid,
constraints::{has_valid_params_presence, is_relay_hash_valid},
error::{CommonError, SvmError},
event::{FillType, FilledRelay, RelayExecutionEventInfo},
state::{FillRelayParams, FillStatus, FillStatusAccount, State},
Expand All @@ -16,7 +16,12 @@ use crate::{

#[event_cpi]
#[derive(Accounts)]
#[instruction(relay_hash: [u8; 32], relay_data: Option<RelayData>)]
#[instruction(
relay_hash: [u8; 32],
relay_data: Option<RelayData>,
repayment_chain_id: Option<u64>,
repayment_address: Option<Pubkey>
)]
pub struct FillRelay<'info> {
#[account(mut)]
pub signer: Signer<'info>,
Expand Down Expand Up @@ -66,6 +71,10 @@ pub struct FillRelay<'info> {
space = DISCRIMINATOR_SIZE + FillStatusAccount::INIT_SPACE,
seeds = [b"fills", relay_hash.as_ref()],
bump,
// Validate optional parameters before they are unwrapped in other constraints by Anchor.
constraint = has_valid_params_presence(
&[relay_data.is_some(), repayment_chain_id.is_some(), repayment_address.is_some()],
instruction_params.is_some()) @ SvmError::InconsistentOptionalParameters,
constraint = is_relay_hash_valid(
&relay_hash,
&relay_data.clone().unwrap_or_else(|| instruction_params.as_ref().unwrap().relay_data.clone()),
Expand Down Expand Up @@ -187,7 +196,7 @@ fn unwrap_fill_relay_params(
repayment_chain_id: account.repayment_chain_id,
repayment_address: account.repayment_address,
})
.unwrap(), // We do not expect this to panic here as missing instruction_params is unwrapped in context.
.unwrap(), // We do not expect this to panic here as optional parameters are validated in context.
}
}

Expand Down
27 changes: 22 additions & 5 deletions programs/svm-spoke/src/instructions/slow_fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::event::{FillType, FilledRelay, RelayExecutionEventInfo, RequestedSlow
use crate::{
common::RelayData,
constants::DISCRIMINATOR_SIZE,
constraints::is_relay_hash_valid,
constraints::{has_valid_params_presence, is_relay_hash_valid},
error::{CommonError, SvmError},
state::{ExecuteSlowRelayLeafParams, FillStatus, FillStatusAccount, RequestSlowFillParams, RootBundle, State},
utils::{get_current_time, hash_non_empty_message, invoke_handler, verify_merkle_proof},
Expand Down Expand Up @@ -35,6 +35,10 @@ pub struct RequestSlowFill<'info> {
space = DISCRIMINATOR_SIZE + FillStatusAccount::INIT_SPACE,
seeds = [b"fills", _relay_hash.as_ref()],
bump,
// Validate optional parameters before they are unwrapped in other constraints by Anchor.
constraint = has_valid_params_presence(
&[relay_data.is_some()],
instruction_params.is_some()) @ SvmError::InconsistentOptionalParameters,
constraint = is_relay_hash_valid(
&_relay_hash,
&relay_data.clone().unwrap_or_else(|| instruction_params.as_ref().unwrap().relay_data.clone()),
Expand Down Expand Up @@ -101,7 +105,7 @@ fn unwrap_request_slow_fill_params(
_ => account
.as_ref()
.map(|account| RequestSlowFillParams { relay_data: account.relay_data.clone() })
.unwrap(), // We do not expect this to panic here as missing instruction_params is unwrapped in context.
.unwrap(), // We do not expect this to panic here as optional parameters are validated in context.
}
}

Expand Down Expand Up @@ -133,15 +137,28 @@ impl SlowFill {

#[event_cpi]
#[derive(Accounts)]
#[instruction(_relay_hash: [u8; 32], slow_fill_leaf: Option<SlowFill>, _root_bundle_id: Option<u32>)]
#[instruction(
_relay_hash: [u8; 32],
slow_fill_leaf: Option<SlowFill>,
_root_bundle_id: Option<u32>,
proof: Option<Vec<[u8; 32]>>
)]
pub struct ExecuteSlowRelayLeaf<'info> {
pub signer: Signer<'info>,

// This is required as fallback when None instruction params are passed in arguments.
#[account(mut, seeds = [b"instruction_params", signer.key().as_ref()], bump, close = signer)]
pub instruction_params: Option<Account<'info, ExecuteSlowRelayLeafParams>>,

#[account(seeds = [b"state", state.seed.to_le_bytes().as_ref()], bump)]
#[account(
seeds = [b"state", state.seed.to_le_bytes().as_ref()],
bump,
// This check is not directly relevant to the state account, but the validation must be performed before any of
// the optional params are unwrapped by Anchor.
constraint = has_valid_params_presence(
&[slow_fill_leaf.is_some(), _root_bundle_id.is_some(), proof.is_some()],
instruction_params.is_some()) @ SvmError::InconsistentOptionalParameters
)]
pub state: Account<'info, State>,

#[account(
Expand Down Expand Up @@ -310,6 +327,6 @@ fn unwrap_execute_slow_relay_leaf_params(
root_bundle_id: account.root_bundle_id,
proof: account.proof.clone(),
})
.unwrap(), // We do not expect this to panic here as missing instruction_params is unwrapped in context.
.unwrap(), // We do not expect this to panic here as optional parameters are validated in context.
}
}
8 changes: 4 additions & 4 deletions programs/svm-spoke/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,8 @@ pub mod svm_spoke {
/// - repayment_chain_id: Chain of SpokePool where relayer wants to be refunded after the challenge window has
/// passed. Will receive input_amount of the equivalent token to input_token on the repayment chain.
/// - repayment_address: The address of the recipient on the repayment chain that they want to be refunded to.
/// Note: relay_data, repayment_chain_id, and repayment_address are optional parameters. If None for any of these
/// is passed, the caller must load them via the instruction_params account.
/// Note: relay_data, repayment_chain_id, and repayment_address are optional parameters, but their presence must be
/// consistent. If None for these is passed, the caller must load them via the instruction_params account.
pub fn fill_relay<'info>(
ctx: Context<'_, '_, '_, 'info, FillRelay<'info>>,
relay_hash: [u8; 32],
Expand Down Expand Up @@ -715,8 +715,8 @@ pub mod svm_spoke {
/// this will be set higher to reimburse the recipient for waiting for the slow fill.
/// - _root_bundle_id: Unique ID of root bundle containing slow relay root that this leaf is contained in.
/// - proof: Inclusion proof for this leaf in slow relay root in root bundle.
/// Note: slow_fill_leaf, _root_bundle_id, and proof are optional parameters. If None for any of these is passed,
/// the caller must load them via the instruction_params account.
/// Note: slow_fill_leaf, _root_bundle_id, and proof are optional parameters, but their presence must be consistent.
/// If None for these parameters is passed, the caller must load them via the instruction_params account.
/// Note: When verifying the slow fill leaf, the relay data is hashed using AnchorSerialize::serialize that encodes
/// output token amounts to little-endian format while input token amount preserves its big-endian encoding as it
/// is passed as [u8; 32] array.
Expand Down
47 changes: 47 additions & 0 deletions src/types/svm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,50 @@ export interface EventType {
* Supported Networks
*/
export type SupportedNetworks = "mainnet" | "devnet";

/**
* Fill instruction accounts
*/
export type FillAccounts = {
signer: PublicKey;
instructionParams: PublicKey;
state: PublicKey;
delegate: PublicKey;
mint: PublicKey;
relayerTokenAccount: PublicKey;
recipientTokenAccount: PublicKey;
fillStatus: PublicKey;
tokenProgram: PublicKey;
associatedTokenProgram: PublicKey;
systemProgram: PublicKey;
program: PublicKey;
};

/**
* Request Slow Fill Accounts
*/
export type RequestSlowFillAccounts = {
signer: PublicKey;
instructionParams: PublicKey;
state: PublicKey;
fillStatus: PublicKey;
systemProgram: PublicKey;
program: PublicKey;
};

/**
* Execute Slow Relay Leaf Accounts
*/
export type ExecuteSlowRelayLeafAccounts = {
signer: PublicKey;
instructionParams: PublicKey;
state: PublicKey;
rootBundle: PublicKey;
fillStatus: PublicKey;
mint: PublicKey;
recipientTokenAccount: PublicKey;
vault: PublicKey;
tokenProgram: PublicKey;
systemProgram: PublicKey;
program: PublicKey;
};
17 changes: 1 addition & 16 deletions test/svm/SvmSpoke.Fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
readEventsUntilFound,
sendTransactionWithLookupTable as sendTransactionWithLookupTableV1,
} from "../../src/svm/web3-v1";
import { FillDataValues, RelayData } from "../../src/types/svm";
import { FillDataValues, RelayData, FillAccounts } from "../../src/types/svm";
import { common } from "./SvmSpoke.common";
import { createDefaultSolanaClient, testAcrossPlusMessage } from "./utils";
const {
Expand Down Expand Up @@ -81,21 +81,6 @@ describe("svm_spoke.fill", () => {
const relayAmount = 500000;
let relayData: RelayData; // reused relay data for all tests.

type FillAccounts = {
state: PublicKey;
delegate: PublicKey;
signer: PublicKey;
instructionParams: PublicKey;
mint: PublicKey;
relayerTokenAccount: PublicKey;
recipientTokenAccount: PublicKey;
fillStatus: PublicKey;
tokenProgram: PublicKey;
associatedTokenProgram: PublicKey;
systemProgram: PublicKey;
program: PublicKey;
};

let accounts: FillAccounts; // Store accounts to simplify contract interactions.

const updateRelayData = (newRelayData: RelayData) => {
Expand Down
Loading
Loading