diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index ae13a6fc..60ffa515 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -18,8 +18,8 @@ import { isV2SpeedUp, isV3SpeedUp, toBN, - isValidType, - assert, + isPartialDepositFormedCorrectly, + isV3Deposit, } from "../utils"; import { paginatedEventQuery, @@ -51,7 +51,6 @@ import { V2SpeedUp, V3DepositWithBlock, V3FillWithBlock, - V3FundsDepositEventProps, V3FundsDepositedEvent, V3RelayData, V3RelayerRefundExecutionWithBlock, @@ -757,34 +756,33 @@ export class SpokePoolClient extends BaseAbstractClient { const dataForQuoteTime = await this.batchComputeRealizedLpFeePct(depositEvents); for (const [index, event] of Array.from(depositEvents.entries())) { - const rawDeposit = spreadEventWithBlockNumber(event); - let deposit: DepositWithBlock; - - if (this.isV3DepositEvent(event)) { - deposit = { ...(rawDeposit as V3DepositWithBlock) }; - if (deposit.outputToken === ZERO_ADDRESS) { - deposit.outputToken = this.getDestinationTokenForDeposit(deposit); - } - } else { - deposit = { ...(rawDeposit as V2DepositWithBlock) }; - deposit.destinationToken = this.getDestinationTokenForDeposit(deposit); - } - - // Derive and append the common properties that are not part of the onchain event. + const partialDeposit = spreadEventWithBlockNumber(event); const { quoteBlock: quoteBlockNumber, realizedLpFeePct } = dataForQuoteTime[index]; - deposit.realizedLpFeePct = realizedLpFeePct; - deposit.quoteBlockNumber = quoteBlockNumber; + if (isPartialDepositFormedCorrectly(partialDeposit)) { + let deposit: DepositWithBlock; + + if (isV3Deposit(partialDeposit)) { + let modifiedOutputToken = partialDeposit.outputToken; + if (partialDeposit.outputToken === ZERO_ADDRESS) { + modifiedOutputToken = this.getDestinationTokenForDeposit({ ...partialDeposit, quoteBlockNumber }); + } + deposit = { ...partialDeposit, quoteBlockNumber, realizedLpFeePct, outputToken: modifiedOutputToken }; + } else { + const destinationToken = this.getDestinationTokenForDeposit({ ...partialDeposit, quoteBlockNumber }); + deposit = { ...partialDeposit, quoteBlockNumber, realizedLpFeePct, destinationToken }; + } - if (this.depositHashes[this.getDepositHash(deposit)] !== undefined) { - throw new Error(`SpokePoolClient: Duplicate deposit for relayDataHash: ${this.getDepositHash(deposit)}`); - } - assign(this.depositHashes, [this.getDepositHash(deposit)], deposit); + if (this.depositHashes[this.getDepositHash(deposit)] !== undefined) { + throw new Error(`SpokePoolClient: Duplicate deposit for relayDataHash: ${this.getDepositHash(deposit)}`); + } + assign(this.depositHashes, [this.getDepositHash(deposit)], deposit); - if (deposit.depositId < this.earliestDepositIdQueried) { - this.earliestDepositIdQueried = deposit.depositId; - } - if (deposit.depositId > this.latestDepositIdQueried) { - this.latestDepositIdQueried = deposit.depositId; + if (deposit.depositId < this.earliestDepositIdQueried) { + this.earliestDepositIdQueried = deposit.depositId; + } + if (deposit.depositId > this.latestDepositIdQueried) { + this.latestDepositIdQueried = deposit.depositId; + } } } } @@ -994,7 +992,11 @@ export class SpokePoolClient extends BaseAbstractClient { * @param deposit The deposit to retrieve the destination token for. * @returns The destination token. */ - protected getDestinationTokenForDeposit(deposit: DepositWithBlock): string { + protected getDestinationTokenForDeposit( + deposit: + | Pick + | Pick + ): string { // If there is no rate model client return address(0). if (!this.hubPoolClient) { return ZERO_ADDRESS; @@ -1088,25 +1090,29 @@ export class SpokePoolClient extends BaseAbstractClient { ` between ${srcChain} blocks [${searchBounds.low}, ${searchBounds.high}]` ); } - const partialDeposit = spreadEventWithBlockNumber(event) as V2DepositWithBlock; - const { realizedLpFeePct, quoteBlock: quoteBlockNumber } = (await this.batchComputeRealizedLpFeePct([event]))[0]; // Append the realizedLpFeePct. - - // Append destination token and realized lp fee to deposit. - const deposit: V2DepositWithBlock = { - ...partialDeposit, - realizedLpFeePct, - destinationToken: this.getDestinationTokenForDeposit(partialDeposit), - quoteBlockNumber, - }; + const partialDeposit = spreadEventWithBlockNumber(event); + if (isPartialDepositFormedCorrectly(partialDeposit) && isV2Deposit(partialDeposit)) { + const { realizedLpFeePct, quoteBlock: quoteBlockNumber } = (await this.batchComputeRealizedLpFeePct([event]))[0]; // Append the realizedLpFeePct. + + // Append destination token and realized lp fee to deposit. + const deposit: V2DepositWithBlock = { + ...partialDeposit, + realizedLpFeePct, + destinationToken: this.getDestinationTokenForDeposit({ ...partialDeposit, quoteBlockNumber }), + quoteBlockNumber, + }; - this.logger.debug({ - at: "SpokePoolClient#findDeposit", - message: "Located deposit outside of SpokePoolClient's search range", - deposit, - elapsedMs: tStop - tStart, - }); + this.logger.debug({ + at: "SpokePoolClient#findDeposit", + message: "Located deposit outside of SpokePoolClient's search range", + deposit, + elapsedMs: tStop - tStart, + }); - return deposit; + return deposit; + } else { + throw new Error("event format is unexpected"); + } } async findDepositV3(depositId: number, destinationChainId: number, depositor: string): Promise { @@ -1160,28 +1166,31 @@ export class SpokePoolClient extends BaseAbstractClient { ` between ${srcChain} blocks [${searchBounds.low}, ${searchBounds.high}]` ); } - const partialDeposit = spreadEventWithBlockNumber(event) as V3DepositWithBlock; - assert(isValidType(partialDeposit, V3FundsDepositEventProps), "Invalid FundsDeposited event"); - const { realizedLpFeePct, quoteBlock: quoteBlockNumber } = (await this.batchComputeRealizedLpFeePct([event]))[0]; // Append the realizedLpFeePct. - - // Append destination token and realized lp fee to deposit. - const deposit: V3DepositWithBlock = { - ...partialDeposit, - realizedLpFeePct, - quoteBlockNumber, - outputToken: - partialDeposit.outputToken === ZERO_ADDRESS - ? this.getDestinationTokenForDeposit(partialDeposit) - : partialDeposit.outputToken, - }; + const partialDeposit = spreadEventWithBlockNumber(event); + if (isPartialDepositFormedCorrectly(partialDeposit) && isV3Deposit(partialDeposit)) { + const { realizedLpFeePct, quoteBlock: quoteBlockNumber } = (await this.batchComputeRealizedLpFeePct([event]))[0]; // Append the realizedLpFeePct. + + // Append destination token and realized lp fee to deposit. + const deposit: V3DepositWithBlock = { + ...partialDeposit, + realizedLpFeePct, + quoteBlockNumber, + outputToken: + partialDeposit.outputToken === ZERO_ADDRESS + ? this.getDestinationTokenForDeposit({ ...partialDeposit, quoteBlockNumber }) + : partialDeposit.outputToken, + }; - this.logger.debug({ - at: "SpokePoolClient#findDepositV3", - message: "Located V3 deposit outside of SpokePoolClient's search range", - deposit, - elapsedMs: tStop - tStart, - }); + this.logger.debug({ + at: "SpokePoolClient#findDepositV3", + message: "Located V3 deposit outside of SpokePoolClient's search range", + deposit, + elapsedMs: tStop - tStart, + }); - return deposit; + return deposit; + } else { + throw new Error("event format is unexpected"); + } } } diff --git a/src/interfaces/SpokePool.ts b/src/interfaces/SpokePool.ts index c78bf19f..48438c3e 100644 --- a/src/interfaces/SpokePool.ts +++ b/src/interfaces/SpokePool.ts @@ -43,51 +43,36 @@ export interface V2Deposit extends V2RelayData { updatedMessage?: string; } +export interface V2PartialDepositWithBlock extends V2RelayData, SortableEvent { + originToken: string; + quoteTimestamp: number; +} + export interface V2DepositWithBlock extends V2Deposit, SortableEvent { quoteBlockNumber: number; } +export interface V3PartialDepositWithBlock extends V3RelayData, SortableEvent { + destinationChainId: number; + quoteTimestamp: number; +} + export interface V3Deposit extends V3RelayData { destinationChainId: number; quoteTimestamp: number; realizedLpFeePct?: BigNumber; - relayerFeePct?: BigNumber; speedUpSignature?: string; updatedRecipient?: string; updatedOutputAmount?: BigNumber; updatedMessage?: string; } -export const SortableEventProps = [ - "blockNumber", - "transactionIndex", - "logIndex", - "transactionHash" -] -export const V3RelayDataProps = [ - "originChainId", - "depositor", - "recipient", - "depositId", - "message", - "inputToken", - "inputAmount", - "outputToken", - "outputAmount", - "fillDeadline", - "exclusiveRelayer", - "exclusivityDeadline", -] -export const V3FundsDepositEventProps = [ - ...SortableEventProps, - ...V3RelayDataProps -] - export interface V3DepositWithBlock extends V3Deposit, SortableEvent { quoteBlockNumber: number; } export type Deposit = V2Deposit | V3Deposit; +export type PartialDepositWithBlock = V2PartialDepositWithBlock | V3PartialDepositWithBlock; export type DepositWithBlock = V2DepositWithBlock | V3DepositWithBlock; export interface RelayExecutionInfoCommon { diff --git a/src/utils/TypeGuards.ts b/src/utils/TypeGuards.ts index 3a4fbeb3..900d79b5 100644 --- a/src/utils/TypeGuards.ts +++ b/src/utils/TypeGuards.ts @@ -21,4 +21,4 @@ export function isValidType(input: object, expectedTypeProps: string[]): boolean } } return true; -} \ No newline at end of file +} diff --git a/src/utils/ValidatorUtils.ts b/src/utils/ValidatorUtils.ts index b9f7ad4a..92546920 100644 --- a/src/utils/ValidatorUtils.ts +++ b/src/utils/ValidatorUtils.ts @@ -1,36 +1,76 @@ import { BigNumber, ethers } from "ethers"; -import { object, min as Min, define, optional, string, integer } from "superstruct"; -import { DepositWithBlock } from "../interfaces"; +import { object, min as Min, define, optional, string, integer, assign } from "superstruct"; +import { DepositWithBlock, PartialDepositWithBlock } from "../interfaces"; +import { isV2Deposit } from "./V3Utils"; const AddressValidator = define("AddressValidator", (v) => ethers.utils.isAddress(String(v))); const HexValidator = define("HexValidator", (v) => ethers.utils.isHexString(String(v))); const BigNumberValidator = define("BigNumberValidator", (v) => ethers.BigNumber.isBigNumber(v)); -const DepositSchema = object({ - depositId: Min(integer(), 0), - depositor: AddressValidator, - recipient: AddressValidator, - originToken: AddressValidator, - amount: BigNumberValidator, - originChainId: Min(integer(), 0), - destinationChainId: Min(integer(), 0), - relayerFeePct: BigNumberValidator, - quoteTimestamp: Min(integer(), 0), - realizedLpFeePct: optional(BigNumberValidator), - destinationToken: AddressValidator, - message: string(), - speedUpSignature: optional(string()), - newRelayerFeePct: optional(BigNumberValidator), - updatedRecipient: optional(string()), - updatedMessage: optional(string()), +const CommonEventSchema = object({ blockNumber: Min(integer(), 0), transactionIndex: Min(integer(), 0), logIndex: Min(integer(), 0), - quoteBlockNumber: Min(integer(), 0), transactionHash: HexValidator, - blockTimestamp: optional(Min(integer(), 0)), }); +const CommonPartialDepositSchema = assign( + CommonEventSchema, + object({ + depositId: Min(integer(), 0), + depositor: AddressValidator, + recipient: AddressValidator, + originChainId: Min(integer(), 0), + destinationChainId: Min(integer(), 0), + quoteTimestamp: Min(integer(), 0), + message: string(), + }) +); +const V2PartialDepositSchema = assign( + assign(CommonEventSchema, CommonPartialDepositSchema), + object({ + originToken: AddressValidator, + amount: BigNumberValidator, + relayerFeePct: BigNumberValidator, + }) +); +const V3PartialDepositSchema = assign( + assign(CommonEventSchema, CommonPartialDepositSchema), + object({ + inputToken: AddressValidator, + inputAmount: BigNumberValidator, + outputToken: AddressValidator, + outputAmount: BigNumberValidator, + exclusivityDeadline: Min(integer(), 0), + exclusiveRelayer: AddressValidator, + fillDeadline: Min(integer(), 0), + }) +); +const CommonDepositSchema = object({ + realizedLpFeePct: BigNumberValidator, + speedUpSignature: optional(string()), + updatedRecipient: optional(string()), + updatedMessage: optional(string()), +}); +const V2DepositSchema = assign( + assign(V2PartialDepositSchema, CommonDepositSchema), + object({ + destinationToken: AddressValidator, + newRelayerFeePct: optional(BigNumberValidator), + }) +); +const V3DepositSchema = assign( + assign(V3PartialDepositSchema, CommonDepositSchema), + object({ + updatedOutputAmount: optional(BigNumberValidator), + }) +); + +export function isPartialDepositFormedCorrectly(deposit: unknown): deposit is PartialDepositWithBlock { + if (isV2Deposit(deposit as DepositWithBlock)) return V2PartialDepositSchema.is(deposit); + else return V3PartialDepositSchema.is(deposit); +} export function isDepositFormedCorrectly(deposit: unknown): deposit is DepositWithBlock { - return DepositSchema.is(deposit); + if (isV2Deposit(deposit as DepositWithBlock)) return V2DepositSchema.is(deposit); + else return V3DepositSchema.is(deposit); }