Skip to content

Commit a20c7e3

Browse files
nicholaspaibmzig
andauthored
improve: Verify relay hashes are the same pre and post upgrade (#878)
* fix: hash entire message when calculating relay hash for evm chains Signed-off-by: bennett <bennett@umaproject.org> * make getV3RelayHash public Signed-off-by: bennett <bennett@umaproject.org> * update fixture with relay hash change Signed-off-by: bennett <bennett@umaproject.org> * improve: Verify relay hashes are the same pre and post upgrade Adds a simple unit test to check that the same data hash is constructed * fix * Update test/evm/hardhat/MerkleLib.Proofs.ts * Update test/evm/hardhat/SpokePool.Relay.ts * Update SpokePool.Relay.ts --------- Signed-off-by: bennett <bennett@umaproject.org> Co-authored-by: bennett <bennett@umaproject.org>
1 parent 39dc879 commit a20c7e3

File tree

3 files changed

+104
-0
lines changed

3 files changed

+104
-0
lines changed

test/evm/hardhat/MerkleLib.Proofs.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import {
1212
BigNumber,
1313
ethers,
1414
randomBytes32,
15+
bytes32ToAddress,
16+
addressToBytes,
1517
} from "../../../utils/utils";
1618
import { V3RelayData, V3SlowFill } from "../../../test-utils";
19+
import { ParamType } from "ethers/lib/utils";
1720

1821
let merkleLibTest: Contract;
1922

@@ -149,4 +152,63 @@ describe("MerkleLib Proofs", async function () {
149152
expect(() => merkleTree.getHexProof(invalidLeaf)).to.throw();
150153
expect(await merkleLibTest.verifyV3SlowRelayFulfillment(root, invalidLeaf, proof)).to.equal(false);
151154
});
155+
// @todo we can remove this after the new spoke pool is upgraded
156+
it("Legacy V3SlowFill produces the same MerkleLeaf", async function () {
157+
const chainId = randomBigNumber(2).toNumber();
158+
const relayData: V3RelayData = {
159+
depositor: addressToBytes(randomAddress()),
160+
recipient: addressToBytes(randomAddress()),
161+
exclusiveRelayer: addressToBytes(randomAddress()),
162+
inputToken: addressToBytes(randomAddress()),
163+
outputToken: addressToBytes(randomAddress()),
164+
inputAmount: randomBigNumber(),
165+
outputAmount: randomBigNumber(),
166+
originChainId: randomBigNumber(2).toNumber(),
167+
depositId: randomBigNumber(2),
168+
fillDeadline: randomBigNumber(2).toNumber(),
169+
exclusivityDeadline: randomBigNumber(2).toNumber(),
170+
message: ethers.utils.hexlify(ethers.utils.randomBytes(1024)),
171+
};
172+
const slowLeaf: V3SlowFill = {
173+
relayData,
174+
chainId,
175+
updatedOutputAmount: relayData.outputAmount,
176+
};
177+
const legacyRelayData: V3RelayData = {
178+
...relayData,
179+
depositor: bytes32ToAddress(relayData.depositor),
180+
recipient: bytes32ToAddress(relayData.recipient),
181+
exclusiveRelayer: bytes32ToAddress(relayData.exclusiveRelayer),
182+
inputToken: bytes32ToAddress(relayData.inputToken),
183+
outputToken: bytes32ToAddress(relayData.outputToken),
184+
};
185+
const legacySlowLeaf: V3SlowFill = {
186+
relayData: legacyRelayData,
187+
chainId: slowLeaf.chainId,
188+
updatedOutputAmount: slowLeaf.updatedOutputAmount,
189+
};
190+
191+
const paramType = await getParamType("MerkleLibTest", "verifyV3SlowRelayFulfillment", "slowFill");
192+
const hashFn = (input: V3SlowFill) => keccak256(defaultAbiCoder.encode([paramType!], [input]));
193+
const merkleTree = new MerkleTree<V3SlowFill>([slowLeaf], hashFn);
194+
const root = merkleTree.getHexRoot();
195+
196+
const legacyHashFn = (input: V3SlowFill) =>
197+
keccak256(
198+
defaultAbiCoder.encode(
199+
[
200+
"tuple(" +
201+
"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," +
202+
"uint256 chainId," +
203+
"uint256 updatedOutputAmount" +
204+
")",
205+
],
206+
[input]
207+
)
208+
);
209+
const merkleTreeLegacy = new MerkleTree<V3SlowFill>([legacySlowLeaf], legacyHashFn);
210+
const legacyRoot = merkleTreeLegacy.getHexRoot();
211+
212+
expect(legacyRoot).to.equal(root);
213+
});
152214
});

