Skip to content

feat(svm): remove enabled deposit route check #939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 3, 2025
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
4 changes: 2 additions & 2 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ queryEvents = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryEvents.ts"
queryEventsV2 = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryEventsV2.ts"
initialize = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/initialize.ts"
queryState = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryState.ts"
enableRoute = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/enableRoute.ts"
queryRoute = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryRoute.ts"
createVault = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/createVault.ts"
queryVault = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryVault.ts"
simpleDeposit = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/simpleDeposit.ts"
queryDeposits = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryDeposits.ts"
queryFills = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryFills.ts"
Expand Down
2 changes: 0 additions & 2 deletions programs/svm-spoke/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use anchor_lang::prelude::*;
// Common Errors with EVM SpokePool.
#[error_code]
pub enum CommonError {
#[msg("The route is not enabled!")]
DisabledRoute,
#[msg("Invalid quote timestamp!")]
InvalidQuoteTimestamp,
#[msg("Ivalid fill deadline!")]
Expand Down
7 changes: 0 additions & 7 deletions programs/svm-spoke/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@ pub struct PausedFills {
pub is_paused: bool,
}

#[event]
pub struct EnabledDepositRoute {
pub origin_token: Pubkey,
pub destination_chain_id: u64,
pub enabled: bool,
}

#[event]
pub struct RelayedRootBundle {
pub root_bundle_id: u32,
Expand Down
71 changes: 3 additions & 68 deletions programs/svm-spoke/src/instructions/admin.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{Mint, TokenAccount, TokenInterface},
};

use crate::{
constants::DISCRIMINATOR_SIZE,
constraints::is_local_or_remote_owner,
error::SvmError,
event::{
EmergencyDeletedRootBundle, EnabledDepositRoute, PausedDeposits, PausedFills, RelayedRootBundle,
SetXDomainAdmin, TransferredOwnership,
EmergencyDeletedRootBundle, PausedDeposits, PausedFills, RelayedRootBundle, SetXDomainAdmin,
TransferredOwnership,
},
state::{RootBundle, Route, State},
state::{RootBundle, State},
utils::{initialize_current_time, set_seed},
};

Expand Down Expand Up @@ -136,67 +132,6 @@ pub fn set_cross_domain_admin(ctx: Context<SetCrossDomainAdmin>, cross_domain_ad
Ok(())
}

#[event_cpi]
#[derive(Accounts)]
#[instruction(origin_token: Pubkey, destination_chain_id: u64)]
pub struct SetEnableRoute<'info> {
#[account(constraint = is_local_or_remote_owner(&signer, &state) @ SvmError::NotOwner)]
pub signer: Signer<'info>,

#[account(mut)]
pub payer: Signer<'info>,

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

#[account(
init_if_needed,
payer = payer,
space = DISCRIMINATOR_SIZE + Route::INIT_SPACE,
seeds = [
b"route",
origin_token.as_ref(),
state.seed.to_le_bytes().as_ref(),
destination_chain_id.to_le_bytes().as_ref(),
],
bump
)]
pub route: Account<'info, Route>, // PDA to store route information for this particular token & chainId pair.

#[account(
init_if_needed,
payer = payer,
associated_token::mint = origin_token_mint,
associated_token::authority = state,
associated_token::token_program = token_program
)]
pub vault: InterfaceAccount<'info, TokenAccount>, // ATA, owned by the state, to store the origin token for spoke.

#[account(
mint::token_program = token_program,
// IDL build fails when requiring address = origin_token for mint, thus using a custom constraint.
constraint = origin_token_mint.key() == origin_token @ SvmError::InvalidMint
)]
pub origin_token_mint: InterfaceAccount<'info, Mint>,

pub token_program: Interface<'info, TokenInterface>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}

pub fn set_enable_route(
ctx: Context<SetEnableRoute>,
origin_token: Pubkey,
destination_chain_id: u64,
enabled: bool,
) -> Result<()> {
ctx.accounts.route.enabled = enabled;

emit_cpi!(EnabledDepositRoute { origin_token, destination_chain_id, enabled });

Ok(())
}

