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

improve: Verify relay hashes are the same pre and post upgrade #878

Merged
merged 9 commits into from
Feb 5, 2025
62 changes: 62 additions & 0 deletions test/evm/hardhat/MerkleLib.Proofs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 () {
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
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<V3SlowFill>([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<V3SlowFill>([legacySlowLeaf], legacyHashFn);
const legacyRoot = merkleTreeLegacy.getHexRoot();

expect(legacyRoot).to.equal(root);
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
});
});
28 changes: 28 additions & 0 deletions test/evm/hardhat/SpokePool.Relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
bytes32ToAddress,
hashNonEmptyMessage,
toBN,
randomBytes32,
} from "../../../utils/utils";
import {
spokePoolFixture,
Expand All @@ -21,6 +22,7 @@ import {
FillType,
getUpdatedV3DepositSignature,
getV3RelayHash,
getLegacyV3RelayHash,
} from "./fixtures/SpokePool.Fixture";
import {
repaymentChainId,
Expand Down Expand Up @@ -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 () {
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand Down
14 changes: 14 additions & 0 deletions test/evm/hardhat/fixtures/SpokePool.Fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading