Skip to content
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

feat: add determinstic deposit hash & new exclusivity logic #759

Merged
merged 26 commits into from
Nov 25, 2024
Merged
6 changes: 3 additions & 3 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ skip-lint = false

[programs.localnet]
multicall_handler = "6zbEkDZGuHqGiACGWc2Xd5DY4m52qwXjmthzWtnoCTyG"
svm_spoke = "Fdedr2RqfufUiE1sbVEfpSQ3NADJqxrvu1zojWpQJj4q"
test = "84j1xFuoz2xynhesB8hxC5N1zaWPr4MW1DD2gVm9PUs4"
svm_spoke = "DnLjPzpMCW2CF99URhGF3jDYnVRcCJMjUWsbPb4xVoBn"
test = "DnLjPzpMCW2CF99URhGF3jDYnVRcCJMjUWsbPb4xVoBn"

[programs.devnet]
multicall_handler = "6zbEkDZGuHqGiACGWc2Xd5DY4m52qwXjmthzWtnoCTyG"
svm_spoke = "Fdedr2RqfufUiE1sbVEfpSQ3NADJqxrvu1zojWpQJj4q"
svm_spoke = "DnLjPzpMCW2CF99URhGF3jDYnVRcCJMjUWsbPb4xVoBn"

[registry]
url = "https://api.apr.dev"
Expand Down
2 changes: 1 addition & 1 deletion programs/multicall-handler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use anchor_lang::{
},
};

declare_id!("6zbEkDZGuHqGiACGWc2Xd5DY4m52qwXjmthzWtnoCTyG");
declare_id!("6kqWTz3A3ZYMV2FMU24ke8rHzT82SaBz7GkBKTd7Z9BH");

