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

fix(svm): no transfer on self-relay #678

Merged
merged 1 commit into from
Oct 22, 2024
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
37 changes: 19 additions & 18 deletions programs/svm-spoke/src/instructions/fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,24 +124,25 @@ pub fn fill_v3_relay(
_ => FillType::FastFill,
};

// TODO: EVM SpokePool skips the transfer if relayer and receiver are the same, should we do the same here?
// Invoke the transfer_checked instruction on the token program
let transfer_accounts = TransferChecked {
// TODO: check what happens if the relayer and recipient are the same
from: ctx.accounts.relayer_token_account.to_account_info(),
mint: ctx.accounts.mint_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
};
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
);
transfer_checked(
cpi_context,
relay_data.output_amount,
ctx.accounts.mint_account.decimals,
)?;
// If relayer and receiver are the same, there is no need to do the transfer. This might be a case when relayers
// intentionally self-relay in a capital efficient way (no need to have funds on the destination).
if ctx.accounts.relayer_token_account.key() != ctx.accounts.recipient_token_account.key() {
let transfer_accounts = TransferChecked {
from: ctx.accounts.relayer_token_account.to_account_info(),
mint: ctx.accounts.mint_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
};
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
);
transfer_checked(
cpi_context,
relay_data.output_amount,
ctx.accounts.mint_account.decimals,
)?;
}

// Update the fill status to Filled and set the relayer
fill_status_account.status = FillStatus::Filled;
Expand Down
34 changes: 34 additions & 0 deletions test/svm/SvmSpoke.Fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,38 @@ describe("svm_spoke.fill", () => {
assert.strictEqual(err.error.errorCode.code, "InvalidMint", "Expected error code InvalidMint");
}
});

it("Self-relay does not invoke token transfer", async () => {
// Set recipient to be the same as relayer.
updateRelayData({ ...relayData, depositor: relayer.publicKey, recipient: relayer.publicKey });
accounts.recipient = relayer.publicKey;
accounts.recipientTA = relayerTA;

// Store relayer's balance before the fill
const iRelayerBalance = (await getAccount(connection, relayerTA)).amount;

const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId));
const txSignature = await program.methods
.fillV3Relay(relayHash, relayData, new BN(1))
.accounts(accounts)
.signers([relayer])
.rpc();

// Verify relayer's balance after the fill is unchanged
const fRelayerBalance = (await getAccount(connection, relayerTA)).amount;
assertSE(fRelayerBalance, iRelayerBalance, "Relayer's balance should not have changed");

await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for tx processing
const txResult = await connection.getTransaction(txSignature, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});
if (txResult === null || txResult.meta === null) throw new Error("Transaction meta not confirmed");
if (txResult.meta.logMessages === null || txResult.meta.logMessages === undefined)
throw new Error("Transaction logs not found");
assert.isTrue(
txResult.meta.logMessages.every((log) => !log.includes(`Program ${TOKEN_PROGRAM_ID} invoke`)),
"Token Program should not be invoked"
);
});
});
Loading