#[event_cpi]
#[derive(Accounts)]
pub struct RelayRootBundle<'info> {
Expand Down
9 changes: 1 addition & 8 deletions programs/svm-spoke/src/instructions/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
constants::{MAX_EXCLUSIVITY_PERIOD_SECONDS, ZERO_DEPOSIT_ID},
error::{CommonError, SvmError},
event::FundsDeposited,
state::{Route, State},
state::State,
utils::{get_current_time, get_unsafe_deposit_id, transfer_from},
};

Expand All @@ -36,13 +36,6 @@ pub struct Deposit<'info> {
)]
pub state: Account<'info, State>,

#[account(
seeds = [b"route", input_token.as_ref(), state.seed.to_le_bytes().as_ref(), destination_chain_id.to_le_bytes().as_ref()],
bump,
constraint = route.enabled @ CommonError::DisabledRoute
)]
pub route: Account<'info, Route>,

#[account(
mut,
associated_token::mint = mint,
Expand Down
9 changes: 0 additions & 9 deletions programs/svm-spoke/src/instructions/handle_receive_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,6 @@ fn translate_message(data: &Vec<u8>) -> Result<Vec<u8>> {

new_cross_domain_admin.encode_instruction_data("global:set_cross_domain_admin")
}
// The EVM function signature is setEnableRoute(address,uint256,bool).
// The EVM Solana adapter translates this to the expected Solana format: setEnableRoute(bytes32,uint64,bool).
s if s == utils::encode_solidity_selector("setEnableRoute(bytes32,uint64,bool)") => {
let origin_token = Pubkey::new_from_array(utils::get_solidity_arg(data, 0)?);
let destination_chain_id = utils::decode_solidity_uint64(&utils::get_solidity_arg(data, 1)?)?;
let enabled = utils::decode_solidity_bool(&utils::get_solidity_arg(data, 2)?)?;

(origin_token, destination_chain_id, enabled).encode_instruction_data("global:set_enable_route")
}
s if s == utils::encode_solidity_selector("relayRootBundle(bytes32,bytes32)") => {
let relayer_refund_root = utils::get_solidity_arg(data, 0)?;
let slow_relay_root = utils::get_solidity_arg(data, 1)?;
Expand Down
36 changes: 1 addition & 35 deletions programs/svm-spoke/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,34 +123,6 @@ pub mod svm_spoke {
instructions::transfer_ownership(ctx, new_owner)
}

/// Enables or disables a route for deposits from origin token to destination chain ID. Callable only by the owner.
///
/// ### Required Accounts:
/// - signer (Signer): The account that must be the owner to authorize the route change.
/// - payer (Signer): The account responsible for paying the transaction fees.
/// - state (Writable): The Spoke state PDA. Seed: ["state",state.seed] where `seed` is 0 on mainnet.
/// - route (Writable): PDA to store route information. Created on the first call, updated subsequently.
/// Seed: ["route",origin_token,state.seed,destination_chain_id].
/// - vault (Writable): ATA to hold the origin token for the associated route. Created on the first call.
/// Authority must be set as the state, and mint must be the origin_token_mint.
/// - origin_token_mint: The mint account for the origin token.
/// - token_program: The token program.
/// - associated_token_program: The associated token program.
/// - system_program: The system program required for account creation.
///
/// ### Parameters:
/// - origin_token: The public key of the origin token.
/// - destination_chain_id: The chain ID of the destination.
/// - enabled: Boolean indicating whether the route is enabled or disabled.
pub fn set_enable_route(
ctx: Context<SetEnableRoute>,
origin_token: Pubkey,
destination_chain_id: u64,
enabled: bool,
) -> Result<()> {
instructions::set_enable_route(ctx, origin_token, destination_chain_id, enabled)
}

/// Sets the cross-domain admin for the Spoke Pool. Only callable by owner. Used if Hubpool upgrades.
///
/// ### Required Accounts:
Expand Down Expand Up @@ -228,8 +200,6 @@ pub mod svm_spoke {
/// ### Required Accounts:
/// - signer (Signer): The account that authorizes the deposit.
/// - state (Writable): Spoke state PDA. Seed: ["state",state.seed] where seed is 0 on mainnet.
/// - route (Account): The route PDA for the particular bridged route in question. Validates a route is enabled.
/// Seed: ["route",input_token,state.seed,destination_chain_id].
/// - depositor_token_account (Writable): The depositor's ATA for the input token.
/// - vault (Writable): Programs ATA for the associated input token. This is where the depositor's assets are sent.
/// Authority must be the state.
Expand All @@ -246,8 +216,7 @@ pub mod svm_spoke {
/// amount will be sent to the relayer on their repayment chain of choice as a refund following an optimistic
/// challenge window in the HubPool, less a system fee.
/// - output_amount: The amount of output tokens that the relayer will send to the recipient on the destination.
/// - destination_chain_id: The destination chain identifier. Must be enabled along with the input token as a valid
/// deposit route from this spoke pool or this transaction will revert.
/// - destination_chain_id: The destination chain identifier where the fill should be made.
/// - exclusive_relayer: The relayer that will be exclusively allowed to fill this deposit before the exclusivity
/// deadline timestamp. This must be a valid, non-zero address if the exclusivity deadline is greater than the
/// current block timestamp.
Expand Down Expand Up @@ -400,9 +369,6 @@ pub mod svm_spoke {
/// instruction data due to message size constraints. Pass this program ID to represent None. When Some, this must
/// be derived from the signer's public key with seed ["instruction_params",signer].
/// - state (Writable): Spoke state PDA. Seed: ["state",state.seed] where seed is 0 on mainnet.
/// - route (Account): The route PDA for the particular bridged route in question. Validates a route is enabled.
/// Seed: ["route",input_token,state.seed,destination_chain_id].
/// - vault (Writable): The ATA for refunded mint. Authority must be the state.
/// - mint (Account): The mint of the output token, sent from the relayer to the recipient.
/// - relayer_token_account (Writable): The relayer's ATA for the input token.
/// - recipient_token_account (Writable): The recipient's ATA for the output token.
Expand Down
2 changes: 0 additions & 2 deletions programs/svm-spoke/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ 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::*;
pub use transfer_liability::*;
7 changes: 0 additions & 7 deletions programs/svm-spoke/src/state/route.rs

This file was deleted.

68 changes: 68 additions & 0 deletions scripts/svm/createVault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// This script is used by a chain admin to create a vault for a token on the Solana Spoke Pool. Note that this is a
// permissionless operation, only requiring the caller to spend rent-exempt deposit to create the vault account that is
// not recoverable. Similar to other chains, this enables one to deposit and fill non-whitelisted tokens.

import * as anchor from "@coral-xyz/anchor";
import { AnchorProvider, BN } from "@coral-xyz/anchor";
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { getSpokePoolProgram } from "../../src/svm/web3-v1";

// Set up the provider
const provider = AnchorProvider.env();
anchor.setProvider(provider);
const payer = (anchor.AnchorProvider.env().wallet as anchor.Wallet).payer;
const program = getSpokePoolProgram(provider);
const programId = program.programId;
console.log("SVM-Spoke Program ID:", programId.toString());

// Parse arguments
const argv = yargs(hideBin(process.argv))
.option("seed", { type: "string", demandOption: true, describe: "Seed for the state account PDA" })
.option("originToken", { type: "string", demandOption: true, describe: "Origin token public key" }).argv;

async function createVault(): Promise<void> {
const resolvedArgv = await argv;
const seed = new BN(resolvedArgv.seed);
const originToken = new PublicKey(resolvedArgv.originToken);

// Define the state account PDA
const [statePda, _] = PublicKey.findProgramAddressSync(
[Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)],
programId
);

// Define the signer (replace with your actual signer)
const signer = provider.wallet.publicKey;

console.log("Creating vault...");
console.table([
{ Property: "seed", Value: seed.toString() },
{ Property: "originToken", Value: originToken.toString() },
{ Property: "programId", Value: programId.toString() },
{ Property: "providerPublicKey", Value: provider.wallet.publicKey.toString() },
{ Property: "statePda", Value: statePda.toString() },
]);

// Create ATA for the origin token to be stored by state (vault).
const vault = await getOrCreateAssociatedTokenAccount(
provider.connection,
payer,
originToken,
statePda,
true,
"confirmed",
{
commitment: "confirmed",
},
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
);

console.log("Created vault:", vault.address.toString());
}

// Run the createVault function
createVault();
Loading
Loading