#[program]
pub mod multicall_handler {
Expand Down
2 changes: 1 addition & 1 deletion programs/svm-spoke/src/common/v3_relay_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub struct V3RelayData {
pub input_amount: u64,
pub output_amount: u64,
pub origin_chain_id: u64,
pub deposit_id: u32,
pub deposit_id: [u8; 32],
pub fill_deadline: u32,
pub exclusivity_deadline: u32,
pub message: Vec<u8>,
Expand Down
6 changes: 6 additions & 0 deletions programs/svm-spoke/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ use anchor_lang::prelude::*;
pub const DISCRIMINATOR_SIZE: usize = 8;

pub const MESSAGE_TRANSMITTER_PROGRAM_ID: Pubkey = pubkey!("CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd");

// One year in seconds. If exclusivityParameter is set to a value less than this, then the emitted exclusivityDeadline
// in a deposit event will be set to the current time plus this value.
pub const MAX_EXCLUSIVITY_PERIOD_SECONDS: u32 = 31_536_000;

pub const ZERO_DEPOSIT_ID: [u8; 32] = [0u8; 32];
2 changes: 2 additions & 0 deletions programs/svm-spoke/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub enum CommonError {
FillsArePaused,
#[msg("Insufficient spoke pool balance to execute leaf")]
InsufficientSpokePoolBalanceToExecuteLeaf,
#[msg("Invalid exclusive relayer!")]
InvalidExclusiveRelayer,
}

// SVM specific errors.
Expand Down
6 changes: 3 additions & 3 deletions programs/svm-spoke/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub struct V3FundsDeposited {
pub input_amount: u64,
pub output_amount: u64,
pub destination_chain_id: u64,
pub deposit_id: u32,
pub deposit_id: [u8; 32],
pub quote_timestamp: u32,
pub fill_deadline: u32,
pub exclusivity_deadline: u32,
Expand Down Expand Up @@ -72,7 +72,7 @@ pub struct FilledV3Relay {
pub output_amount: u64,
pub repayment_chain_id: u64,
pub origin_chain_id: u64,
pub deposit_id: u32,
pub deposit_id: [u8; 32],
pub fill_deadline: u32,
pub exclusivity_deadline: u32,
pub exclusive_relayer: Pubkey,
Expand All @@ -92,7 +92,7 @@ pub struct RequestedV3SlowFill {
pub input_amount: u64,
pub output_amount: u64,
pub origin_chain_id: u64,
pub deposit_id: u32,
pub deposit_id: [u8; 32],
pub fill_deadline: u32,
pub exclusivity_deadline: u32,
pub exclusive_relayer: Pubkey,
Expand Down
114 changes: 108 additions & 6 deletions programs/svm-spoke/src/instructions/deposit.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use anchor_lang::prelude::*;
use anchor_lang::{prelude::*, solana_program::keccak};
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};

use crate::{
constants::{MAX_EXCLUSIVITY_PERIOD_SECONDS, ZERO_DEPOSIT_ID},
error::{CommonError, SvmError},
event::V3FundsDeposited,
get_current_time,
Expand Down Expand Up @@ -63,7 +64,7 @@ pub struct DepositV3<'info> {
pub token_program: Interface<'info, TokenInterface>,
}

pub fn deposit_v3(
pub fn _deposit_v3(
ctx: Context<DepositV3>,
depositor: Pubkey,
recipient: Pubkey,
Expand All @@ -73,9 +74,10 @@ pub fn deposit_v3(
output_amount: u64,
destination_chain_id: u64,
exclusive_relayer: Pubkey,
deposit_id: [u8; 32],
quote_timestamp: u32,
fill_deadline: u32,
exclusivity_period: u32,
exclusivity_parameter: u32,
message: Vec<u8>,
) -> Result<()> {
let state = &mut ctx.accounts.state;
Expand All @@ -90,6 +92,17 @@ pub fn deposit_v3(
return err!(CommonError::InvalidFillDeadline);
}

let mut exclusivity_deadline = exclusivity_parameter;
if exclusivity_deadline > 0 {
if exclusivity_deadline <= MAX_EXCLUSIVITY_PERIOD_SECONDS {
exclusivity_deadline += current_time;
}

if exclusive_relayer == Pubkey::default() {
return err!(CommonError::InvalidExclusiveRelayer);
}
}

// Depositor must have delegated input_amount to the state PDA.
transfer_from(
&ctx.accounts.depositor_token_account,
Expand All @@ -101,18 +114,23 @@ pub fn deposit_v3(
&ctx.accounts.token_program,
)?;

state.number_of_deposits += 1; // Increment number of deposits
let mut applied_deposit_id = deposit_id;
// If the passed in deposit_id is all zeros, then we use the state's number of deposits as deposit_id.
if deposit_id == ZERO_DEPOSIT_ID {
state.number_of_deposits += 1;
applied_deposit_id[..4].copy_from_slice(&state.number_of_deposits.to_le_bytes());
}

emit_cpi!(V3FundsDeposited {
input_token,
output_token,
input_amount,
output_amount,
destination_chain_id,
deposit_id: state.number_of_deposits,
deposit_id: applied_deposit_id,
quote_timestamp,
fill_deadline,
exclusivity_deadline: current_time + exclusivity_period,
exclusivity_deadline,
depositor,
recipient,
exclusive_relayer,
Expand All @@ -122,6 +140,41 @@ pub fn deposit_v3(
Ok(())
}

pub fn deposit_v3(
ctx: Context<DepositV3>,
depositor: Pubkey,
recipient: Pubkey,
input_token: Pubkey,
output_token: Pubkey,
input_amount: u64,
output_amount: u64,
destination_chain_id: u64,
exclusive_relayer: Pubkey,
quote_timestamp: u32,
fill_deadline: u32,
exclusivity_parameter: u32,
message: Vec<u8>,
) -> Result<()> {
_deposit_v3(
ctx,
depositor,
recipient,
input_token,
output_token,
input_amount,
output_amount,
destination_chain_id,
exclusive_relayer,
ZERO_DEPOSIT_ID, // ZERO_DEPOSIT_ID informs internal function to use state.number_of_deposits as id.
quote_timestamp,
fill_deadline,
exclusivity_parameter,
message,
)?;

Ok(())
}

pub fn deposit_v3_now(
ctx: Context<DepositV3>,
depositor: Pubkey,
Expand Down Expand Up @@ -156,3 +209,52 @@ pub fn deposit_v3_now(

Ok(())
}

pub fn unsafe_deposit_v3(
ctx: Context<DepositV3>,
depositor: Pubkey,
recipient: Pubkey,
input_token: Pubkey,
output_token: Pubkey,
input_amount: u64,
output_amount: u64,
destination_chain_id: u64,
exclusive_relayer: Pubkey,
deposit_nonce: u64,
quote_timestamp: u32,
fill_deadline: u32,
exclusivity_parameter: u32,
message: Vec<u8>,
) -> Result<()> {
// Calculate the unsafe deposit ID as a [u8; 32]
let deposit_id = get_unsafe_deposit_id(ctx.accounts.signer.key(), depositor, deposit_nonce);
_deposit_v3(
ctx,
depositor,
recipient,
input_token,
output_token,
input_amount,
output_amount,
destination_chain_id,
exclusive_relayer,
deposit_id,
quote_timestamp,
fill_deadline,
exclusivity_parameter,
message,
)?;

Ok(())
}

// Define a dummy context struct so we can export this as a view function in lib.
#[derive(Accounts)]
pub struct Null {}
pub fn get_unsafe_deposit_id(msg_sender: Pubkey, depositor: Pubkey, deposit_nonce: u64) -> [u8; 32] {
let mut data = Vec::new();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think it makes sense to expose this? Are we planning to use it off-chain, or should we implement a similar function in TS for efficiency?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, I think having it exposed is useful. it's done in solidity and it's good to not force users to have to do this in js. for example say you wanted to do the deposit with etherscan for example. i don't think there is any downside in having it public. do you agree?

// Use AnchorSerialize to serialize the tuple of values
(msg_sender, depositor, deposit_nonce).serialize(&mut data).unwrap();

Copy link
Contributor

Choose a reason for hiding this comment

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

note that this is not AnchorSerialize wrapper (like mentioned in comment above), but using borch directly. Though, I guess functionally its the same in this case.

Copy link
Member Author

Choose a reason for hiding this comment

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

done.

keccak::hash(&data).to_bytes()
}
2 changes: 1 addition & 1 deletion programs/svm-spoke/src/instructions/slow_fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl V3SlowFill {
bytes.extend_from_slice(&self.relay_data.input_amount.to_le_bytes());
bytes.extend_from_slice(&self.relay_data.output_amount.to_le_bytes());
bytes.extend_from_slice(&self.relay_data.origin_chain_id.to_le_bytes());
bytes.extend_from_slice(&self.relay_data.deposit_id.to_le_bytes());
bytes.extend_from_slice(&self.relay_data.deposit_id);
bytes.extend_from_slice(&self.relay_data.fill_deadline.to_le_bytes());
bytes.extend_from_slice(&self.relay_data.exclusivity_deadline.to_le_bytes());
bytes.extend_from_slice(&self.relay_data.message);
Expand Down
49 changes: 48 additions & 1 deletion programs/svm-spoke/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anchor_lang::prelude::*;

declare_id!("Fdedr2RqfufUiE1sbVEfpSQ3NADJqxrvu1zojWpQJj4q");
declare_id!("DnLjPzpMCW2CF99URhGF3jDYnVRcCJMjUWsbPb4xVoBn");

// External programs from idls directory (requires `anchor run generateExternalTypes`).
declare_program!(message_transmitter);
Expand Down Expand Up @@ -171,6 +171,53 @@ pub mod svm_spoke {
)
}

pub fn unsafe_deposit_v3(
ctx: Context<DepositV3>,
depositor: Pubkey,
recipient: Pubkey,
input_token: Pubkey,
output_token: Pubkey,
input_amount: u64,
output_amount: u64,
destination_chain_id: u64,
exclusive_relayer: Pubkey,
deposit_nonce: u64,
quote_timestamp: u32,
fill_deadline: u32,
exclusivity_parameter: u32,
message: Vec<u8>,
) -> Result<()> {
instructions::unsafe_deposit_v3(
ctx,
depositor,
recipient,
input_token,
output_token,
input_amount,
output_amount,
destination_chain_id,
exclusive_relayer,
deposit_nonce,
quote_timestamp,
fill_deadline,
exclusivity_parameter,
message,
)
}

pub fn get_unsafe_deposit_id(
_ctx: Context<Null>,
msg_sender: Pubkey,
depositor: Pubkey,
deposit_nonce: u64,
) -> Result<[u8; 32]> {
Ok(instructions::get_unsafe_deposit_id(
msg_sender,
depositor,
deposit_nonce,
))
}

// Relayer methods.
pub fn fill_v3_relay<'info>(
ctx: Context<'_, '_, '_, 'info, FillV3Relay<'info>>,
Expand Down
2 changes: 1 addition & 1 deletion programs/test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use svm_spoke::{
utils::{is_claimed, process_proof, set_claimed},
};

declare_id!("84j1xFuoz2xynhesB8hxC5N1zaWPr4MW1DD2gVm9PUs4");
declare_id!("8tsEfDSiE4WUMf97oyyyasLAvWwjeRZb2GByh4w7HckA");

// This program is used to test the svm_spoke program internal utils methods. It's kept separate from the svm_spoke
// as it simply exports utils methods so direct unit tests can be run against them.
Expand Down
4 changes: 2 additions & 2 deletions scripts/svm/fakeFillWithRandomDistribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const argv = yargs(hideBin(process.argv))
.option("inputAmount", { type: "number", demandOption: true, describe: "Input amount" })
.option("outputAmount", { type: "number", demandOption: true, describe: "Output amount" })
.option("originChainId", { type: "string", demandOption: true, describe: "Origin chain ID" })
.option("depositId", { type: "number", demandOption: true, describe: "Deposit ID" })
.option("depositId", { type: "array", demandOption: true, describe: "Deposit ID" })
.option("fillDeadline", { type: "number", demandOption: false, describe: "Fill deadline" })
.option("exclusivityDeadline", { type: "number", demandOption: false, describe: "Exclusivity deadline" })
.option("repaymentChain", { type: "number", demandOption: false, description: "Repayment chain ID" })
Expand All @@ -58,7 +58,7 @@ async function fillV3RelayToRandom(): Promise<void> {
const inputAmount = new BN(resolvedArgv.inputAmount);
const outputAmount = new BN(resolvedArgv.outputAmount);
const originChainId = new BN(resolvedArgv.originChainId);
const depositId = resolvedArgv.depositId;
const depositId = (resolvedArgv.depositId as number[]).map((id) => id); // Ensure depositId is an array of BN
const fillDeadline = resolvedArgv.fillDeadline || Math.floor(Date.now() / 1000) + 60; // Current time + 1 minute
const exclusivityDeadline = resolvedArgv.exclusivityDeadline || Math.floor(Date.now() / 1000) + 30; // Current time + 30 seconds
const repaymentChain = new BN(resolvedArgv.repaymentChain || 1);
Expand Down
4 changes: 2 additions & 2 deletions scripts/svm/simpleFill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const argv = yargs(hideBin(process.argv))
.option("inputAmount", { type: "number", demandOption: true, describe: "Input amount" })
.option("outputAmount", { type: "number", demandOption: true, describe: "Output amount" })
.option("originChainId", { type: "string", demandOption: true, describe: "Origin chain ID" })
.option("depositId", { type: "number", demandOption: true, describe: "Deposit ID" })
.option("depositId", { type: "array", demandOption: true, describe: "Deposit ID" })
.option("fillDeadline", { type: "number", demandOption: false, describe: "Fill deadline" })
.option("exclusivityDeadline", { type: "number", demandOption: false, describe: "Exclusivity deadline" }).argv;

Expand Down Expand Up @@ -64,7 +64,7 @@ async function fillV3Relay(): Promise<void> {
inputAmount,
outputAmount,
originChainId,
depositId,
depositId: depositId.map((id) => Number(id)),
fillDeadline,
exclusivityDeadline,
message,
Expand Down
2 changes: 1 addition & 1 deletion src/SvmUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export function calculateRelayHashUint8Array(relayData: any, chainId: BN): Uint8
relayData.inputAmount.toArrayLike(Buffer, "le", 8),
relayData.outputAmount.toArrayLike(Buffer, "le", 8),
relayData.originChainId.toArrayLike(Buffer, "le", 8),
new BN(relayData.depositId).toArrayLike(Buffer, "le", 4),
relayData.depositId,
new BN(relayData.fillDeadline).toArrayLike(Buffer, "le", 4),
new BN(relayData.exclusivityDeadline).toArrayLike(Buffer, "le", 4),
messageBuffer,
Expand Down
Loading
Loading