diff --git a/test/evm/hardhat/MerkleLib.Proofs.ts b/test/evm/hardhat/MerkleLib.Proofs.ts index a0a1be168..4d3c4493d 100644 --- a/test/evm/hardhat/MerkleLib.Proofs.ts +++ b/test/evm/hardhat/MerkleLib.Proofs.ts @@ -12,8 +12,11 @@ import { BigNumber, ethers, randomBytes32, + bytes32ToAddress, + addressToBytes, } from "../../../utils/utils"; import { V3RelayData, V3SlowFill } from "../../../test-utils"; +import { ParamType } from "ethers/lib/utils"; let merkleLibTest: Contract; @@ -149,4 +152,63 @@ describe("MerkleLib Proofs", async function () { expect(() => merkleTree.getHexProof(invalidLeaf)).to.throw(); expect(await merkleLibTest.verifyV3SlowRelayFulfillment(root, invalidLeaf, proof)).to.equal(false); }); + // @todo we can remove this after the new spoke pool is upgraded + it("Legacy V3SlowFill produces the same MerkleLeaf", async function () { + const chainId = randomBigNumber(2).toNumber(); + const relayData: V3RelayData = { + depositor: addressToBytes(randomAddress()), + recipient: addressToBytes(randomAddress()), + exclusiveRelayer: addressToBytes(randomAddress()), + inputToken: addressToBytes(randomAddress()), + outputToken: addressToBytes(randomAddress()), + inputAmount: randomBigNumber(), + outputAmount: randomBigNumber(), + originChainId: randomBigNumber(2).toNumber(), + depositId: randomBigNumber(2), + fillDeadline: randomBigNumber(2).toNumber(), + exclusivityDeadline: randomBigNumber(2).toNumber(), + message: ethers.utils.hexlify(ethers.utils.randomBytes(1024)), + }; + const slowLeaf: V3SlowFill = { + relayData, + chainId, + updatedOutputAmount: relayData.outputAmount, + }; + const legacyRelayData: V3RelayData = { + ...relayData, + depositor: bytes32ToAddress(relayData.depositor), + recipient: bytes32ToAddress(relayData.recipient), + exclusiveRelayer: bytes32ToAddress(relayData.exclusiveRelayer), + inputToken: bytes32ToAddress(relayData.inputToken), + outputToken: bytes32ToAddress(relayData.outputToken), + }; + const legacySlowLeaf: V3SlowFill = { + relayData: legacyRelayData, + chainId: slowLeaf.chainId, + updatedOutputAmount: slowLeaf.updatedOutputAmount, + }; + + const paramType = await getParamType("MerkleLibTest", "verifyV3SlowRelayFulfillment", "slowFill"); + const hashFn = (input: V3SlowFill) => keccak256(defaultAbiCoder.encode([paramType!], [input])); + const merkleTree = new MerkleTree([slowLeaf], hashFn); + const root = merkleTree.getHexRoot(); + + const legacyHashFn = (input: V3SlowFill) => + keccak256( + defaultAbiCoder.encode( + [ + "tuple(" + + "tuple(address depositor, address recipient, address exclusiveRelayer, address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 originChainId, uint32 depositId, uint32 fillDeadline, uint32 exclusivityDeadline, bytes message) relayData," + + "uint256 chainId," + + "uint256 updatedOutputAmount" + + ")", + ], + [input] + ) + ); + const merkleTreeLegacy = new MerkleTree([legacySlowLeaf], legacyHashFn); + const legacyRoot = merkleTreeLegacy.getHexRoot(); + + expect(legacyRoot).to.equal(root); + }); }); diff --git a/test/evm/hardhat/SpokePool.Relay.ts b/test/evm/hardhat/SpokePool.Relay.ts index 2d159e8e9..09ec54a3c 100644 --- a/test/evm/hardhat/SpokePool.Relay.ts +++ b/test/evm/hardhat/SpokePool.Relay.ts @@ -12,6 +12,7 @@ import { bytes32ToAddress, hashNonEmptyMessage, toBN, + randomBytes32, } from "../../../utils/utils"; import { spokePoolFixture, @@ -21,6 +22,7 @@ import { FillType, getUpdatedV3DepositSignature, getV3RelayHash, + getLegacyV3RelayHash, } from "./fixtures/SpokePool.Fixture"; import { repaymentChainId, @@ -89,6 +91,32 @@ describe("SpokePool Relayer Logic", async function () { const relayExecution = await getRelayExecutionParams(relayData, destinationChainId); expect(await spokePool.fillStatuses(relayExecution.relayHash)).to.equal(FillStatus.Unfilled); }); + // @todo we can remove this after the new spoke pool is upgraded + it("relay hash is same pre and post address -> bytes32 upgrade", async function () { + const newBytes32Keys = ["depositor", "recipient", "exclusiveRelayer", "inputToken", "outputToken"]; + const relayDataCopy = { ...relayData, message: randomBytes32() }; + const legacyRelayData = { + ...relayDataCopy, + depositor: bytes32ToAddress(relayData.depositor), + recipient: bytes32ToAddress(relayData.recipient), + exclusiveRelayer: bytes32ToAddress(relayData.exclusiveRelayer), + inputToken: bytes32ToAddress(relayData.inputToken), + outputToken: bytes32ToAddress(relayData.outputToken), + }; + expect( + newBytes32Keys.every( + (key) => ethers.utils.hexDataLength(legacyRelayData[key as keyof typeof legacyRelayData] as string) === 20 + ) + ).to.be.true; + expect( + newBytes32Keys.every( + (key) => ethers.utils.hexDataLength(relayDataCopy[key as keyof typeof relayDataCopy] as string) === 32 + ) + ).to.be.true; + const newRelayHash = getV3RelayHash(relayDataCopy, destinationChainId); + const oldRelayHash = getLegacyV3RelayHash(legacyRelayData, destinationChainId); + expect(newRelayHash).to.equal(oldRelayHash); + }); it("expired fill deadline reverts", async function () { const _relay = { ...relayData, diff --git a/test/evm/hardhat/fixtures/SpokePool.Fixture.ts b/test/evm/hardhat/fixtures/SpokePool.Fixture.ts index 6293c87d9..878015d02 100644 --- a/test/evm/hardhat/fixtures/SpokePool.Fixture.ts +++ b/test/evm/hardhat/fixtures/SpokePool.Fixture.ts @@ -191,6 +191,20 @@ export function getV3RelayHash(relayData: V3RelayData, destinationChainId: numbe ); } +// @todo we likely don't need to keep this function around for too long but its useful for making sure that the new relay hash is identical to the +// legacy one. +export function getLegacyV3RelayHash(relayData: V3RelayData, destinationChainId: number): string { + return ethers.utils.keccak256( + defaultAbiCoder.encode( + [ + "tuple(address depositor, address recipient, address exclusiveRelayer, address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 originChainId, uint32 depositId, uint32 fillDeadline, uint32 exclusivityDeadline, bytes message)", + "uint256 destinationChainId", + ], + [relayData, destinationChainId] + ) + ); +} + export function getDepositParams(args: { recipient?: string; originToken: string;