From dc7152ca29cfe1e88782488ff10a12739b56f9ff Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 9 Feb 2024 15:01:18 +0100 Subject: [PATCH] improve: Add tests for v3 SpeedUps --- test/SpokePoolClient.SpeedUp.ts | 286 +++++++++++++++++++++++++++----- 1 file changed, 248 insertions(+), 38 deletions(-) diff --git a/test/SpokePoolClient.SpeedUp.ts b/test/SpokePoolClient.SpeedUp.ts index 02d37ad6..632facdd 100644 --- a/test/SpokePoolClient.SpeedUp.ts +++ b/test/SpokePoolClient.SpeedUp.ts @@ -1,10 +1,19 @@ -import { depositRelayerFeePct, destinationChainId, modifyRelayHelper } from "./constants"; +import { EMPTY_MESSAGE } from "../src/constants"; +import { SpokePoolClient } from "../src/clients"; +import { DepositWithBlock, V2DepositWithBlock, V3Deposit, V3DepositWithBlock, V3SpeedUp } from "../src/interfaces"; +import { bnOne, isV3Deposit } from "../src/utils"; +import { depositRelayerFeePct, destinationChainId, getUpdatedV3DepositSignature, modifyRelayHelper } from "./constants"; import { + assert, + assertPromisePasses, + assertPromiseError, Contract, + BigNumber, SignerWithAddress, createSpyLogger, deepEqualsWithBigNumber, deploySpokePoolWithToken, + depositV3, enableRoutes, ethers, expect, @@ -14,15 +23,6 @@ import { toBNWei, } from "./utils"; -import { SpokePoolClient } from "../src/clients"; -import { DepositWithBlock } from "../src/interfaces"; - -let spokePool: Contract, erc20: Contract, destErc20: Contract, weth: Contract; -let depositor: SignerWithAddress, deploymentBlock: number; -const destinationChainId2 = destinationChainId + 1; - -let spokePoolClient: SpokePoolClient; - describe("SpokePoolClient: SpeedUp", function () { const ignoredFields = [ "blockNumber", @@ -33,16 +33,34 @@ describe("SpokePoolClient: SpeedUp", function () { "transactionIndex", ]; + const destinationChainId2 = destinationChainId + 1; + const message = EMPTY_MESSAGE; + + let spokePool: Contract, erc20: Contract, destErc20: Contract, weth: Contract; + let depositor: SignerWithAddress, deploymentBlock: number; + let spokePoolClient: SpokePoolClient; + let balance: BigNumber; + let inputToken: string, outputToken: string; + let inputAmount: BigNumber, outputAmount: BigNumber; + beforeEach(async function () { [, depositor] = await ethers.getSigners(); + }); + + beforeEach(async function () { ({ spokePool, erc20, destErc20, weth, deploymentBlock } = await deploySpokePoolWithToken(originChainId)); await enableRoutes(spokePool, [{ originToken: erc20.address, destinationChainId: destinationChainId2 }]); spokePoolClient = new SpokePoolClient(createSpyLogger().spyLogger, spokePool, null, originChainId, deploymentBlock); await setupTokensForWallet(spokePool, depositor, [erc20, destErc20], weth, 10); + balance = await erc20.connect(depositor).balanceOf(depositor.address); + inputToken = erc20.address; + outputToken = destErc20.address; + inputAmount = balance; + outputAmount = inputAmount.sub(bnOne); }); - it("Fetches speedup data associated with a deposit", async function () { + it("v2: Fetches speedup data associated with a deposit", async function () { const deposit = await simpleDeposit(spokePool, erc20, depositor, depositor, destinationChainId); await spokePoolClient.update(); @@ -57,14 +75,14 @@ describe("SpokePoolClient: SpeedUp", function () { deposit.originChainId.toString(), depositor, deposit.recipient, - "0x" + message ); await spokePool.speedUpDeposit( depositor.address, newRelayFeePct, deposit.depositId, deposit.recipient, - "0x", + message, speedUpSignature.signature ); await spokePoolClient.update(); @@ -74,7 +92,7 @@ describe("SpokePoolClient: SpeedUp", function () { ...deposit, speedUpSignature: speedUpSignature.signature, newRelayerFeePct: newRelayFeePct, - updatedMessage: "0x", + updatedMessage: deposit.message, updatedRecipient: deposit.recipient, }; expect( @@ -95,7 +113,69 @@ describe("SpokePoolClient: SpeedUp", function () { expect(spokePoolClient.getDepositsForDestinationChain(destinationChainId).length).to.equal(1); }); - it("Fetches speedup data associated with an early deposit", async function () { + it("v3: Fetches speedup data associated with a deposit", async function () { + const deposit = await depositV3( + spokePool, + destinationChainId, + depositor, + inputToken, + inputAmount, + outputToken, + outputAmount + ); + await spokePoolClient.update(); + + // Should return the normal deposit object before any update is applied. + expect(spokePoolClient.appendMaxSpeedUpSignatureToDeposit(deposit)).to.deep.equal(deposit); + + const updatedOutputAmount = deposit.outputAmount.sub(bnOne); + const updatedRecipient = deposit.recipient; + const updatedMessage = deposit.message; + const signature = await getUpdatedV3DepositSignature( + depositor, + deposit.depositId, + originChainId, + updatedOutputAmount, + updatedRecipient, + updatedMessage + ); + + await spokePool + .connect(depositor) + .speedUpV3Deposit( + depositor.address, + deposit.depositId, + updatedOutputAmount, + updatedRecipient, + updatedMessage, + signature + ); + await spokePoolClient.update(); + + // After speedup should return the appended object with the new fee information and signature. + const expectedDepositData: V3Deposit = { + ...deposit, + speedUpSignature: signature, + updatedOutputAmount, + updatedMessage, + updatedRecipient, + }; + + expect(deepEqualsWithBigNumber(spokePoolClient.appendMaxSpeedUpSignatureToDeposit(deposit), expectedDepositData)).to + .be.true; + + // Fetching deposits for the depositor should contain the correct fees. + expect( + deepEqualsWithBigNumber( + spokePoolClient.getDepositsForDestinationChain(destinationChainId)[0], + expectedDepositData, + [...ignoredFields, "realizedLpFeePct"] + ) + ).to.be.true; + expect(spokePoolClient.getDepositsForDestinationChain(destinationChainId).length).to.equal(1); + }); + + it("v2: Fetches speedup data associated with an early deposit", async function () { const delta = await spokePool.depositQuoteTimeBuffer(); const now = Number(await spokePool.getCurrentTime()); @@ -115,14 +195,14 @@ describe("SpokePoolClient: SpeedUp", function () { deposit.originChainId.toString(), depositor, deposit.recipient, - "0x" + deposit.message ); await spokePool.speedUpDeposit( depositor.address, newRelayFeePct, deposit.depositId, deposit.recipient, - "0x", + deposit.message, speedUpSignature.signature ); await spokePoolClient.update(); @@ -140,7 +220,7 @@ describe("SpokePoolClient: SpeedUp", function () { ...deposit, speedUpSignature: speedUpSignature.signature, newRelayerFeePct: newRelayFeePct, - updatedMessage: "0x", + updatedMessage: deposit.message, updatedRecipient: deposit.recipient, }; expect( @@ -160,7 +240,7 @@ describe("SpokePoolClient: SpeedUp", function () { ).to.be.true; }); - it("Selects the highest speedup option when multiple are presented", async function () { + it("v2: Selects the highest speedup option when multiple are presented", async function () { const deposit = await simpleDeposit(spokePool, erc20, depositor, depositor, destinationChainId); // Speedup below the original fee should not update to use the new fee. @@ -171,14 +251,14 @@ describe("SpokePoolClient: SpeedUp", function () { deposit.originChainId.toString(), depositor, deposit.recipient, - "0x" + deposit.message ); await spokePool.speedUpDeposit( depositor.address, newLowerRelayFeePct, deposit.depositId, deposit.recipient, - "0x", + deposit.message, speedUpSignature.signature ); await spokePoolClient.update(); @@ -205,14 +285,14 @@ describe("SpokePoolClient: SpeedUp", function () { deposit.originChainId.toString(), depositor, deposit.recipient, - "0x" + deposit.message ); await spokePool.speedUpDeposit( depositor.address, speedupFast, deposit.depositId, deposit.recipient, - "0x", + deposit.message, speedUpFastSignature.signature ); const speedupFaster = toBNWei(0.1338); @@ -222,14 +302,14 @@ describe("SpokePoolClient: SpeedUp", function () { deposit.originChainId.toString(), depositor, deposit.recipient, - "0x" + deposit.message ); await spokePool.speedUpDeposit( depositor.address, speedupFaster, deposit.depositId, deposit.recipient, - "0x", + deposit.message, speedUpFasterSignature.signature ); await spokePoolClient.update(); @@ -239,7 +319,7 @@ describe("SpokePoolClient: SpeedUp", function () { ...deposit, speedUpSignature: speedUpFasterSignature.signature, newRelayerFeePct: speedupFaster, - updatedMessage: "0x", + updatedMessage: deposit.message, updatedRecipient: deposit.recipient, }; expect( @@ -257,13 +337,94 @@ describe("SpokePoolClient: SpeedUp", function () { ).to.be.true; expect(spokePoolClient.getDepositsForDestinationChain(destinationChainId).length).to.equal(1); }); - it("Receives a speed up for a correct depositor but invalid deposit Id", async function () { + + it("v3: Selects the lowest outputAtmount when multiple are presented", async function () { + const deposit = await depositV3( + spokePool, + destinationChainId, + depositor, + inputToken, + inputAmount, + outputToken, + outputAmount + ); + await spokePoolClient.update(); + + // Should return the normal deposit object before any update is applied. + expect(spokePoolClient.appendMaxSpeedUpSignatureToDeposit(deposit)).to.deep.equal(deposit); + + const depositUpdates: V3SpeedUp[] = []; + const { depositId, recipient: updatedRecipient, message: updatedMessage } = deposit; + for (const updatedOutputAmount of [outputAmount.add(1), outputAmount, outputAmount.sub(1), outputAmount.sub(2)]) { + const depositorSignature = await getUpdatedV3DepositSignature( + depositor, + depositId, + originChainId, + updatedOutputAmount, + updatedRecipient, + updatedMessage + ); + + await spokePool + .connect(depositor) + .speedUpV3Deposit( + depositor.address, + depositId, + updatedOutputAmount, + updatedRecipient, + updatedMessage, + depositorSignature + ); + + depositUpdates.push({ + depositorSignature, + updatedOutputAmount, + depositId, + depositor: depositor.address, + originChainId, + updatedRecipient, + updatedMessage, + }); + + const bestDepositUpdate = depositUpdates.reduce((prev, current) => + current.updatedOutputAmount.lt(prev.updatedOutputAmount) ? current : prev + ); + const lowestOutputAmount = bestDepositUpdate.updatedOutputAmount.lt(outputAmount) + ? bestDepositUpdate.updatedOutputAmount + : outputAmount; + + await spokePoolClient.update(); + let updatedDeposit = spokePoolClient + .getDepositsForDestinationChain(deposit.destinationChainId) + .filter(isV3Deposit) + .at(-1); + + // Convoluted checks to help tsc narrow types. + assert.exists(updatedDeposit); + assert.equal(isV3Deposit(updatedDeposit!), true); + updatedDeposit = updatedDeposit!; + + if (lowestOutputAmount.eq(deposit.outputAmount)) { + expect(updatedDeposit.updatedOutputAmount).to.be.undefined; + expect(updatedDeposit.speedUpSignature).to.be.undefined; + expect(updatedDeposit.updatedRecipient).to.be.undefined; + expect(updatedDeposit.updatedMessage).to.be.undefined; + } else { + expect(updatedDeposit.updatedOutputAmount!.eq(bestDepositUpdate.updatedOutputAmount)).to.be.true; + expect(updatedDeposit.speedUpSignature).to.equal(bestDepositUpdate.depositorSignature); + expect(updatedDeposit.updatedRecipient).to.equal(bestDepositUpdate.updatedRecipient); + expect(updatedDeposit.updatedMessage).to.equal(bestDepositUpdate.updatedMessage); + } + } + }); + + it("v2: Receives a speed up for a correct depositor but invalid deposit Id", async function () { const deposit = await simpleDeposit(spokePool, erc20, depositor, depositor, destinationChainId); await spokePoolClient.update(); // change deposit ID to some invalid value - deposit.depositId = 1337; + deposit.depositId = ++deposit.depositId; const newRelayFeePct = toBNWei(0.1337); const speedUpSignature = await modifyRelayHelper( @@ -272,25 +433,74 @@ describe("SpokePoolClient: SpeedUp", function () { deposit.originChainId.toString(), depositor, deposit.recipient, - "0x" + deposit.message ); await spokePool.speedUpDeposit( depositor.address, newRelayFeePct, deposit.depositId, deposit.recipient, - "0x", + deposit.message, speedUpSignature.signature ); - let success = false; - try { - await spokePoolClient.update(); - success = true; - } catch { - // no-op - } + await assertPromisePasses(spokePoolClient.update()); + }); + + it("v3: Ignores invalid updates", async function () { + const deposit = await depositV3( + spokePool, + destinationChainId, + depositor, + inputToken, + inputAmount, + outputToken, + outputAmount + ); + await spokePoolClient.update(); + + // Should return the normal deposit object before any update is applied. + expect(spokePoolClient.appendMaxSpeedUpSignatureToDeposit(deposit)).to.deep.equal(deposit); + + const { depositId, originChainId, recipient: updatedRecipient, message: updatedMessage } = deposit; + const updatedOutputAmount = deposit.outputAmount.sub(bnOne); + + // Independently toggle originChainId, depositId and depositor. Verify that a mismatch on these fields is not + // attributed to the existing deposit. + for (const field of ["originChainId", "depositId", "depositor"]) { + const testOriginChainId = field !== "originChainId" ? originChainId : originChainId + 1; + const testDepositId = field !== "depositId" ? depositId : depositId + 1; + const testDepositor = field !== "depositor" ? depositor : (await ethers.getSigners())[0]; + assert.isTrue(field !== "depositor" || testDepositor.address !== depositor.address); // Sanity check - expect(success).to.be.true; + const signature = await getUpdatedV3DepositSignature( + testDepositor, + testDepositId, + testOriginChainId, + updatedOutputAmount, + updatedRecipient, + updatedMessage + ); + + const speedUp = spokePool + .connect(depositor) + .speedUpV3Deposit( + testDepositor.address, + testDepositId, + updatedOutputAmount, + updatedRecipient, + updatedMessage, + signature + ); + + if (field === "originChainId") { + // Mismatched originChainId gets caught by the SpokePool contract. + await assertPromiseError(speedUp); + } + + // The updated deposit information should never be attached to the + // deposit. + expect(spokePoolClient.appendMaxSpeedUpSignatureToDeposit(deposit)).to.deep.equal(deposit); + } }); });