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: depositNow #712

Merged
merged 10 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
43 changes: 37 additions & 6 deletions programs/svm-spoke/src/instructions/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,14 @@ pub fn deposit_v3(
exclusive_relayer: Pubkey,
quote_timestamp: u32,
fill_deadline: u32,
exclusivity_deadline: u32,
exclusivity_period: u32,
Copy link
Member Author

Choose a reason for hiding this comment

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

we had miss-named this prop! the implementation was correct but the variable name was wrong. see

uint32 exclusivityPeriod,

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we had it correct initially, but EVM implementation had changed since here

Copy link
Member Author

Choose a reason for hiding this comment

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

on master EVM implementation it's still called exclusivityPeriod

so I think as it is now we are consistent?

message: Vec<u8>,
) -> Result<()> {
let state = &mut ctx.accounts.state;

let current_time = get_current_time(state)?;

// TODO: if the deposit quote timestamp is bad it is possible to make this error with a subtraction
// overflow (from devnet testing). add a test to re-create this and fix it such that the error is thrown,
// not caught via overflow.
if current_time - quote_timestamp > state.deposit_quote_time_buffer {
if current_time.checked_sub(quote_timestamp).unwrap_or(u32::MAX) > state.deposit_quote_time_buffer {
Copy link
Member Author

Choose a reason for hiding this comment

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

if checked_sub underflows then it defaults to the unwrap_or value, of u32::MAX, forcing the if to return true, and throwing the error. This fixes the TODO above that I saw on testnet (and validated in unit tests).

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 EVM implementation has the same underflow issue, so we might fix it there too

Copy link
Member Author

Choose a reason for hiding this comment

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

in the evm implementation if it fails at that point does it revert with an underflow error or does it throw the associated revert message that we are expecting?

Copy link
Contributor

Choose a reason for hiding this comment

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

as per this comment, it would underflow in EVM

Copy link
Member Author

@chrismaree chrismaree Nov 4, 2024

Choose a reason for hiding this comment

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

right but my question was would it throw the error InvalidQuoteTimestamp or underflow. it seems it will throw with underflow (see here).

my original point is that this is actually a bit confusing for a caller. there are situations where multiple invalid input values return the same error. equally, there are situations where some invalid input values, depending on the input, return inconsistent errors.

I agree with your original reply that we should fix this consistently, changing the evm implementation such that all invalid errors return the same error.

return err!(CommonError::InvalidQuoteTimestamp);
}

Expand Down Expand Up @@ -112,7 +109,7 @@ pub fn deposit_v3(
deposit_id: state.number_of_deposits,
quote_timestamp,
fill_deadline,
exclusivity_deadline,
exclusivity_deadline: current_time + exclusivity_period,
depositor,
recipient,
exclusive_relayer,
Expand All @@ -121,3 +118,37 @@ pub fn deposit_v3(

Ok(())
}

pub fn deposit_v3_now(
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,
fill_deadline_offset: u32,
exclusivity_period: u32,
message: Vec<u8>,
) -> Result<()> {
let current_time = get_current_time(ctx.accounts.state)?;
deposit_v3(
ctx,
depositor,
recipient,
input_token,
output_token,
input_amount,
output_amount,
destination_chain_id,
exclusive_relayer,
current_time,
current_time + fill_deadline_offset,
exclusivity_period,
message,
)?;

Ok(())
}
32 changes: 31 additions & 1 deletion programs/svm-spoke/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub mod svm_spoke {
instructions::set_cross_domain_admin(ctx, cross_domain_admin)
}

// User methods.
// Deposit methods.
pub fn deposit_v3(
ctx: Context<DepositV3>,
depositor: Pubkey,
Expand Down Expand Up @@ -132,6 +132,36 @@ pub mod svm_spoke {
)
}

pub fn deposit_v3_now(
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,
fill_deadline: u32,
exclusivity_deadline: u32,
message: Vec<u8>,
) -> Result<()> {
instructions::deposit_v3_now(
ctx,
depositor,
recipient,
input_token,
output_token,
input_amount,
output_amount,
destination_chain_id,
exclusive_relayer,
fill_deadline,
exclusivity_deadline,
message,
)
}

// Relayer methods.
pub fn fill_v3_relay(
ctx: Context<FillV3Relay>,
Expand Down
71 changes: 61 additions & 10 deletions test/svm/SvmSpoke.Deposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ describe("svm_spoke.deposit", () => {

await enableRoute();
});

it("Deposits tokens via deposit_v3 function and checks balances", async () => {
// Verify vault balance is zero before the deposit
let vaultAccount = await getAccount(connection, vault);
Expand Down Expand Up @@ -144,7 +143,13 @@ describe("svm_spoke.deposit", () => {
// Fetch and verify the depositEvent
let events = await readProgramEvents(connection, program);
let event = events.find((event) => event.name === "v3FundsDeposited").data;
const expectedValues1 = { ...depositData, depositId: "1" }; // Verify the event props emitted match the depositData.
const currentTime = await getCurrentTime(program, state);
const { exclusivityPeriod, ...restOfDepositData } = depositData; // Strip exclusivityPeriod from depositData
const expectedValues1 = {
...restOfDepositData,
depositId: "1",
exclusivityDeadline: currentTime + exclusivityPeriod.toNumber(),
}; // Verify the event props emitted match the depositData.
for (const [key, value] of Object.entries(expectedValues1)) {
assertSE(event[key], value, `${key} should match`);
}
Expand All @@ -161,7 +166,7 @@ describe("svm_spoke.deposit", () => {
events = await readProgramEvents(connection, program);
event = events.find((event) => event.name === "v3FundsDeposited" && event.data.depositId.toString() === "2").data;

const expectedValues2 = { ...depositData, depositId: "2" }; // Verify the event props emitted match the depositData.
const expectedValues2 = { ...expectedValues1, depositId: "2" }; // Verify the event props emitted match the depositData.
for (const [key, value] of Object.entries(expectedValues2)) {
assertSE(event[key], value, `${key} should match`);
}
Expand Down Expand Up @@ -248,11 +253,7 @@ describe("svm_spoke.deposit", () => {
.rpc();
assert.fail("Deposit should have failed due to InvalidQuoteTimestamp");
} catch (err: any) {
assert.include(
err.toString(),
"attempt to subtract with overflow",
"Expected underflow error due to future quote timestamp"
);
assert.include(err.toString(), "Error Code: InvalidQuoteTimestamp", "Expected InvalidQuoteTimestamp error");
}
});

Expand Down Expand Up @@ -391,8 +392,9 @@ describe("svm_spoke.deposit", () => {
// Fetch and verify the depositEvent
let events = await readProgramEvents(connection, program);
let event = events.find((event) => event.name === "v3FundsDeposited").data;
const expectedValues1 = { ...{ ...depositData, destinationChainId: fakeRouteChainId }, depositId: "1" }; // Verify the event props emitted match the depositData.
for (const [key, value] of Object.entries(expectedValues1)) {
const { exclusivityPeriod, ...restOfDepositData } = depositData; // Strip exclusivityPeriod from depositData
const expectedValues = { ...{ ...restOfDepositData, destinationChainId: fakeRouteChainId }, depositId: "1" }; // Verify the event props emitted match the depositData.
for (const [key, value] of Object.entries(expectedValues)) {
assertSE(event[key], value, `${key} should match`);
}

Expand Down Expand Up @@ -422,4 +424,53 @@ describe("svm_spoke.deposit", () => {
const vaultAccount = await getAccount(connection, vault);
assertSE(vaultAccount.amount, 0, "Vault balance should not be changed by the fake route deposit");
});

it("depositV3Now behaves as deposit but forces the quote timestamp as expected", async () => {
// Set up initial deposit data. Note that this method has a slightly different interface to deposit, using
// fillDeadlineOffset rather than fillDeadline. current chain time is added to fillDeadlineOffset to set the
// fillDeadline for the deposit. exclusivityPeriod operates the same as in standard deposit.
// Equally, depositV3Now does not have `quoteTimestamp`. this is set to the current time from the program.
const fillDeadlineOffset = 60; // 60 seconds offset

// Execute the deposit_v3_now call. Remove the quoteTimestamp from the depositData as not needed for this method.

await program.methods
.depositV3Now(
depositData.depositor!,
depositData.recipient!,
depositData.inputToken!,
depositData.outputToken!,
depositData.inputAmount,
depositData.outputAmount,
depositData.destinationChainId,
depositData.exclusiveRelayer!,
fillDeadlineOffset,
depositData.exclusivityPeriod.toNumber(),
depositData.message
)
.accounts(depositAccounts)
.signers([depositor])
.rpc();

await new Promise((resolve) => setTimeout(resolve, 500));

// Fetch and verify the depositEvent
const events = await readProgramEvents(connection, program);
const event = events.find((event) => event.name === "v3FundsDeposited").data;

// Verify the event props emitted match the expected values
const currentTime = await getCurrentTime(program, state);
const { exclusivityPeriod, ...restOfDepositData } = depositData; // Strip exclusivityPeriod from depositData
const expectedValues = {
...restOfDepositData,
quoteTimestamp: currentTime,
fillDeadline: currentTime + fillDeadlineOffset,
exclusivityDeadline: currentTime + exclusivityPeriod.toNumber(),
depositId: "1",
};

for (const [key, value] of Object.entries(expectedValues)) {
assertSE(event[key], value, `${key} should match`);
}
});
});
16 changes: 10 additions & 6 deletions test/svm/SvmSpoke.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ const exclusiveRelayer = Keypair.generate().publicKey;
const outputToken = new PublicKey("1111111111113EsMD5n1VA94D2fALdb1SAKLam8j"); // TODO: this is lazy. this is cast USDC from Eth mainnet.
const inputAmount = new BN(500000);
const outputAmount = inputAmount;
const quoteTimestamp = new BN(Math.floor(Date.now() / 1000) - 10); // 10 seconds ago.
const quoteTimestamp = new BN(Math.floor(Date.now() / 1000) - 60); // 60 seconds ago.
const fillDeadline = new BN(Math.floor(Date.now() / 1000) + 600); // 600 seconds from now.
const exclusivityDeadline = new BN(Math.floor(Date.now() / 1000) + 300); // 300 seconds from now.
const exclusivityPeriod = new BN(300); // 300 seconds.
Copy link
Member Author

Choose a reason for hiding this comment

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

this update applies to match the EVM changes and the correct input prop and how it's meant to be used.

const message = Buffer.from("Test message");
const depositQuoteTimeBuffer = new BN(3600); // 1 hour.
const fillDeadlineBuffer = new BN(3600 * 4); // 4 hours.
Expand Down Expand Up @@ -90,7 +90,11 @@ async function getCurrentTime(program: Program<SvmSpoke>, state: any) {
}

function assertSE(a: any, b: any, errorMessage: string) {
assert.strictEqual(a.toString(), b.toString(), errorMessage);
if (a === undefined || b === undefined) {
throw new Error("Undefined value" + errorMessage);
} else {
assert.strictEqual(a.toString(), b.toString(), errorMessage);
}
}

interface DepositData {
Expand All @@ -104,7 +108,7 @@ interface DepositData {
exclusiveRelayer: PublicKey;
quoteTimestamp: BN;
fillDeadline: BN;
exclusivityDeadline: BN;
exclusivityPeriod: BN;
message: Buffer;
}

Expand Down Expand Up @@ -141,7 +145,7 @@ export const common = {
outputAmount,
quoteTimestamp,
fillDeadline,
exclusivityDeadline,
exclusivityPeriod,
message,
depositQuoteTimeBuffer,
fillDeadlineBuffer,
Expand All @@ -163,7 +167,7 @@ export const common = {
exclusiveRelayer,
quoteTimestamp,
fillDeadline,
exclusivityDeadline,
exclusivityPeriod,
message,
} as DepositData,
};
Loading