From de9794d64d4e8a1d8b9cc3faf730c82b295ef7f6 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Fri, 25 Oct 2024 10:42:09 +0100 Subject: [PATCH 1/4] test: multi-chain verifiable merkle tree (#688) * test: multi-chain verifiable merkle tree Signed-off-by: Pablo Maldonado * feat: add sorted test svm Signed-off-by: Pablo Maldonado * refactor: import Signed-off-by: Pablo Maldonado --------- Signed-off-by: Pablo Maldonado --- .../hardhat/SpokePool.ExecuteRootBundle.ts | 73 +++++ test/svm/SvmSpoke.Bundle.ts | 297 ++++++++++++++---- test/svm/utils.ts | 90 +++++- 3 files changed, 387 insertions(+), 73 deletions(-) diff --git a/test/evm/hardhat/SpokePool.ExecuteRootBundle.ts b/test/evm/hardhat/SpokePool.ExecuteRootBundle.ts index 1a2ae5c18..61dc527ec 100644 --- a/test/evm/hardhat/SpokePool.ExecuteRootBundle.ts +++ b/test/evm/hardhat/SpokePool.ExecuteRootBundle.ts @@ -1,4 +1,5 @@ import { SignerWithAddress, seedContract, toBN, expect, Contract, ethers, BigNumber } from "../../../utils/utils"; +import { buildRelayerRefundMerkleTree } from "../../svm/utils"; import * as consts from "./constants"; import { spokePoolFixture } from "./fixtures/SpokePool.Fixture"; import { buildRelayerRefundTree, buildRelayerRefundLeaves } from "./MerkleLib.utils"; @@ -76,6 +77,78 @@ describe("SpokePool Root Bundle Execution", function () { expect(tokensBridgedEvents.length).to.equal(1); }); + it("Execute relayer root correctly with mixed solana and evm leaves", async function () { + const totalDistributions = 10; + const evmDistributions = totalDistributions / 2; + const { relayerRefundLeaves: leaves, merkleTree: tree } = buildRelayerRefundMerkleTree({ + totalEvmDistributions: evmDistributions, + totalSolanaDistributions: evmDistributions, + mixLeaves: true, + chainId: destinationChainId, + evmTokenAddress: destErc20.address, + evmRelayers: [relayer.address, rando.address], + evmRefundAmounts: [consts.amountToRelay.div(evmDistributions), consts.amountToRelay.div(evmDistributions)], + }); + + const leavesRefundAmount = leaves + .filter((leaf) => !leaf.isSolana) + .map((leaf) => (leaf.refundAmounts as BigNumber[]).reduce((bn1, bn2) => bn1.add(bn2), toBN(0))) + .reduce((bn1, bn2) => bn1.add(bn2), toBN(0)); + + // Store new tree. + await spokePool.connect(dataWorker).relayRootBundle( + tree.getHexRoot(), // relayer refund root. Generated from the merkle tree constructed before. + consts.mockSlowRelayRoot + ); + + // Distribute all non-Solana leaves. + for (let i = 0; i < leaves.length; i++) { + if (leaves[i].isSolana) continue; + await spokePool.connect(dataWorker).executeRelayerRefundLeaf(0, leaves[i], tree.getHexProof(leaves[i])); + } + + // Relayers should be refunded + expect(await destErc20.balanceOf(spokePool.address)).to.equal(consts.amountHeldByPool.sub(leavesRefundAmount)); + expect(await destErc20.balanceOf(relayer.address)).to.equal(consts.amountToRelay); + expect(await destErc20.balanceOf(rando.address)).to.equal(consts.amountToRelay); + }); + + it("Execute relayer root correctly with sorted solana and evm leaves", async function () { + const totalDistributions = 10; + const evmDistributions = totalDistributions / 2; + const { relayerRefundLeaves: leaves, merkleTree: tree } = buildRelayerRefundMerkleTree({ + totalEvmDistributions: evmDistributions, + totalSolanaDistributions: evmDistributions, + mixLeaves: false, + chainId: destinationChainId, + evmTokenAddress: destErc20.address, + evmRelayers: [relayer.address, rando.address], + evmRefundAmounts: [consts.amountToRelay.div(evmDistributions), consts.amountToRelay.div(evmDistributions)], + }); + + const leavesRefundAmount = leaves + .filter((leaf) => !leaf.isSolana) + .map((leaf) => (leaf.refundAmounts as BigNumber[]).reduce((bn1, bn2) => bn1.add(bn2), toBN(0))) + .reduce((bn1, bn2) => bn1.add(bn2), toBN(0)); + + // Store new tree. + await spokePool.connect(dataWorker).relayRootBundle( + tree.getHexRoot(), // relayer refund root. Generated from the merkle tree constructed before. + consts.mockSlowRelayRoot + ); + + // Distribute all non-Solana leaves. + for (let i = 0; i < leaves.length; i++) { + if (leaves[i].isSolana) continue; + await spokePool.connect(dataWorker).executeRelayerRefundLeaf(0, leaves[i], tree.getHexProof(leaves[i])); + } + + // Relayers should be refunded + expect(await destErc20.balanceOf(spokePool.address)).to.equal(consts.amountHeldByPool.sub(leavesRefundAmount)); + expect(await destErc20.balanceOf(relayer.address)).to.equal(consts.amountToRelay); + expect(await destErc20.balanceOf(rando.address)).to.equal(consts.amountToRelay); + }); + it("Execution rejects invalid leaf, tree, proof combinations", async function () { const { leaves, tree } = await constructSimpleTree(destErc20, destinationChainId); await spokePool.connect(dataWorker).relayRootBundle( diff --git a/test/svm/SvmSpoke.Bundle.ts b/test/svm/SvmSpoke.Bundle.ts index 5ad50c5d9..ec51bb726 100644 --- a/test/svm/SvmSpoke.Bundle.ts +++ b/test/svm/SvmSpoke.Bundle.ts @@ -1,6 +1,12 @@ import * as anchor from "@coral-xyz/anchor"; -import * as crypto from "crypto"; -import { BN, web3, Wallet, AnchorProvider } from "@coral-xyz/anchor"; +import { AnchorProvider, BN, Wallet, web3 } from "@coral-xyz/anchor"; +import { + createMint, + getAssociatedTokenAddressSync, + getOrCreateAssociatedTokenAccount, + mintTo, + TOKEN_PROGRAM_ID, +} from "@solana/spl-token"; import { AddressLookupTableProgram, ComputeBudgetProgram, @@ -10,26 +16,19 @@ import { VersionedTransaction, } from "@solana/web3.js"; import { assert } from "chai"; +import * as crypto from "crypto"; import { common } from "./SvmSpoke.common"; -import { MerkleTree } from "@uma/common/dist/MerkleTree"; -import { - createMint, - getAssociatedTokenAddressSync, - getOrCreateAssociatedTokenAccount, - mintTo, - TOKEN_PROGRAM_ID, -} from "@solana/spl-token"; import { + buildRelayerRefundMerkleTree, loadExecuteRelayerRefundLeafParams, - relayerRefundHashFn, - randomAddress, randomBigInt, - RelayerRefundLeaf, - RelayerRefundLeafSolana, - RelayerRefundLeafType, readEvents, readProgramEvents, + relayerRefundHashFn, + RelayerRefundLeafSolana, + RelayerRefundLeafType, } from "./utils"; +import { MerkleTree } from "../../utils"; const { provider, program, owner, initializeState, connection, chainId, assertSE } = common; @@ -306,41 +305,32 @@ describe("svm_spoke.bundle", () => { }); it("Test Merkle Proof Verification", async () => { - const relayerRefundLeaves: RelayerRefundLeafType[] = []; const solanaDistributions = 50; const evmDistributions = 50; const solanaLeafNumber = 13; + const { relayerRefundLeaves, merkleTree } = buildRelayerRefundMerkleTree({ + totalEvmDistributions: evmDistributions, + totalSolanaDistributions: solanaDistributions, + mixLeaves: false, + chainId: chainId.toNumber(), + mint, + svmRelayers: [relayerTA, relayerTB], + svmRefundAmounts: [new BN(randomBigInt(2).toString()), new BN(randomBigInt(2).toString())], + }); - for (let i = 0; i < solanaDistributions + 1; i++) { - relayerRefundLeaves.push({ - isSolana: true, - leafId: new BN(i), - chainId: chainId, - amountToReturn: new BN(randomBigInt(2).toString()), - mintPublicKey: mint, - refundAccounts: [relayerTA, relayerTB], - refundAmounts: [new BN(randomBigInt(2).toString()), new BN(randomBigInt(2).toString())], - }); - } - const invalidRelayerRefundLeaf = relayerRefundLeaves.pop()!; - - for (let i = 0; i < evmDistributions; i++) { - relayerRefundLeaves.push({ - isSolana: false, - leafId: BigInt(i), - chainId: randomBigInt(2), - amountToReturn: randomBigInt(), - l2TokenAddress: randomAddress(), - refundAddresses: [randomAddress(), randomAddress()], - refundAmounts: [randomBigInt(), randomBigInt()], - } as RelayerRefundLeaf); - } - - const merkleTree = new MerkleTree(relayerRefundLeaves, relayerRefundHashFn); + const invalidRelayerRefundLeaf = { + isSolana: true, + leafId: new BN(solanaDistributions + 1), + chainId: chainId, + amountToReturn: new BN(0), + mintPublicKey: mint, + refundAccounts: [relayerTA, relayerTB], + refundAmounts: [new BN(randomBigInt(2).toString()), new BN(randomBigInt(2).toString())], + } as RelayerRefundLeafSolana; const root = merkleTree.getRoot(); const proof = merkleTree.getProof(relayerRefundLeaves[solanaLeafNumber]); - const leaf = relayerRefundLeaves[13] as RelayerRefundLeafSolana; + const leaf = relayerRefundLeaves[solanaLeafNumber] as RelayerRefundLeafSolana; const proofAsNumbers = proof.map((p) => Array.from(p)); let stateAccountData = await program.account.state.fetch(state); @@ -461,6 +451,196 @@ describe("svm_spoke.bundle", () => { } }); + it("Test Merkle Proof Verification with Mixed Solana and EVM Leaves", async () => { + const evmDistributions = 5; + const solanaDistributions = 5; + const { relayerRefundLeaves, merkleTree } = buildRelayerRefundMerkleTree({ + totalEvmDistributions: evmDistributions, + totalSolanaDistributions: solanaDistributions, + mixLeaves: true, + chainId: chainId.toNumber(), + mint, + svmRelayers: [relayerTA, relayerTB], + svmRefundAmounts: [new BN(randomBigInt(2).toString()), new BN(randomBigInt(2).toString())], + }); + + const root = merkleTree.getRoot(); + let stateAccountData = await program.account.state.fetch(state); + const rootBundleId = stateAccountData.rootBundleId; + const rootBundleIdBuffer = Buffer.alloc(4); + rootBundleIdBuffer.writeUInt32LE(rootBundleId); + const seeds = [Buffer.from("root_bundle"), state.toBuffer(), rootBundleIdBuffer]; + const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId); + + // Relay root bundle + let relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId }; + await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc(); + + const remainingAccounts = [ + { pubkey: relayerTA, isWritable: true, isSigner: false }, + { pubkey: relayerTB, isWritable: true, isSigner: false }, + ]; + + const iVaultBal = (await connection.getTokenAccountBalance(vault)).value.amount; + const iRelayerABal = (await connection.getTokenAccountBalance(relayerTA)).value.amount; + const iRelayerBBal = (await connection.getTokenAccountBalance(relayerTB)).value.amount; + + // Execute each Solana leaf + for (let i = 0; i < relayerRefundLeaves.length; i += 1) { + // Only Solana leaves + if (!relayerRefundLeaves[i].isSolana) continue; + + const leaf = relayerRefundLeaves[i] as RelayerRefundLeafSolana; + const proof = merkleTree.getProof(leaf); + const proofAsNumbers = proof.map((p) => Array.from(p)); + + let executeRelayerRefundLeafAccounts = { + state: state, + rootBundle: rootBundle, + signer: owner, + vault: vault, + tokenProgram: TOKEN_PROGRAM_ID, + mint: mint, + transferLiability, + systemProgram: web3.SystemProgram.programId, + program: program.programId, + }; + await loadExecuteRelayerRefundLeafParams(program, owner, stateAccountData.rootBundleId, leaf, proofAsNumbers); + + await program.methods + .executeRelayerRefundLeaf() + .accounts(executeRelayerRefundLeafAccounts) + .remainingAccounts(remainingAccounts) + .rpc(); + } + + const fVaultBal = (await connection.getTokenAccountBalance(vault)).value.amount; + const fRelayerABal = (await connection.getTokenAccountBalance(relayerTA)).value.amount; + const fRelayerBBal = (await connection.getTokenAccountBalance(relayerTB)).value.amount; + + const totalRefund = relayerRefundLeaves + .filter((leaf) => leaf.isSolana) + .reduce((acc, leaf) => acc.add((leaf.refundAmounts[0] as BN).add(leaf.refundAmounts[1] as BN)), new BN(0)) + .toString(); + + assert.strictEqual(BigInt(iVaultBal) - BigInt(fVaultBal), BigInt(totalRefund), "Vault balance"); + assert.strictEqual( + BigInt(fRelayerABal) - BigInt(iRelayerABal), + BigInt( + relayerRefundLeaves + .filter((leaf) => leaf.isSolana) + .reduce((acc, leaf) => acc.add(leaf.refundAmounts[0] as BN), new BN(0)) + .toString() + ), + "Relayer A bal" + ); + assert.strictEqual( + BigInt(fRelayerBBal) - BigInt(iRelayerBBal), + BigInt( + relayerRefundLeaves + .filter((leaf) => leaf.isSolana) + .reduce((acc, leaf) => acc.add(leaf.refundAmounts[1] as BN), new BN(0)) + .toString() + ), + "Relayer B bal" + ); + }); + + it("Test Merkle Proof Verification with Sorted Solana and EVM Leaves", async () => { + const evmDistributions = 5; + const solanaDistributions = 5; + const { relayerRefundLeaves, merkleTree } = buildRelayerRefundMerkleTree({ + totalEvmDistributions: evmDistributions, + totalSolanaDistributions: solanaDistributions, + mixLeaves: false, + chainId: chainId.toNumber(), + mint, + svmRelayers: [relayerTA, relayerTB], + svmRefundAmounts: [new BN(randomBigInt(2).toString()), new BN(randomBigInt(2).toString())], + }); + + const root = merkleTree.getRoot(); + let stateAccountData = await program.account.state.fetch(state); + const rootBundleId = stateAccountData.rootBundleId; + const rootBundleIdBuffer = Buffer.alloc(4); + rootBundleIdBuffer.writeUInt32LE(rootBundleId); + const seeds = [Buffer.from("root_bundle"), state.toBuffer(), rootBundleIdBuffer]; + const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId); + + // Relay root bundle + let relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId }; + await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc(); + + const remainingAccounts = [ + { pubkey: relayerTA, isWritable: true, isSigner: false }, + { pubkey: relayerTB, isWritable: true, isSigner: false }, + ]; + + const iVaultBal = (await connection.getTokenAccountBalance(vault)).value.amount; + const iRelayerABal = (await connection.getTokenAccountBalance(relayerTA)).value.amount; + const iRelayerBBal = (await connection.getTokenAccountBalance(relayerTB)).value.amount; + + // Execute each Solana leaf + for (let i = 0; i < relayerRefundLeaves.length; i += 1) { + // Only Solana leaves + if (!relayerRefundLeaves[i].isSolana) continue; + + const leaf = relayerRefundLeaves[i] as RelayerRefundLeafSolana; + const proof = merkleTree.getProof(leaf); + const proofAsNumbers = proof.map((p) => Array.from(p)); + + let executeRelayerRefundLeafAccounts = { + state: state, + rootBundle: rootBundle, + signer: owner, + vault: vault, + tokenProgram: TOKEN_PROGRAM_ID, + mint: mint, + transferLiability, + systemProgram: web3.SystemProgram.programId, + program: program.programId, + }; + await loadExecuteRelayerRefundLeafParams(program, owner, stateAccountData.rootBundleId, leaf, proofAsNumbers); + + await program.methods + .executeRelayerRefundLeaf() + .accounts(executeRelayerRefundLeafAccounts) + .remainingAccounts(remainingAccounts) + .rpc(); + } + + const fVaultBal = (await connection.getTokenAccountBalance(vault)).value.amount; + const fRelayerABal = (await connection.getTokenAccountBalance(relayerTA)).value.amount; + const fRelayerBBal = (await connection.getTokenAccountBalance(relayerTB)).value.amount; + + const totalRefund = relayerRefundLeaves + .filter((leaf) => leaf.isSolana) + .reduce((acc, leaf) => acc.add((leaf.refundAmounts[0] as BN).add(leaf.refundAmounts[1] as BN)), new BN(0)) + .toString(); + + assert.strictEqual(BigInt(iVaultBal) - BigInt(fVaultBal), BigInt(totalRefund), "Vault balance"); + assert.strictEqual( + BigInt(fRelayerABal) - BigInt(iRelayerABal), + BigInt( + relayerRefundLeaves + .filter((leaf) => leaf.isSolana) + .reduce((acc, leaf) => acc.add(leaf.refundAmounts[0] as BN), new BN(0)) + .toString() + ), + "Relayer A bal" + ); + assert.strictEqual( + BigInt(fRelayerBBal) - BigInt(iRelayerBBal), + BigInt( + relayerRefundLeaves + .filter((leaf) => leaf.isSolana) + .reduce((acc, leaf) => acc.add(leaf.refundAmounts[1] as BN), new BN(0)) + .toString() + ), + "Relayer B bal" + ); + }); + it("Execute Leaf Refunds Relayers with invalid chain id", async () => { const relayerRefundLeaves: RelayerRefundLeafType[] = []; const relayerARefund = new BN(400000); @@ -787,7 +967,6 @@ describe("svm_spoke.bundle", () => { describe("Execute Max Refunds", () => { const executeMaxRefunds = async (refundType: RefundType) => { - const relayerRefundLeaves: RelayerRefundLeafType[] = []; // Higher refund count hits inner instruction size limit when doing `emit_cpi` on public devnet. On localnet this is // not an issue, but we hit out of memory panic above 31 refunds. This should not be an issue as currently Across // protocol does not expect this to be above 25. @@ -832,30 +1011,16 @@ describe("svm_spoke.bundle", () => { refundAmounts.push(new BN(randomBigInt(2).toString())); } - relayerRefundLeaves.push({ - isSolana: true, - leafId: new BN(0), - chainId: chainId, - amountToReturn: new BN(0), - mintPublicKey: mint, - refundAccounts: tokenAccounts, - refundAmounts: refundAmounts, + const { relayerRefundLeaves, merkleTree } = buildRelayerRefundMerkleTree({ + totalEvmDistributions: evmDistributions, + totalSolanaDistributions: solanaDistributions, + mixLeaves: false, + chainId: chainId.toNumber(), + mint, + svmRelayers: tokenAccounts, + svmRefundAmounts: refundAmounts, }); - for (let i = 0; i < evmDistributions; i++) { - relayerRefundLeaves.push({ - isSolana: false, - leafId: BigInt(i + 1), // The first leaf is for Solana, so we start EVM leaves at 1. - chainId: randomBigInt(2), - amountToReturn: randomBigInt(), - l2TokenAddress: randomAddress(), - refundAddresses: [randomAddress(), randomAddress()], - refundAmounts: [randomBigInt(), randomBigInt()], - } as RelayerRefundLeaf); - } - - const merkleTree = new MerkleTree(relayerRefundLeaves, relayerRefundHashFn); - const root = merkleTree.getRoot(); const proof = merkleTree.getProof(relayerRefundLeaves[0]); const leaf = relayerRefundLeaves[0] as RelayerRefundLeafSolana; diff --git a/test/svm/utils.ts b/test/svm/utils.ts index 135552a1d..b29164953 100644 --- a/test/svm/utils.ts +++ b/test/svm/utils.ts @@ -1,6 +1,6 @@ import { BN, Program } from "@coral-xyz/anchor"; -import { PublicKey } from "@solana/web3.js"; -import { ethers } from "ethers"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { BigNumber, ethers } from "ethers"; import * as crypto from "crypto"; import { SvmSpoke } from "../../target/types/svm_spoke"; @@ -11,6 +11,9 @@ import { findProgramAddress, LargeAccountsCoder, } from "../../src/SvmUtils"; +import { MerkleTree } from "@uma/common"; +import { getParamType, keccak256 } from "../../test-utils"; +import { ParamType } from "ethers/lib/utils"; export { readEvents, readProgramEvents, calculateRelayHashUint8Array, findProgramAddress }; @@ -50,10 +53,10 @@ export function randomBigInt(bytes = 8, signed = false) { export interface RelayerRefundLeaf { isSolana: boolean; - amountToReturn: bigint; - chainId: bigint; - refundAmounts: bigint[]; - leafId: bigint; + amountToReturn: BigNumber; + chainId: BigNumber; + refundAmounts: BigNumber[]; + leafId: BigNumber; l2TokenAddress: string; refundAddresses: string[]; } @@ -74,6 +77,79 @@ export function convertLeafIdToNumber(leaf: RelayerRefundLeafSolana) { return { ...leaf, leafId: leaf.leafId.toNumber() }; } +export function buildRelayerRefundMerkleTree({ + totalEvmDistributions, + totalSolanaDistributions, + mixLeaves, + chainId, + mint, + svmRelayers, + evmRelayers, + evmTokenAddress, + evmRefundAmounts, + svmRefundAmounts, +}: { + totalEvmDistributions: number; + totalSolanaDistributions: number; + chainId: number; + mixLeaves?: boolean; + mint?: PublicKey; + svmRelayers?: PublicKey[]; + evmRelayers?: string[]; + evmTokenAddress?: string; + evmRefundAmounts?: BigNumber[]; + svmRefundAmounts?: BN[]; +}): { relayerRefundLeaves: RelayerRefundLeafType[]; merkleTree: MerkleTree } { + const relayerRefundLeaves: RelayerRefundLeafType[] = []; + + const createSolanaLeaf = (index: number) => ({ + isSolana: true, + leafId: new BN(index), + chainId: new BN(chainId), + amountToReturn: new BN(0), + mintPublicKey: mint ?? Keypair.generate().publicKey, + refundAccounts: svmRelayers || [Keypair.generate().publicKey, Keypair.generate().publicKey], + refundAmounts: svmRefundAmounts || [new BN(randomBigInt(2).toString()), new BN(randomBigInt(2).toString())], + }); + + const createEvmLeaf = (index: number) => + ({ + isSolana: false, + leafId: BigNumber.from(index), + chainId: BigNumber.from(chainId), + amountToReturn: BigNumber.from(0), + l2TokenAddress: evmTokenAddress ?? randomAddress(), + refundAddresses: evmRelayers || [randomAddress(), randomAddress()], + refundAmounts: evmRefundAmounts || [BigNumber.from(randomBigInt()), BigNumber.from(randomBigInt())], + } as RelayerRefundLeaf); + + if (mixLeaves) { + let solanaIndex = 0; + let evmIndex = 0; + const totalDistributions = totalSolanaDistributions + totalEvmDistributions; + for (let i = 0; i < totalDistributions; i++) { + if (solanaIndex < totalSolanaDistributions && (i % 2 === 0 || evmIndex >= totalEvmDistributions)) { + relayerRefundLeaves.push(createSolanaLeaf(solanaIndex)); + solanaIndex++; + } else if (evmIndex < totalEvmDistributions) { + relayerRefundLeaves.push(createEvmLeaf(evmIndex)); + evmIndex++; + } + } + } else { + for (let i = 0; i < totalSolanaDistributions; i++) { + relayerRefundLeaves.push(createSolanaLeaf(i)); + } + for (let i = 0; i < totalEvmDistributions; i++) { + relayerRefundLeaves.push(createEvmLeaf(i + totalSolanaDistributions)); + } + } + + const merkleTree = new MerkleTree(relayerRefundLeaves, relayerRefundHashFn); + + return { relayerRefundLeaves, merkleTree }; +} + export function calculateRelayerRefundLeafHashUint8Array(relayData: RelayerRefundLeafSolana): string { const refundAmountsBuffer = Buffer.concat( relayData.refundAmounts.map((amount) => { @@ -103,7 +179,7 @@ export const relayerRefundHashFn = (input: RelayerRefundLeaf | RelayerRefundLeaf const abiCoder = new ethers.utils.AbiCoder(); const encodedData = abiCoder.encode( [ - "tuple(uint256 leafId, uint256 chainId, uint256 amountToReturn, address l2TokenAddress, address[] refundAddresses, uint256[] refundAmounts)", + "tuple( uint256 amountToReturn, uint256 chainId, uint256[] refundAmounts, uint256 leafId, address l2TokenAddress, address[] refundAddresses)", ], [ { From 77fb3d76cb2af7948b2a6a03b97e41fb2df3a25d Mon Sep 17 00:00:00 2001 From: Reinis Martinsons <77973553+Reinis-FRP@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:13:02 +0300 Subject: [PATCH 2/4] fix(svm): remove redundant accounts (#689) * fix: remove recipient from fill accounts Signed-off-by: Reinis Martinsons * fix: remove recipient from slow fill accounts Signed-off-by: Reinis Martinsons * fix: remove relayer from fill accounts Signed-off-by: Reinis Martinsons --------- Signed-off-by: Reinis Martinsons --- programs/svm-spoke/src/error.rs | 2 -- programs/svm-spoke/src/instructions/fill.rs | 11 ++------- .../svm-spoke/src/instructions/slow_fill.rs | 7 +----- scripts/svm/simpleFill.ts | 2 -- test/svm/SvmSpoke.Fill.ts | 24 +++++++------------ test/svm/SvmSpoke.SlowFill.ts | 18 ++++---------- 6 files changed, 17 insertions(+), 47 deletions(-) diff --git a/programs/svm-spoke/src/error.rs b/programs/svm-spoke/src/error.rs index cd31886b2..5dfff6915 100644 --- a/programs/svm-spoke/src/error.rs +++ b/programs/svm-spoke/src/error.rs @@ -68,8 +68,6 @@ pub enum CustomError { ExceededPendingBridgeAmount, #[msg("Deposits are currently paused!")] DepositsArePaused, - #[msg("Invalid fill recipient!")] - InvalidFillRecipient, #[msg("Invalid quote timestamp!")] InvalidQuoteTimestamp, #[msg("Ivalid fill deadline!")] diff --git a/programs/svm-spoke/src/instructions/fill.rs b/programs/svm-spoke/src/instructions/fill.rs index df47a0843..3361917aa 100644 --- a/programs/svm-spoke/src/instructions/fill.rs +++ b/programs/svm-spoke/src/instructions/fill.rs @@ -27,13 +27,6 @@ pub struct FillV3Relay<'info> { #[account(mut)] pub signer: Signer<'info>, - pub relayer: SystemAccount<'info>, // TODO: should this be the same as signer? - - #[account( - address = relay_data.recipient @ CustomError::InvalidFillRecipient - )] - pub recipient: SystemAccount<'info>, // TODO: this might be redundant. - #[account( token::token_program = token_program, // TODO: consistent token imports address = relay_data.output_token @ CustomError::InvalidMint @@ -43,7 +36,7 @@ pub struct FillV3Relay<'info> { #[account( mut, associated_token::mint = mint_account, // TODO: consistent token imports - associated_token::authority = relayer, + associated_token::authority = signer, associated_token::token_program = token_program )] pub relayer_token_account: InterfaceAccount<'info, TokenAccount>, @@ -51,7 +44,7 @@ pub struct FillV3Relay<'info> { #[account( mut, associated_token::mint = mint_account, - associated_token::authority = recipient, // TODO: use relay_data.recipient + associated_token::authority = relay_data.recipient, associated_token::token_program = token_program )] pub recipient_token_account: InterfaceAccount<'info, TokenAccount>, diff --git a/programs/svm-spoke/src/instructions/slow_fill.rs b/programs/svm-spoke/src/instructions/slow_fill.rs index 9f20a2e38..bbe9e4d0e 100644 --- a/programs/svm-spoke/src/instructions/slow_fill.rs +++ b/programs/svm-spoke/src/instructions/slow_fill.rs @@ -148,11 +148,6 @@ pub struct ExecuteV3SlowRelayLeaf<'info> { )] pub fill_status: Account<'info, FillStatusAccount>, - #[account( - address = slow_fill_leaf.relay_data.recipient @ CustomError::InvalidFillRecipient - )] - pub recipient: SystemAccount<'info>, - #[account( token::token_program = token_program, address = slow_fill_leaf.relay_data.output_token @ CustomError::InvalidMint @@ -162,7 +157,7 @@ pub struct ExecuteV3SlowRelayLeaf<'info> { #[account( mut, associated_token::mint = mint, - associated_token::authority = recipient, + associated_token::authority = slow_fill_leaf.relay_data.recipient, associated_token::token_program = token_program )] pub recipient_token_account: InterfaceAccount<'info, TokenAccount>, diff --git a/scripts/svm/simpleFill.ts b/scripts/svm/simpleFill.ts index 30271a464..3e7a291e9 100644 --- a/scripts/svm/simpleFill.ts +++ b/scripts/svm/simpleFill.ts @@ -125,8 +125,6 @@ async function fillV3Relay(): Promise { .accounts({ state: statePda, signer: signer, - relayer: signer, - recipient: recipient, mintAccount: outputToken, relayerTokenAccount: relayerTokenAccount, recipientTokenAccount: recipientTokenAccount, diff --git a/test/svm/SvmSpoke.Fill.ts b/test/svm/SvmSpoke.Fill.ts index 772e1135d..3bc496cad 100644 --- a/test/svm/SvmSpoke.Fill.ts +++ b/test/svm/SvmSpoke.Fill.ts @@ -37,11 +37,9 @@ describe("svm_spoke.fill", () => { accounts = { state, signer: relayer.publicKey, - relayer: relayer.publicKey, - recipient: recipient, mintAccount: mint, - relayerTA: relayerTA, - recipientTA: recipientTA, + relayerTokenAccount: relayerTA, + recipientTokenAccount: recipientTA, fillStatus: fillStatusPDA, tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, @@ -138,8 +136,7 @@ describe("svm_spoke.fill", () => { it("Fails to fill a V3 relay by non-exclusive relayer before exclusivity deadline", async () => { accounts.signer = otherRelayer.publicKey; - accounts.relayer = otherRelayer.publicKey; - accounts.relayerTA = otherRelayerTA; + accounts.relayerTokenAccount = otherRelayerTA; const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); try { @@ -158,8 +155,7 @@ describe("svm_spoke.fill", () => { updateRelayData({ ...relayData, exclusivityDeadline: new BN(Math.floor(Date.now() / 1000) - 100) }); accounts.signer = otherRelayer.publicKey; - accounts.relayer = otherRelayer.publicKey; - accounts.relayerTA = otherRelayerTA; + accounts.relayerTokenAccount = otherRelayerTA; const recipientAccountBefore = await getAccount(connection, recipientTA); const relayerAccountBefore = await getAccount(connection, otherRelayerTA); @@ -274,7 +270,7 @@ describe("svm_spoke.fill", () => { } }); - it("Fails to fill a relay to wrong recipient", async () => { + it("Fails to fill a relay to wrong recipient token account", async () => { const relayHash = calculateRelayHashUint8Array(relayData, chainId); // Create new accounts as derived from wrong recipient. @@ -287,16 +283,15 @@ describe("svm_spoke.fill", () => { .fillV3Relay(Array.from(relayHash), relayData, new BN(1)) .accounts({ ...accounts, - recipient: wrongRecipient, - recipientTA: wrongRecipientTA, + recipientTokenAccount: wrongRecipientTA, fillStatus: wrongFillStatus, }) .signers([relayer]) .rpc(); - assert.fail("Should not be able to fill relay to wrong recipient"); + assert.fail("Should not be able to fill relay to wrong recipient token account"); } catch (err: any) { assert.instanceOf(err, anchor.AnchorError); - assert.strictEqual(err.error.errorCode.code, "InvalidFillRecipient", "Expected error code InvalidFillRecipient"); + assert.strictEqual(err.error.errorCode.code, "ConstraintTokenOwner", "Expected error code ConstraintTokenOwner"); } }); @@ -331,8 +326,7 @@ describe("svm_spoke.fill", () => { 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; + accounts.recipientTokenAccount = relayerTA; // Store relayer's balance before the fill const iRelayerBalance = (await getAccount(connection, relayerTA)).amount; diff --git a/test/svm/SvmSpoke.SlowFill.ts b/test/svm/SvmSpoke.SlowFill.ts index 618694580..8ea0c89ad 100644 --- a/test/svm/SvmSpoke.SlowFill.ts +++ b/test/svm/SvmSpoke.SlowFill.ts @@ -65,11 +65,9 @@ describe("svm_spoke.slow_fill", () => { fillAccounts = { state, signer: relayer.publicKey, - relayer: relayer.publicKey, - recipient: relayData.recipient, // This could be different from global recipient. mintAccount: mint, - relayerTA: relayerTA, - recipientTA: recipientTA, + relayerTokenAccount: relayerTA, + recipientTokenAccount: recipientTA, fillStatus, tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, @@ -325,7 +323,6 @@ describe("svm_spoke.slow_fill", () => { vault: vault, tokenProgram: TOKEN_PROGRAM_ID, mint: mint, - recipient, recipientTokenAccount: recipientTA, program: program.programId, }; @@ -413,7 +410,7 @@ describe("svm_spoke.slow_fill", () => { .signers([relayer]) .rpc(); - // Try to execute V3 slow relay leaf with wrong recipient should fail. + // Try to execute V3 slow relay leaf with wrong recipient token account should fail. const wrongRecipient = Keypair.generate().publicKey; const wrongRecipientTA = (await getOrCreateAssociatedTokenAccount(connection, payer, mint, wrongRecipient)).address; try { @@ -425,7 +422,6 @@ describe("svm_spoke.slow_fill", () => { vault: vault, tokenProgram: TOKEN_PROGRAM_ID, mint: mint, - recipient: wrongRecipient, recipientTokenAccount: wrongRecipientTA, program: program.programId, }; @@ -438,10 +434,10 @@ describe("svm_spoke.slow_fill", () => { ) .accounts(executeSlowRelayLeafAccounts) .rpc(); - assert.fail("Execution should have failed due to wrong recipient"); + assert.fail("Execution should have failed due to wrong recipient token account"); } catch (err: any) { assert.instanceOf(err, anchor.AnchorError); - assert.strictEqual(err.error.errorCode.code, "InvalidFillRecipient", "Expected error code InvalidFillRecipient"); + assert.strictEqual(err.error.errorCode.code, "ConstraintTokenOwner", "Expected error code ConstraintTokenOwner"); } }); @@ -485,7 +481,6 @@ describe("svm_spoke.slow_fill", () => { vault, tokenProgram: TOKEN_PROGRAM_ID, mint, - recipient: firstRecipient, recipientTokenAccount: firstRecipientTA, program: program.programId, }; @@ -516,7 +511,6 @@ describe("svm_spoke.slow_fill", () => { vault, tokenProgram: TOKEN_PROGRAM_ID, mint, - recipient: firstRecipient, recipientTokenAccount: firstRecipientTA, program: program.programId, }; @@ -561,7 +555,6 @@ describe("svm_spoke.slow_fill", () => { vault: wrongVault, tokenProgram: TOKEN_PROGRAM_ID, mint: wrongMint, - recipient, recipientTokenAccount: wrongRecipientTA, program: program.programId, }; @@ -605,7 +598,6 @@ describe("svm_spoke.slow_fill", () => { vault, tokenProgram: TOKEN_PROGRAM_ID, mint, - recipient, recipientTokenAccount: recipientTA, program: program.programId, }; From f3ac21e96c89e35e47c4922714f6e7242ce02b43 Mon Sep 17 00:00:00 2001 From: Chris Maree Date: Fri, 25 Oct 2024 12:27:34 +0200 Subject: [PATCH 3/4] feat: move drift from svm-dev back into master (#682) --- programs/svm-spoke/src/instructions/fill.rs | 5 +- .../svm-spoke/src/instructions/slow_fill.rs | 2 +- programs/svm-spoke/src/lib.rs | 9 ++- scripts/svm/simpleFill.ts | 2 +- test/svm/SvmSpoke.Fill.ts | 63 ++++++++++++++----- test/svm/SvmSpoke.SlowFill.ts | 2 +- 6 files changed, 62 insertions(+), 21 deletions(-) diff --git a/programs/svm-spoke/src/instructions/fill.rs b/programs/svm-spoke/src/instructions/fill.rs index 3361917aa..9b8fa7d33 100644 --- a/programs/svm-spoke/src/instructions/fill.rs +++ b/programs/svm-spoke/src/instructions/fill.rs @@ -88,6 +88,7 @@ pub fn fill_v3_relay( relay_hash: [u8; 32], // include in props, while not using it, to enable us to access it from the #Instruction Attribute within the accounts. This enables us to pass in the relay_hash PDA. relay_data: V3RelayData, repayment_chain_id: u64, + repayment_address: Pubkey, ) -> Result<()> { let state = &ctx.accounts.state; let current_time = get_current_time(state)?; @@ -152,7 +153,7 @@ pub fn fill_v3_relay( fill_deadline: relay_data.fill_deadline, exclusivity_deadline: relay_data.exclusivity_deadline, exclusive_relayer: relay_data.exclusive_relayer, - relayer: *ctx.accounts.signer.key, + relayer: repayment_address, depositor: relay_data.depositor, recipient: relay_data.recipient, message: relay_data.message, @@ -210,5 +211,3 @@ pub fn close_fill_pda( Ok(()) } - -// Events. diff --git a/programs/svm-spoke/src/instructions/slow_fill.rs b/programs/svm-spoke/src/instructions/slow_fill.rs index bbe9e4d0e..f6447cf21 100644 --- a/programs/svm-spoke/src/instructions/slow_fill.rs +++ b/programs/svm-spoke/src/instructions/slow_fill.rs @@ -246,7 +246,7 @@ pub fn execute_v3_slow_relay_leaf( fill_deadline: relay_data.fill_deadline, exclusivity_deadline: relay_data.exclusivity_deadline, exclusive_relayer: relay_data.exclusive_relayer, - relayer: *ctx.accounts.signer.key, + relayer: Pubkey::default(), // There is no repayment address for slow depositor: relay_data.depositor, recipient: relay_data.recipient, message: relay_data.message, diff --git a/programs/svm-spoke/src/lib.rs b/programs/svm-spoke/src/lib.rs index b640d7d0f..2992107e9 100644 --- a/programs/svm-spoke/src/lib.rs +++ b/programs/svm-spoke/src/lib.rs @@ -139,8 +139,15 @@ pub mod svm_spoke { relay_hash: [u8; 32], relay_data: V3RelayData, repayment_chain_id: u64, + repayment_address: Pubkey, ) -> Result<()> { - instructions::fill_v3_relay(ctx, relay_hash, relay_data, repayment_chain_id) + instructions::fill_v3_relay( + ctx, + relay_hash, + relay_data, + repayment_chain_id, + repayment_address, + ) } pub fn close_fill_pda( diff --git a/scripts/svm/simpleFill.ts b/scripts/svm/simpleFill.ts index 3e7a291e9..f528ffd93 100644 --- a/scripts/svm/simpleFill.ts +++ b/scripts/svm/simpleFill.ts @@ -121,7 +121,7 @@ async function fillV3Relay(): Promise { })) ); - const tx = await (program.methods.fillV3Relay(Array.from(relayHashUint8Array), relayData, chainId) as any) + const tx = await (program.methods.fillV3Relay(Array.from(relayHashUint8Array), relayData, chainId, signer) as any) .accounts({ state: statePda, signer: signer, diff --git a/test/svm/SvmSpoke.Fill.ts b/test/svm/SvmSpoke.Fill.ts index 3bc496cad..21efbd880 100644 --- a/test/svm/SvmSpoke.Fill.ts +++ b/test/svm/SvmSpoke.Fill.ts @@ -91,7 +91,11 @@ describe("svm_spoke.fill", () => { assertSE(relayerAccount.amount, seedBalance, "Relayer's balance should be equal to seed balance before the fill"); const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); - await program.methods.fillV3Relay(relayHash, relayData, new BN(1)).accounts(accounts).signers([relayer]).rpc(); + await program.methods + .fillV3Relay(relayHash, relayData, new BN(1), relayer.publicKey) + .accounts(accounts) + .signers([relayer]) + .rpc(); // Verify relayer's balance after the fill relayerAccount = await getAccount(connection, relayerTA); @@ -108,7 +112,11 @@ describe("svm_spoke.fill", () => { it("Verifies FilledV3Relay event after filling a relay", async () => { const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); - await program.methods.fillV3Relay(relayHash, relayData, new BN(1)).accounts(accounts).signers([relayer]).rpc(); + await program.methods + .fillV3Relay(relayHash, relayData, new BN(420), otherRelayer.publicKey) + .accounts(accounts) + .signers([relayer]) + .rpc(); // Fetch and verify the FilledV3Relay event await new Promise((resolve) => setTimeout(resolve, 500)); @@ -120,6 +128,9 @@ describe("svm_spoke.fill", () => { Object.keys(relayData).forEach((key) => { assertSE(event[key], relayData[key], `${key.charAt(0).toUpperCase() + key.slice(1)} should match`); }); + // These props below are not part of relayData. + assertSE(event.repaymentChainId, new BN(420), "Repayment chain id should match"); + assertSE(event.relayer, otherRelayer.publicKey, "Repayment address should match"); }); it("Fails to fill a V3 relay after the fill deadline", async () => { @@ -127,7 +138,11 @@ describe("svm_spoke.fill", () => { const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); try { - await program.methods.fillV3Relay(relayHash, relayData, new BN(1)).accounts(accounts).signers([relayer]).rpc(); + await program.methods + .fillV3Relay(relayHash, relayData, new BN(1), relayer.publicKey) + .accounts(accounts) + .signers([relayer]) + .rpc(); assert.fail("Fill should have failed due to fill deadline passed"); } catch (err: any) { assert.include(err.toString(), "ExpiredFillDeadline", "Expected ExpiredFillDeadline error"); @@ -141,7 +156,7 @@ describe("svm_spoke.fill", () => { const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); try { await program.methods - .fillV3Relay(relayHash, relayData, new BN(1)) + .fillV3Relay(relayHash, relayData, new BN(1), relayer.publicKey) .accounts(accounts) .signers([otherRelayer]) .rpc(); @@ -161,7 +176,11 @@ describe("svm_spoke.fill", () => { const relayerAccountBefore = await getAccount(connection, otherRelayerTA); const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); - await program.methods.fillV3Relay(relayHash, relayData, new BN(1)).accounts(accounts).signers([otherRelayer]).rpc(); + await program.methods + .fillV3Relay(relayHash, relayData, new BN(1), relayer.publicKey) + .accounts(accounts) + .signers([otherRelayer]) + .rpc(); // Verify relayer's balance after the fill const relayerAccountAfter = await getAccount(connection, otherRelayerTA); @@ -184,11 +203,19 @@ describe("svm_spoke.fill", () => { const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); // First fill attempt - await program.methods.fillV3Relay(relayHash, relayData, new BN(1)).accounts(accounts).signers([relayer]).rpc(); + await program.methods + .fillV3Relay(relayHash, relayData, new BN(1), relayer.publicKey) + .accounts(accounts) + .signers([relayer]) + .rpc(); // Second fill attempt with the same data try { - await program.methods.fillV3Relay(relayHash, relayData, new BN(1)).accounts(accounts).signers([relayer]).rpc(); + await program.methods + .fillV3Relay(relayHash, relayData, new BN(1), relayer.publicKey) + .accounts(accounts) + .signers([relayer]) + .rpc(); assert.fail("Fill should have failed due to RelayFilled error"); } catch (err: any) { assert.include(err.toString(), "RelayFilled", "Expected RelayFilled error"); @@ -206,7 +233,11 @@ describe("svm_spoke.fill", () => { }; // Execute the fill_v3_relay call - await program.methods.fillV3Relay(relayHash, relayData, new BN(1)).accounts(accounts).signers([relayer]).rpc(); + await program.methods + .fillV3Relay(relayHash, relayData, new BN(1), relayer.publicKey) + .accounts(accounts) + .signers([relayer]) + .rpc(); // Verify the fill PDA exists before closing const fillStatusAccountBefore = await connection.getAccountInfo(accounts.fillStatus); @@ -221,7 +252,7 @@ describe("svm_spoke.fill", () => { } // Set the current time to past the fill deadline - await setCurrentTime(program, state, relayer, relayData.fillDeadline.add(new BN(1))); + await setCurrentTime(program, state, relayer, relayData.fillDeadline.add(new BN(1), relayer.publicKey)); // Close the fill PDA await program.methods.closeFillPda(relayHash, relayData).accounts(closeFillPdaAccounts).signers([relayer]).rpc(); @@ -241,7 +272,7 @@ describe("svm_spoke.fill", () => { // Fill the relay await program.methods - .fillV3Relay(Array.from(relayHash), relayData, new BN(1)) + .fillV3Relay(Array.from(relayHash), relayData, new BN(1), relayer.publicKey) .accounts(accounts) .signers([relayer]) .rpc(); @@ -263,7 +294,11 @@ describe("svm_spoke.fill", () => { // Try to fill the relay. This should fail because fills are paused. const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); try { - await program.methods.fillV3Relay(relayHash, relayData, new BN(1)).accounts(accounts).signers([relayer]).rpc(); + await program.methods + .fillV3Relay(relayHash, relayData, new BN(1), relayer.publicKey) + .accounts(accounts) + .signers([relayer]) + .rpc(); assert.fail("Should not be able to fill relay when fills are paused"); } catch (err: any) { assert.include(err.toString(), "Fills are currently paused!", "Expected fills paused error"); @@ -280,7 +315,7 @@ describe("svm_spoke.fill", () => { try { await program.methods - .fillV3Relay(Array.from(relayHash), relayData, new BN(1)) + .fillV3Relay(Array.from(relayHash), relayData, new BN(1), relayer.publicKey) .accounts({ ...accounts, recipientTokenAccount: wrongRecipientTA, @@ -307,7 +342,7 @@ describe("svm_spoke.fill", () => { try { await program.methods - .fillV3Relay(Array.from(relayHash), relayData, new BN(1)) + .fillV3Relay(Array.from(relayHash), relayData, new BN(1), relayer.publicKey) .accounts({ ...accounts, mintAccount: wrongMint, @@ -333,7 +368,7 @@ describe("svm_spoke.fill", () => { const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); const txSignature = await program.methods - .fillV3Relay(relayHash, relayData, new BN(1)) + .fillV3Relay(relayHash, relayData, new BN(1), relayer.publicKey) .accounts(accounts) .signers([relayer]) .rpc(); diff --git a/test/svm/SvmSpoke.SlowFill.ts b/test/svm/SvmSpoke.SlowFill.ts index 8ea0c89ad..6ccb26f8e 100644 --- a/test/svm/SvmSpoke.SlowFill.ts +++ b/test/svm/SvmSpoke.SlowFill.ts @@ -219,7 +219,7 @@ describe("svm_spoke.slow_fill", () => { // Fill the relay first await program.methods - .fillV3Relay(relayHash, formatRelayData(relayData), new BN(1)) + .fillV3Relay(relayHash, formatRelayData(relayData), new BN(1), relayer.publicKey) .accounts(fillAccounts) .signers([relayer]) .rpc(); From fb49aaf32b30014259af46c11785846b1c9ea4b4 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Fri, 25 Oct 2024 13:54:30 +0100 Subject: [PATCH 4/4] refactor(svm): relayer refund leaf order (#692) * refactor: relayer refund leaf order Signed-off-by: Pablo Maldonado * refactor: relayer refund leaf order Signed-off-by: Pablo Maldonado * refactor: comment Signed-off-by: Pablo Maldonado --------- Signed-off-by: Pablo Maldonado --- programs/svm-spoke/src/instructions/bundle.rs | 13 +++++-------- test/svm/utils.ts | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/programs/svm-spoke/src/instructions/bundle.rs b/programs/svm-spoke/src/instructions/bundle.rs index 59f484097..e856c7382 100644 --- a/programs/svm-spoke/src/instructions/bundle.rs +++ b/programs/svm-spoke/src/instructions/bundle.rs @@ -66,17 +66,15 @@ pub struct ExecuteRelayerRefundLeaf<'info> { pub system_program: Program<'info, System>, } -// TODO: add multichain test to check if its possible to verify both EVM and SVM leaves in the same bundle. -// TODO: update UMIP to consider different encoding for different chains. +// TODO: update UMIP to consider different encoding for different chains (evm and svm). #[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace)] // TODO: check if all derives are needed. pub struct RelayerRefundLeaf { - // TODO: at least the same ordering as in EVM. pub amount_to_return: u64, pub chain_id: u64, - pub leaf_id: u32, - pub mint_public_key: Pubkey, #[max_len(0)] pub refund_amounts: Vec, + pub leaf_id: u32, + pub mint_public_key: Pubkey, #[max_len(0)] pub refund_accounts: Vec, } @@ -87,12 +85,11 @@ impl RelayerRefundLeaf { bytes.extend_from_slice(&self.amount_to_return.to_le_bytes()); bytes.extend_from_slice(&self.chain_id.to_le_bytes()); - bytes.extend_from_slice(&self.leaf_id.to_le_bytes()); - bytes.extend_from_slice(self.mint_public_key.as_ref()); - for amount in &self.refund_amounts { bytes.extend_from_slice(&amount.to_le_bytes()); } + bytes.extend_from_slice(&self.leaf_id.to_le_bytes()); + bytes.extend_from_slice(self.mint_public_key.as_ref()); for account in &self.refund_accounts { bytes.extend_from_slice(account.as_ref()); } diff --git a/test/svm/utils.ts b/test/svm/utils.ts index b29164953..862c90147 100644 --- a/test/svm/utils.ts +++ b/test/svm/utils.ts @@ -164,9 +164,9 @@ export function calculateRelayerRefundLeafHashUint8Array(relayData: RelayerRefun const contentToHash = Buffer.concat([ relayData.amountToReturn.toArrayLike(Buffer, "le", 8), relayData.chainId.toArrayLike(Buffer, "le", 8), + refundAmountsBuffer, relayData.leafId.toArrayLike(Buffer, "le", 4), relayData.mintPublicKey.toBuffer(), - refundAmountsBuffer, refundAccountsBuffer, ]);