test/evm/hardhat/SpokePool.Relay.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
bytes32ToAddress,
1313
hashNonEmptyMessage,
1414
toBN,
15+
randomBytes32,
1516
} from "../../../utils/utils";
1617
import {
1718
spokePoolFixture,
@@ -21,6 +22,7 @@ import {
2122
FillType,
2223
getUpdatedV3DepositSignature,
2324
getV3RelayHash,
25+
getLegacyV3RelayHash,
2426
} from "./fixtures/SpokePool.Fixture";
2527
import {
2628
repaymentChainId,
@@ -89,6 +91,32 @@ describe("SpokePool Relayer Logic", async function () {
8991
const relayExecution = await getRelayExecutionParams(relayData, destinationChainId);
9092
expect(await spokePool.fillStatuses(relayExecution.relayHash)).to.equal(FillStatus.Unfilled);
9193
});
94+
// @todo we can remove this after the new spoke pool is upgraded
95+
it("relay hash is same pre and post address -> bytes32 upgrade", async function () {
96+
const newBytes32Keys = ["depositor", "recipient", "exclusiveRelayer", "inputToken", "outputToken"];
97+
const relayDataCopy = { ...relayData, message: randomBytes32() };
98+
const legacyRelayData = {
99+
...relayDataCopy,
100+
depositor: bytes32ToAddress(relayData.depositor),
101+
recipient: bytes32ToAddress(relayData.recipient),
102+
exclusiveRelayer: bytes32ToAddress(relayData.exclusiveRelayer),
103+
inputToken: bytes32ToAddress(relayData.inputToken),
104+
outputToken: bytes32ToAddress(relayData.outputToken),
105+
};
106+
expect(
107+
newBytes32Keys.every(
108+
(key) => ethers.utils.hexDataLength(legacyRelayData[key as keyof typeof legacyRelayData] as string) === 20
109+
)
110+
).to.be.true;
111+
expect(
112+
newBytes32Keys.every(
113+
(key) => ethers.utils.hexDataLength(relayDataCopy[key as keyof typeof relayDataCopy] as string) === 32
114+
)
115+
).to.be.true;
116+
const newRelayHash = getV3RelayHash(relayDataCopy, destinationChainId);
117+
const oldRelayHash = getLegacyV3RelayHash(legacyRelayData, destinationChainId);
118+
expect(newRelayHash).to.equal(oldRelayHash);
119+
});
92120
it("expired fill deadline reverts", async function () {
93121
const _relay = {
94122
...relayData,

test/evm/hardhat/fixtures/SpokePool.Fixture.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,20 @@ export function getV3RelayHash(relayData: V3RelayData, destinationChainId: numbe
191191
);
192192
}
193193

194+
// @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
195+
// legacy one.
196+
export function getLegacyV3RelayHash(relayData: V3RelayData, destinationChainId: number): string {
197+
return ethers.utils.keccak256(
198+
defaultAbiCoder.encode(
199+
[
200+
"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)",
201+
"uint256 destinationChainId",
202+
],
203+
[relayData, destinationChainId]
204+
)
205+
);
206+
}
207+
194208
export function getDepositParams(args: {
195209
recipient?: string;
196210
originToken: string;

0 commit comments

Comments
 (0)