Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
Signed-off-by: chrismaree <[email protected]>
  • Loading branch information
chrismaree committed Sep 23, 2024
1 parent 3acbfcb commit 0db1965
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 34 deletions.
4 changes: 2 additions & 2 deletions programs/svm-spoke/src/instructions/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ pub fn deposit_v3(
} else {
Clock::get()?.unix_timestamp as u32
};
// TODO: in solidity these are if then revert. here they are requires. i.e the assertion goes in the oposite direction to get the same effect. verify we want <= vs < for these assertions as this can change things for inclusive/exclusive checks.
// TODO: in solidity these are if then revert. here they are requires. i.e the assertion goes in the opposite direction to get the same effect. verify we want <= vs < for these assertions as this can change things for inclusive/exclusive checks.
require!(
current_timestamp - quote_timestamp <= state.deposit_quote_time_buffer,
CustomError::InvalidQuoteTimestamp
);

require!(
fill_deadline >= current_timestamp
|| fill_deadline <= current_timestamp + state.fill_deadline_buffer,
&& fill_deadline <= current_timestamp + state.fill_deadline_buffer,
CustomError::InvalidFillDeadline,
);

Expand Down
80 changes: 77 additions & 3 deletions test/svm/SvmSpoke.Deposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { PublicKey, Keypair } from "@solana/web3.js";
import { readProgramEvents } from "../../src/SvmUtils";
import { common } from "./SvmSpoke.common";
const { provider, connection, program, owner, seedBalance, initializeState, depositData } = common;
const { createRoutePda, getVaultAta, assertSE, assert } = common;
const { createRoutePda, getVaultAta, assertSE, assert, getCurrentTime, depositQuoteTimeBuffer, fillDeadlineBuffer } =
common;

describe("svm_spoke.deposit", () => {
anchor.setProvider(provider);
Expand Down Expand Up @@ -212,6 +213,79 @@ describe("svm_spoke.deposit", () => {
assert.strictEqual(err.error.errorCode.code, "DepositsArePaused", "Expected error code DepositsArePaused");
}
});
// TODO: test invalid Deposit deadline for InvalidQuoteTimestamp
// TODO: test invalid InvalidFillDeadline

it("Fails to deposit tokens with InvalidQuoteTimestamp when quote timestamp is in the future", async () => {
const currentTime = await getCurrentTime(program, state);
const futureQuoteTimestamp = new BN(currentTime + 10); // 10 seconds in the future

depositData.quoteTimestamp = futureQuoteTimestamp;

try {
await program.methods
.depositV3(...Object.values(depositData))
.accounts(depositAccounts)
.signers([depositor])
.rpc();
assert.fail("Deposit should have failed due to InvalidQuoteTimestamp");
} catch (err) {
assert.include(
err.toString(),
"attempt to subtract with overflow",
"Expected underflow error due to future quote timestamp"
);
}
});

it("Fails to deposit tokens with quoteTimestamp is too old", async () => {
const currentTime = await getCurrentTime(program, state);
const futureQuoteTimestamp = new BN(currentTime - depositQuoteTimeBuffer.toNumber() - 1); // older than buffer.

depositData.quoteTimestamp = futureQuoteTimestamp;

try {
await program.methods
.depositV3(...Object.values(depositData))
.accounts(depositAccounts)
.signers([depositor])
.rpc();
assert.fail("Deposit should have failed due to InvalidQuoteTimestamp");
} catch (err) {
assert.include(err.toString(), "Error Code: InvalidQuoteTimestamp", "Expected InvalidQuoteTimestamp error");
}
});

it("Fails to deposit tokens with InvalidFillDeadline when fill deadline is invalid", async () => {
const currentTime = await getCurrentTime(program, state);

// Case 1: Fill deadline is older than the current time on the contract
let invalidFillDeadline = currentTime - 1; // 1 second before current time on the contract.
depositData.fillDeadline = new BN(invalidFillDeadline);
depositData.quoteTimestamp = new BN(currentTime - 1); // 1 second before current time on the contract to reset.

try {
await program.methods
.depositV3(...Object.values(depositData))
.accounts(depositAccounts)
.signers([depositor])
.rpc();
assert.fail("Deposit should have failed due to InvalidFillDeadline (past deadline)");
} catch (err) {
assert.include(err.toString(), "InvalidFillDeadline", "Expected InvalidFillDeadline error for past deadline");
}

// Case 2: Fill deadline is too far ahead (longer than fill_deadline_buffer + currentTime)
invalidFillDeadline = currentTime + fillDeadlineBuffer.toNumber() + 1; // 1 seconds beyond the buffer
depositData.fillDeadline = new BN(invalidFillDeadline);

try {
await program.methods
.depositV3(...Object.values(depositData))
.accounts(depositAccounts)
.signers([depositor])
.rpc();
assert.fail("Deposit should have failed due to InvalidFillDeadline (future deadline)");
} catch (err) {
assert.include(err.toString(), "InvalidFillDeadline", "Expected InvalidFillDeadline error for future deadline");
}
});
});
17 changes: 2 additions & 15 deletions test/svm/SvmSpoke.HandleReceiveMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { evmAddressToPublicKey } from "../../src/SvmUtils";
import { encodeMessageHeader } from "./cctpHelpers";
import { common } from "./SvmSpoke.common";

const { createRoutePda, getVaultAta } = common;
const { createRoutePda, getVaultAta, initializeState, crossDomainAdmin, remoteDomain, localDomain } = common;

describe("svm_spoke.handle_receive_message", () => {
anchor.setProvider(anchor.AnchorProvider.env());
Expand All @@ -24,11 +24,6 @@ describe("svm_spoke.handle_receive_message", () => {
let usedNonces: anchor.web3.PublicKey;
let selfAuthority: anchor.web3.PublicKey;
let eventAuthority: anchor.web3.PublicKey;
let seed: anchor.BN;
const chainId = new anchor.BN(420);
const remoteDomain = 0; // Ethereum
const localDomain = 5; // Solana
const crossDomainAdmin = evmAddressToPublicKey(ethers.Wallet.createRandom().address);
const firstNonce = 1;
const attestation = Buffer.alloc(0);
let nonce = firstNonce;
Expand All @@ -45,15 +40,7 @@ describe("svm_spoke.handle_receive_message", () => {
]);

beforeEach(async () => {
seed = new anchor.BN(Math.floor(Math.random() * 1000000));
const seeds = [Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)];
[state] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);

// Initialize the state with an initial number of deposits
await program.methods
.initialize(seed, new anchor.BN(0), chainId, remoteDomain, crossDomainAdmin, true)
.accounts({ state, signer: owner, systemProgram: anchor.web3.SystemProgram.programId })
.rpc();
state = await initializeState();

nonce += 1; // Increment CCTP nonce.

Expand Down
8 changes: 6 additions & 2 deletions test/svm/SvmSpoke.Ownership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { readProgramEvents } from "../../src/SvmUtils";

const { provider, program, owner, initializeState, crossDomainAdmin, assertSE } = common;

describe.only("svm_spoke.ownership", () => {
describe("svm_spoke.ownership", () => {
anchor.setProvider(provider);

const nonOwner = Keypair.generate();
Expand Down Expand Up @@ -38,7 +38,11 @@ describe.only("svm_spoke.ownership", () => {

// Assert other properties as needed
Object.keys(initialState).forEach((key) => {
assertSE(stateData[key], initialState[key], `${key} should match`);
if (key !== "testableMode") {
// We dont store testableMode in state.
const adjustedKey = key === "initialNumberOfDeposits" ? "numberOfDeposits" : key; // stored with diff key in state.
assertSE(stateData[adjustedKey], initialState[key], `${key} should match`);
}
});
});

Expand Down
2 changes: 1 addition & 1 deletion test/svm/SvmSpoke.TokenBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ describe("svm_spoke.token_bridge", () => {
const message = decodeMessageSentData(
(await messageTransmitterProgram.account.messageSent.fetch(messageSentEventData.publicKey)).message
);
assert.strictEqual(message.destinationDomain, remoteDomain, "Invalid destination domain");
assert.strictEqual(message.destinationDomain, remoteDomain.toNumber(), "Invalid destination domain");
assert.isTrue(message.messageBody.burnToken.equals(mint), "Invalid burn token");
assert.isTrue(message.messageBody.mintRecipient.equals(crossDomainAdmin), "Invalid mint recipient");
assert.strictEqual(message.messageBody.amount.toString(), pendingToHubPool.toString(), "Invalid amount");
Expand Down
31 changes: 20 additions & 11 deletions test/svm/SvmSpoke.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const provider = anchor.AnchorProvider.env();
const program = anchor.workspace.SvmSpoke as Program<SvmSpoke>;
const owner = provider.wallet.publicKey;
const chainId = new BN(420);
const remoteDomain = new BN(0);
const remoteDomain = new BN(0); // Ethereum
const localDomain = 5; // Solana
const crossDomainAdmin = evmAddressToPublicKey(ethers.Wallet.createRandom().address);

const seedBalance = 20000000;
Expand All @@ -21,12 +22,12 @@ 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));
const fillDeadline = new BN(Math.floor(Date.now() / 1000) + 600);
const exclusivityDeadline = new BN(Math.floor(Date.now() / 1000) + 300);
const quoteTimestamp = new BN(Math.floor(Date.now() / 1000) - 10); // 10 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 message = Buffer.from("Test message");
const depositQuoteTimeBuffer = new BN(3600);
const fillDeadlineBuffer = new BN(3600 * 4);
const depositQuoteTimeBuffer = new BN(3600); // 1 hour.
const fillDeadlineBuffer = new BN(3600 * 4); // 4 hours.

const initializeState = async (
seed?: BN,
Expand All @@ -53,12 +54,12 @@ const initializeState = async (
depositQuoteTimeBuffer,
fillDeadlineBuffer,
};
await program.methods
.initialize(...Object.values(initialState))
.accounts({ state, signer: owner, systemProgram: anchor.web3.SystemProgram.programId })
.rpc();
return state;
}
await program.methods
.initialize(...[actualSeed, ...Object.values(initialState)])
.accounts({ state: state as any, signer: owner, systemProgram: anchor.web3.SystemProgram.programId })
.rpc();
return state;
};

const createRoutePda = (originToken: PublicKey, routeChainId: BN) => {
Expand All @@ -76,6 +77,10 @@ async function setCurrentTime(program: Program<SvmSpoke>, state: any, signer: an
await program.methods.setCurrentTime(newTime).accounts({ state, signer: signer.publicKey }).signers([signer]).rpc();
}

async function getCurrentTime(program: Program<SvmSpoke>, state: any) {
return (await program.account.state.fetch(state)).currentTime;
}

function assertSE(a: any, b: any, errorMessage: string) {
assert.strictEqual(a.toString(), b.toString(), errorMessage);
}
Expand All @@ -87,6 +92,7 @@ export const common = {
owner,
chainId,
remoteDomain,
localDomain,
crossDomainAdmin,
seedBalance,
destinationChainId,
Expand All @@ -99,10 +105,13 @@ export const common = {
fillDeadline,
exclusivityDeadline,
message,
depositQuoteTimeBuffer,
fillDeadlineBuffer,
initializeState,
createRoutePda,
getVaultAta,
setCurrentTime,
getCurrentTime,
assert,
assertSE,
depositData: {
Expand Down

0 comments on commit 0db1965

Please sign in to comment.