diff --git a/package.json b/package.json index a9772dd1..481bde31 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@across-protocol/sdk", "author": "UMA Team", - "version": "3.4.12", + "version": "3.4.13", "license": "AGPL-3.0", "homepage": "https://docs.across.to/reference/sdk", "files": [ diff --git a/src/clients/BundleDataClient/utils/SuperstructUtils.ts b/src/clients/BundleDataClient/utils/SuperstructUtils.ts index dd3462fe..c7a0ce01 100644 --- a/src/clients/BundleDataClient/utils/SuperstructUtils.ts +++ b/src/clients/BundleDataClient/utils/SuperstructUtils.ts @@ -10,6 +10,7 @@ import { pattern, boolean, defaulted, + union, type, } from "superstruct"; import { BigNumber } from "../../../utils"; @@ -17,7 +18,7 @@ import { BigNumber } from "../../../utils"; const PositiveIntegerStringSS = pattern(string(), /\d+/); const Web3AddressSS = pattern(string(), /^0x[a-fA-F0-9]{40}$/); -const BigNumberType = coerce(instance(BigNumber), string(), (value) => { +const BigNumberType = coerce(instance(BigNumber), union([string(), number()]), (value) => { try { // Attempt to convert the string to a BigNumber return BigNumber.from(value); @@ -41,7 +42,7 @@ const V3RelayDataSS = { originChainId: number(), depositor: string(), recipient: string(), - depositId: number(), + depositId: BigNumberType, message: string(), }; @@ -91,6 +92,7 @@ const V3FillSS = { repaymentChainId: number(), relayExecutionInfo: V3RelayExecutionEventInfoSS, quoteTimestamp: number(), + messageHash: optional(string()), }; const V3FillWithBlockSS = { diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index ef1b3173..18a077c2 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -12,6 +12,8 @@ import { getRelayDataHash, isDefined, toBN, + bnOne, + isUnsafeDepositId, } from "../utils"; import { paginatedEventQuery, @@ -46,8 +48,8 @@ type SpokePoolUpdateSuccess = { success: true; currentTime: number; oldestTime: number; - firstDepositId: number; - latestDepositId: number; + firstDepositId: BigNumber; + latestDepositId: BigNumber; events: Log[][]; searchEndBlock: number; }; @@ -66,7 +68,7 @@ export class SpokePoolClient extends BaseAbstractClient { protected oldestTime = 0; protected depositHashes: { [depositHash: string]: DepositWithBlock } = {}; protected depositHashesToFills: { [depositHash: string]: FillWithBlock[] } = {}; - protected speedUps: { [depositorAddress: string]: { [depositId: number]: SpeedUpWithBlock[] } } = {}; + protected speedUps: { [depositorAddress: string]: { [depositId: string]: SpeedUpWithBlock[] } } = {}; protected slowFillRequests: { [relayDataHash: string]: SlowFillRequestWithBlock } = {}; protected depositRoutes: { [originToken: string]: { [DestinationChainId: number]: boolean } } = {}; protected tokensBridged: TokensBridged[] = []; @@ -74,10 +76,10 @@ export class SpokePoolClient extends BaseAbstractClient { protected relayerRefundExecutions: RelayerRefundExecutionWithBlock[] = []; protected queryableEventNames: string[] = []; protected configStoreClient: AcrossConfigStoreClient | undefined; - public earliestDepositIdQueried = Number.MAX_SAFE_INTEGER; - public latestDepositIdQueried = 0; - public firstDepositIdForSpokePool = Number.MAX_SAFE_INTEGER; - public lastDepositIdForSpokePool = Number.MAX_SAFE_INTEGER; + public earliestDepositIdQueried = MAX_BIG_INT; + public latestDepositIdQueried = bnZero; + public firstDepositIdForSpokePool = MAX_BIG_INT; + public lastDepositIdForSpokePool = MAX_BIG_INT; public fills: { [OriginChainId: number]: FillWithBlock[] } = {}; /** @@ -232,7 +234,7 @@ export class SpokePoolClient extends BaseAbstractClient { */ public appendMaxSpeedUpSignatureToDeposit(deposit: DepositWithBlock): DepositWithBlock { const { depositId, depositor } = deposit; - const speedups = this.speedUps[depositor]?.[depositId]; + const speedups = this.speedUps[depositor]?.[depositId.toString()]; if (!isDefined(speedups) || speedups.length === 0) { return deposit; } @@ -265,7 +267,7 @@ export class SpokePoolClient extends BaseAbstractClient { * @param depositId The unique ID of the deposit being queried. * @returns The corresponding deposit if found, undefined otherwise. */ - public getDeposit(depositId: number): DepositWithBlock | undefined { + public getDeposit(depositId: BigNumber): DepositWithBlock | undefined { const depositHash = this.getDepositHash({ depositId, originChainId: this.chainId }); return this.depositHashes[depositHash]; } @@ -295,7 +297,7 @@ export class SpokePoolClient extends BaseAbstractClient { * Retrieves speed up requests grouped by depositor and depositId. * @returns A mapping of depositor addresses to deposit ids with their corresponding speed up requests. */ - public getSpeedUps(): { [depositorAddress: string]: { [depositId: number]: SpeedUpWithBlock[] } } { + public getSpeedUps(): { [depositorAddress: string]: { [depositId: string]: SpeedUpWithBlock[] } } { return this.speedUps; } @@ -365,7 +367,7 @@ export class SpokePoolClient extends BaseAbstractClient { ); // Log any invalid deposits with same deposit id but different params. - const invalidFillsForDeposit = invalidFills.filter((x) => x.depositId === deposit.depositId); + const invalidFillsForDeposit = invalidFills.filter((x) => x.depositId.eq(deposit.depositId)); if (invalidFillsForDeposit.length > 0) { this.logger.warn({ at: "SpokePoolClient", @@ -396,7 +398,7 @@ export class SpokePoolClient extends BaseAbstractClient { * @note This hash is used to match deposits and fills together. * @note This hash takes the form of: `${depositId}-${originChainId}`. */ - public getDepositHash(event: { depositId: number; originChainId: number }): string { + public getDepositHash(event: { depositId: BigNumber; originChainId: number }): string { return `${event.depositId}-${event.originChainId}`; } @@ -405,7 +407,7 @@ export class SpokePoolClient extends BaseAbstractClient { * @param blockTag The block number to search for the deposit ID at. * @returns The deposit ID. */ - public _getDepositIdAtBlock(blockTag: number): Promise { + public _getDepositIdAtBlock(blockTag: number): Promise { return getDepositIdAtBlock(this.spokePool as SpokePool, blockTag); } @@ -438,10 +440,11 @@ export class SpokePoolClient extends BaseAbstractClient { */ protected async _update(eventsToQuery: string[]): Promise { // Find the earliest known depositId. This assumes no deposits were placed in the deployment block. - let firstDepositId: number = this.firstDepositIdForSpokePool; - if (firstDepositId === Number.MAX_SAFE_INTEGER) { + let firstDepositId = this.firstDepositIdForSpokePool; + if (firstDepositId.eq(MAX_BIG_INT)) { firstDepositId = await this.spokePool.numberOfDeposits({ blockTag: this.deploymentBlock }); - if (isNaN(firstDepositId) || firstDepositId < 0) { + firstDepositId = BigNumber.from(firstDepositId); // Cast input to a big number. + if (!BigNumber.isBigNumber(firstDepositId) || firstDepositId.lt(bnZero)) { throw new Error(`SpokePoolClient::update: Invalid first deposit id (${firstDepositId})`); } } @@ -491,9 +494,10 @@ export class SpokePoolClient extends BaseAbstractClient { ]); this.log("debug", `Time to query new events from RPC for ${this.chainId}: ${Date.now() - timerStart} ms`); - const [currentTime, numberOfDeposits] = multicallFunctions.map( + const [currentTime, _numberOfDeposits] = multicallFunctions.map( (fn, idx) => spokePool.interface.decodeFunctionResult(fn, multicallOutput[idx])[0] ); + const _latestDepositId = BigNumber.from(_numberOfDeposits).sub(bnOne); if (!BigNumber.isBigNumber(currentTime) || currentTime.lt(this.currentTime)) { const errMsg = BigNumber.isBigNumber(currentTime) @@ -510,7 +514,7 @@ export class SpokePoolClient extends BaseAbstractClient { currentTime: currentTime.toNumber(), // uint32 oldestTime: oldestTime.toNumber(), firstDepositId, - latestDepositId: Math.max(numberOfDeposits - 1, 0), + latestDepositId: _latestDepositId.gt(bnZero) ? _latestDepositId : bnZero, searchEndBlock: searchConfig.toBlock, events, }; @@ -579,10 +583,10 @@ export class SpokePoolClient extends BaseAbstractClient { } assign(this.depositHashes, [this.getDepositHash(deposit)], deposit); - if (deposit.depositId < this.earliestDepositIdQueried) { + if (deposit.depositId.lt(this.earliestDepositIdQueried) && !isUnsafeDepositId(deposit.depositId)) { this.earliestDepositIdQueried = deposit.depositId; } - if (deposit.depositId > this.latestDepositIdQueried) { + if (deposit.depositId.gt(this.latestDepositIdQueried) && !isUnsafeDepositId(deposit.depositId)) { this.latestDepositIdQueried = deposit.depositId; } } @@ -594,7 +598,7 @@ export class SpokePoolClient extends BaseAbstractClient { for (const event of speedUpEvents) { const speedUp = { ...spreadEventWithBlockNumber(event), originChainId: this.chainId } as SpeedUpWithBlock; - assign(this.speedUps, [speedUp.depositor, speedUp.depositId], [speedUp]); + assign(this.speedUps, [speedUp.depositor, speedUp.depositId.toString()], [speedUp]); // Find deposit hash matching this speed up event and update the deposit data associated with the hash, // if the hash+data exists. @@ -778,7 +782,7 @@ export class SpokePoolClient extends BaseAbstractClient { return this.oldestTime; } - async findDeposit(depositId: number, destinationChainId: number): Promise { + async findDeposit(depositId: BigNumber, destinationChainId: number): Promise { // Binary search for event search bounds. This way we can get the blocks before and after the deposit with // deposit ID = fill.depositId and use those blocks to optimize the search for that deposit. // Stop searches after a maximum # of searches to limit number of eth_call requests. Make an @@ -807,7 +811,7 @@ export class SpokePoolClient extends BaseAbstractClient { ); const tStop = Date.now(); - const event = query.find(({ args }) => args["depositId"] === depositId); + const event = query.find(({ args }) => args["depositId"].eq(depositId)); if (event === undefined) { const srcChain = getNetworkName(this.chainId); const dstChain = getNetworkName(destinationChainId); diff --git a/src/clients/mocks/MockSpokePoolClient.ts b/src/clients/mocks/MockSpokePoolClient.ts index 7e9f2ed9..3d3a4aa7 100644 --- a/src/clients/mocks/MockSpokePoolClient.ts +++ b/src/clients/mocks/MockSpokePoolClient.ts @@ -14,7 +14,7 @@ import { SlowFillLeaf, SpeedUp, } from "../../interfaces"; -import { toBN, toBNWei, getCurrentTime, randomAddress } from "../../utils"; +import { toBN, toBNWei, getCurrentTime, randomAddress, BigNumber, bnZero, bnOne, bnMax } from "../../utils"; import { SpokePoolClient, SpokePoolUpdate } from "../SpokePoolClient"; import { HubPoolClient } from "../HubPoolClient"; import { EventManager, EventOverrides, getEventManager } from "./MockEvents"; @@ -26,8 +26,8 @@ export class MockSpokePoolClient extends SpokePoolClient { public eventManager: EventManager; private destinationTokenForChainOverride: Record = {}; // Allow tester to set the numberOfDeposits() returned by SpokePool at a block height. - public depositIdAtBlock: number[] = []; - public numberOfDeposits = 0; + public depositIdAtBlock: BigNumber[] = []; + public numberOfDeposits = bnZero; constructor( logger: winston.Logger, @@ -57,21 +57,21 @@ export class MockSpokePoolClient extends SpokePoolClient { this.latestBlockSearched = blockNumber; } - setDepositIds(_depositIds: number[]): void { + setDepositIds(_depositIds: BigNumber[]): void { this.depositIdAtBlock = []; if (_depositIds.length === 0) { return; } let lastDepositId = _depositIds[0]; for (let i = 0; i < _depositIds.length; i++) { - if (_depositIds[i] < lastDepositId) { + if (_depositIds[i].lt(lastDepositId)) { throw new Error("deposit ID must be equal to or greater than previous"); } this.depositIdAtBlock[i] = _depositIds[i]; lastDepositId = _depositIds[i]; } } - _getDepositIdAtBlock(blockTag: number): Promise { + _getDepositIdAtBlock(blockTag: number): Promise { return Promise.resolve(this.depositIdAtBlock[blockTag]); } @@ -96,13 +96,13 @@ export class MockSpokePoolClient extends SpokePoolClient { // Update latestDepositIdQueried. const idx = eventsToQuery.indexOf("V3FundsDeposited"); const latestDepositId = (events[idx] ?? []).reduce( - (depositId, event) => Math.max(depositId, (event.args["depositId"] ?? 0) as number), + (depositId, event) => bnMax(depositId, event.args["depositId"] ?? bnZero), this.latestDepositIdQueried ); return Promise.resolve({ success: true, - firstDepositId: 0, + firstDepositId: bnZero, latestDepositId, currentTime, oldestTime: 0, @@ -122,8 +122,8 @@ export class MockSpokePoolClient extends SpokePoolClient { const { blockNumber, transactionIndex } = deposit; let { depositId, depositor, destinationChainId, inputToken, inputAmount, outputToken, outputAmount } = deposit; depositId ??= this.numberOfDeposits; - assert(depositId >= this.numberOfDeposits, `${depositId} < ${this.numberOfDeposits}`); - this.numberOfDeposits = depositId + 1; + assert(depositId.gte(this.numberOfDeposits), `${depositId} < ${this.numberOfDeposits}`); + this.numberOfDeposits = depositId.add(bnOne); destinationChainId ??= random(1, 42161, false); depositor ??= randomAddress(); @@ -168,7 +168,7 @@ export class MockSpokePoolClient extends SpokePoolClient { const { blockNumber, transactionIndex } = fill; let { originChainId, depositId, inputToken, inputAmount, outputAmount, fillDeadline, relayer } = fill; originChainId ??= random(1, 42161, false); - depositId ??= random(1, 100_000, false); + depositId ??= BigNumber.from(random(1, 100_000, false)); inputToken ??= randomAddress(); inputAmount ??= toBNWei(random(1, 1000, false)); outputAmount ??= inputAmount; diff --git a/src/constants.ts b/src/constants.ts index 82579196..a424d3fd 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -10,7 +10,7 @@ export { TOKEN_SYMBOLS_MAP, } from "@across-protocol/constants"; -export const { AddressZero: ZERO_ADDRESS } = ethersConstants; +export const { AddressZero: ZERO_ADDRESS, HashZero: ZERO_BYTES } = ethersConstants; // 2^96 - 1 is a conservative erc20 max allowance. export const MAX_SAFE_ALLOWANCE = "79228162514264337593543950335"; @@ -33,7 +33,7 @@ export const TRANSFER_THRESHOLD_MAX_CONFIG_STORE_VERSION = 1; export const ARWEAVE_TAG_APP_NAME = "across-protocol"; // A hardcoded version number used, by default, to tag all Arweave records. -export const ARWEAVE_TAG_APP_VERSION = 2; +export const ARWEAVE_TAG_APP_VERSION = 3; /** * A default list of chain Ids that the protocol supports. This is outlined diff --git a/src/interfaces/SpokePool.ts b/src/interfaces/SpokePool.ts index 9bccb894..83ea6297 100644 --- a/src/interfaces/SpokePool.ts +++ b/src/interfaces/SpokePool.ts @@ -10,7 +10,7 @@ export interface RelayData { originChainId: number; depositor: string; recipient: string; - depositId: number; + depositId: BigNumber; inputToken: string; inputAmount: BigNumber; outputToken: string; @@ -67,7 +67,7 @@ export interface FillWithBlock extends Fill, SortableEvent {} export interface SpeedUp { depositor: string; depositorSignature: string; - depositId: number; + depositId: BigNumber; originChainId: number; updatedRecipient: string; updatedOutputAmount: BigNumber; diff --git a/src/utils/BigNumberUtils.ts b/src/utils/BigNumberUtils.ts index 27476328..a4c42a29 100644 --- a/src/utils/BigNumberUtils.ts +++ b/src/utils/BigNumberUtils.ts @@ -62,3 +62,14 @@ export const toBN = (num: BigNumberish, rounding: "floor" | "round" | "ceil" = " // Otherwise, it is a string int and we can parse it directly. return BigNumber.from(num.toString()); }; + +/** + * Compares two BigNumbers and returns the maximum. Order does not matter. + * + * @param val The first BigNumber to compare. + * @param cmp The second BigNumber to compare. + * @returns The greater of the two BigNumbers. + */ +export const bnMax = (val: BigNumber, cmp: BigNumber) => { + return val.gt(cmp) ? val : cmp; +}; diff --git a/src/utils/DepositUtils.ts b/src/utils/DepositUtils.ts index 5dd97aec..06d61e81 100644 --- a/src/utils/DepositUtils.ts +++ b/src/utils/DepositUtils.ts @@ -52,7 +52,7 @@ export async function queryHistoricalDepositForFill( const { depositId } = fill; let { firstDepositIdForSpokePool: lowId, lastDepositIdForSpokePool: highId } = spokePoolClient; - if (depositId < lowId || depositId > highId) { + if (depositId.lt(lowId) || depositId.gt(highId)) { return { found: false, code: InvalidFill.DepositIdInvalid, @@ -61,7 +61,7 @@ export async function queryHistoricalDepositForFill( } ({ earliestDepositIdQueried: lowId, latestDepositIdQueried: highId } = spokePoolClient); - if (depositId >= lowId && depositId <= highId) { + if (depositId.gte(lowId) && depositId.lte(highId)) { const originChain = getNetworkName(fill.originChainId); const deposit = spokePoolClient.getDeposit(depositId); if (isDefined(deposit)) { diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 7a321c69..f305e80e 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -3,6 +3,7 @@ import { Result } from "@ethersproject/abi"; import { Contract, Event, EventFilter } from "ethers"; import { Log, SortableEvent } from "../interfaces"; import { delay } from "./common"; +import { isDefined, toBN, BigNumberish } from "./"; const maxRetries = 3; const retrySleepTime = 10; @@ -58,6 +59,13 @@ export function spreadEvent(args: Result | Record): { [key: str if (returnedObject.rootBundleId) { returnedObject.rootBundleId = Number(returnedObject.rootBundleId); } + // If depositId is included in the event, cast it to a BigNumber. Need to check if it is defined since the deposit ID can + // be 0, which would still make this evaluate as false. + if (isDefined(returnedObject.depositId)) { + // Assuming a numeric output, we can safely cast the unknown to BigNumberish since the depositId will either be a uint32 (and therefore a TypeScript `number`), + // or a uint256 (and therefore an ethers BigNumber). + returnedObject.depositId = toBN(returnedObject.depositId as BigNumberish); + } return returnedObject; } diff --git a/src/utils/SpokeUtils.ts b/src/utils/SpokeUtils.ts index 23962bd0..dee660cb 100644 --- a/src/utils/SpokeUtils.ts +++ b/src/utils/SpokeUtils.ts @@ -1,10 +1,10 @@ import assert from "assert"; import { BytesLike, Contract, PopulatedTransaction, providers, utils as ethersUtils } from "ethers"; -import { CHAIN_IDs, MAX_SAFE_DEPOSIT_ID, ZERO_ADDRESS } from "../constants"; +import { CHAIN_IDs, MAX_SAFE_DEPOSIT_ID, ZERO_ADDRESS, ZERO_BYTES } from "../constants"; import { Deposit, Fill, FillStatus, RelayData, SlowFillRequest } from "../interfaces"; import { SpokePoolClient } from "../clients"; import { chunk } from "./ArrayUtils"; -import { BigNumber, toBN } from "./BigNumberUtils"; +import { BigNumber, toBN, bnOne, bnZero } from "./BigNumberUtils"; import { isDefined } from "./TypeGuards"; import { getNetworkName } from "./NetworkUtils"; @@ -70,7 +70,7 @@ export function populateV3Relay( * // where the deposit with deposit ID = targetDepositId was created. */ export async function getBlockRangeForDepositId( - targetDepositId: number, + targetDepositId: BigNumber, initLow: number, initHigh: number, maxSearches: number, @@ -79,6 +79,12 @@ export async function getBlockRangeForDepositId( low: number; high: number; }> { + // We can only perform this search when we have a safe deposit ID. + if (isUnsafeDepositId(targetDepositId)) + throw new Error( + `Target deposit ID ${targetDepositId} is deterministic and therefore unresolvable via a binary search.` + ); + // Resolve the deployment block number. const deploymentBlock = spokePool.deploymentBlock; @@ -109,13 +115,13 @@ export async function getBlockRangeForDepositId( }); // Define a mapping of block numbers to number of deposits at that block. This saves repeated lookups. - const queriedIds: Record = {}; + const queriedIds: Record = {}; // Define a llambda function to get the deposit ID at a block number. This function will first check the // queriedIds cache to see if the deposit ID at the block number has already been queried. If not, it will // make an eth_call request to get the deposit ID at the block number. It will then cache the deposit ID // in the queriedIds cache. - const _getDepositIdAtBlock = async (blockNumber: number): Promise => { + const _getDepositIdAtBlock = async (blockNumber: number): Promise => { queriedIds[blockNumber] ??= await spokePool._getDepositIdAtBlock(blockNumber); return queriedIds[blockNumber]; }; @@ -128,7 +134,7 @@ export async function getBlockRangeForDepositId( // If the deposit ID at the initial high block is less than the target deposit ID, then we know that // the target deposit ID must be greater than the initial high block, so we can throw an error. - if (highestDepositIdInRange <= targetDepositId) { + if (highestDepositIdInRange.lte(targetDepositId)) { // initLow = 5: Deposits Num: 10 // // targetId = 11 <- fail (triggers this error) // 10 <= 11 // // targetId = 10 <- fail (triggers this error) // 10 <= 10 @@ -140,7 +146,7 @@ export async function getBlockRangeForDepositId( // If the deposit ID at the initial low block is greater than the target deposit ID, then we know that // the target deposit ID must be less than the initial low block, so we can throw an error. - if (lowestDepositIdInRange > targetDepositId) { + if (lowestDepositIdInRange.gt(targetDepositId)) { // initLow = 5: Deposits Num: 10 // initLow-1 = 4: Deposits Num: 2 // // targetId = 1 <- fail (triggers this error) @@ -154,7 +160,6 @@ export async function getBlockRangeForDepositId( // Define the low and high block numbers for the binary search. let low = initLow; let high = initHigh; - // Define the number of searches performed so far. let searches = 0; @@ -166,12 +171,12 @@ export async function getBlockRangeForDepositId( const midDepositId = await _getDepositIdAtBlock(mid); // Let's define the latest ID of the current midpoint block. - const accountedIdByMidBlock = midDepositId - 1; + const accountedIdByMidBlock = midDepositId.sub(bnOne); // If our target deposit ID is less than the smallest range of our // midpoint deposit ID range, then we know that the target deposit ID // must be in the lower half of the block range. - if (targetDepositId <= accountedIdByMidBlock) { + if (targetDepositId.lte(accountedIdByMidBlock)) { high = mid; } // If our target deposit ID is greater than the largest range of our @@ -200,10 +205,11 @@ export async function getBlockRangeForDepositId( * @param blockTag The block number to search for the deposit ID at. * @returns The deposit ID. */ -export async function getDepositIdAtBlock(contract: Contract, blockTag: number): Promise { - const depositIdAtBlock = await contract.numberOfDeposits({ blockTag }); +export async function getDepositIdAtBlock(contract: Contract, blockTag: number): Promise { + const _depositIdAtBlock = await contract.numberOfDeposits({ blockTag }); + const depositIdAtBlock = toBN(_depositIdAtBlock); // Sanity check to ensure that the deposit ID is an integer and is greater than or equal to zero. - if (!Number.isInteger(depositIdAtBlock) || depositIdAtBlock < 0) { + if (depositIdAtBlock.lt(bnZero)) { throw new Error("Invalid deposit count"); } return depositIdAtBlock; @@ -244,7 +250,7 @@ export function getRelayHashFromEvent(e: Deposit | Fill | SlowFillRequest): stri return getRelayDataHash(e, e.destinationChainId); } -export function isUnsafeDepositId(depositId: number): boolean { +export function isUnsafeDepositId(depositId: BigNumber): boolean { // SpokePool.unsafeDepositV3() produces a uint256 depositId by hashing the msg.sender, depositor and input // uint256 depositNonce. There is a possibility that this resultant uint256 is less than the maxSafeDepositId (i.e. // the maximum uint32 value) which makes it possible that an unsafeDepositV3's depositId can collide with a safe @@ -380,3 +386,8 @@ export async function findFillBlock( return lowBlockNumber; } + +// Determines if the input address (either a bytes32 or bytes20) is the zero address. +export function isZeroAddress(address: string): boolean { + return address === ZERO_ADDRESS || address === ZERO_BYTES; +} diff --git a/src/utils/ValidatorUtils.ts b/src/utils/ValidatorUtils.ts index c1a56e6d..9806bab4 100644 --- a/src/utils/ValidatorUtils.ts +++ b/src/utils/ValidatorUtils.ts @@ -8,7 +8,7 @@ const HexValidator = define("HexValidator", (v) => ethersUtils.isHexStri const BigNumberValidator = define("BigNumberValidator", (v) => BigNumber.isBigNumber(v)); const V3DepositSchema = object({ - depositId: Min(integer(), 0), + depositId: BigNumberValidator, depositor: AddressValidator, recipient: AddressValidator, inputToken: AddressValidator, diff --git a/test/SpokePoolClient.ValidateFill.ts b/test/SpokePoolClient.ValidateFill.ts index f9c91a5d..b342dacc 100644 --- a/test/SpokePoolClient.ValidateFill.ts +++ b/test/SpokePoolClient.ValidateFill.ts @@ -2,6 +2,8 @@ import { DepositWithBlock, FillStatus, FillType } from "../src/interfaces"; import { SpokePoolClient } from "../src/clients"; import { bnOne, + bnZero, + toBN, InvalidFill, fillStatusArray, relayFillStatus, @@ -299,7 +301,7 @@ describe("SpokePoolClient: Fill Validation", function () { // Throws when low < high await assertPromiseError( - getBlockRangeForDepositId(0, 1, 0, 10, spokePoolClient1), + getBlockRangeForDepositId(bnZero, 1, 0, 10, spokePoolClient1), "Binary search failed because low > high" ); @@ -309,7 +311,7 @@ describe("SpokePoolClient: Fill Validation", function () { // Searching for deposit ID 0 with 10 max searches should return the block range that deposit ID 0 was mined in. // Note: the search range is inclusive, so the range should include the block that deposit ID 0 was mined in. const searchRange0 = await getBlockRangeForDepositId( - 0, + bnZero, spokePool1DeploymentBlock, spokePoolClient1.latestBlockSearched, 10, @@ -326,7 +328,7 @@ describe("SpokePoolClient: Fill Validation", function () { // Where correct block is the block that the deposit ID incremented to the target. // So the correct block for deposit ID 1 is the block that deposit ID 0 was mined in. const searchRange1 = await getBlockRangeForDepositId( - 1, + bnOne, spokePool1DeploymentBlock, spokePoolClient1.latestBlockSearched, 10, @@ -339,7 +341,7 @@ describe("SpokePoolClient: Fill Validation", function () { // Searching for deposit ID 2 that doesn't exist yet should throw. await assertPromiseError( getBlockRangeForDepositId( - 2, + toBN(2), spokePool1DeploymentBlock, spokePoolClient1.latestBlockSearched, 10, @@ -381,14 +383,14 @@ describe("SpokePoolClient: Fill Validation", function () { // will never equal any of the target IDs (e.g. 3,4,5) because multiple deposits were mined in the same block, // incrementing numberOfDeposits() atomically from 2 to 6. const searchRange3 = await getBlockRangeForDepositId( - 3, + toBN(3), spokePool1DeploymentBlock, spokePoolClient1.latestBlockSearched, 10, spokePoolClient1 ); const searchRange4 = await getBlockRangeForDepositId( - 4, + toBN(4), spokePool1DeploymentBlock, spokePoolClient1.latestBlockSearched, 10, @@ -397,7 +399,7 @@ describe("SpokePoolClient: Fill Validation", function () { await assertPromiseError( getBlockRangeForDepositId( - 5, + toBN(5), spokePool1DeploymentBlock, spokePoolClient1.latestBlockSearched, 10, @@ -436,7 +438,7 @@ describe("SpokePoolClient: Fill Validation", function () { const increment = Math.max(0, Math.floor((Math.random() - 0.5) * 10)); depositIds[i] = depositIds[i - 1] + increment; } - fuzzClient.setDepositIds(depositIds); + fuzzClient.setDepositIds(depositIds.map(toBN)); fuzzClient.setLatestBlockNumber(initHigh + 1); for (let i = 0; i < testIterations; i++) { @@ -445,7 +447,7 @@ describe("SpokePoolClient: Fill Validation", function () { // Randomize max # of searches. const maxSearches = Math.floor(Math.random() * 19) + 1; - const results = await getBlockRangeForDepositId(target, initLow, initHigh, maxSearches, fuzzClient); + const results = await getBlockRangeForDepositId(toBN(target), initLow, initHigh, maxSearches, fuzzClient); // The "correct" block is the first block whose previous block's deposit ID is greater than // or equal to the target and whose deposit ID count is greater than the target. diff --git a/test/utils/utils.ts b/test/utils/utils.ts index 569b37a3..08e4782e 100644 --- a/test/utils/utils.ts +++ b/test/utils/utils.ts @@ -362,7 +362,7 @@ export async function depositV3( const { blockNumber, transactionHash, transactionIndex, logIndex } = lastEvent!; return { - depositId: args!.depositId, + depositId: toBN(args!.depositId), originChainId: Number(originChainId), destinationChainId: Number(args!.destinationChainId), depositor: args!.depositor, @@ -403,7 +403,7 @@ export async function requestV3SlowFill( const { blockNumber, transactionHash, transactionIndex, logIndex } = lastEvent!; return { - depositId: args.depositId, + depositId: toBN(args.depositId), originChainId: Number(args.originChainId), destinationChainId, depositor: args.depositor, @@ -443,7 +443,7 @@ export async function fillV3Relay( const { blockNumber, transactionHash, transactionIndex, logIndex } = lastEvent!; return { - depositId: args.depositId, + depositId: toBN(args.depositId), originChainId: Number(args.originChainId), destinationChainId, depositor: args.depositor, @@ -539,7 +539,7 @@ export function buildDepositForRelayerFeeTest( const currentTime = getCurrentTime(); return { - depositId: bnUint32Max.toNumber(), + depositId: bnUint32Max, originChainId: 1, destinationChainId: 10, depositor: randomAddress(), diff --git a/test/validatorUtils.test.ts b/test/validatorUtils.test.ts index 8d77225d..05eff465 100644 --- a/test/validatorUtils.test.ts +++ b/test/validatorUtils.test.ts @@ -3,7 +3,7 @@ import { utils, interfaces } from "../src"; import { ZERO_ADDRESS } from "../src/constants"; import { randomAddress, toBN } from "./utils"; import { cloneDeep } from "lodash"; -import { objectWithBigNumberReviver } from "../src/utils"; +import { objectWithBigNumberReviver, bnOne } from "../src/utils"; import { Deposit } from "../src/interfaces"; describe("validatorUtils", () => { @@ -11,7 +11,7 @@ describe("validatorUtils", () => { let deposit: interfaces.DepositWithBlock; beforeEach(() => { deposit = { - depositId: 1, + depositId: bnOne, depositor: ZERO_ADDRESS, destinationChainId: 1, originChainId: 1, @@ -90,7 +90,7 @@ describe("validatorUtils", () => { }); it("should successfully rehydrate real deposits", () => { const deposits: string[] = [ - '{"inputAmount":{"type":"BigNumber","hex":"0x038d7ea4c68000"},"outputAmount":{"type":"BigNumber","hex":"0x038d7ea4c68000"},"originChainId":42161,"destinationChainId":10,"exclusivityDeadline":1697088000,"fillDeadline":1697088000,"depositId":1160366,"quoteTimestamp":1697088000,"inputToken":"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1","recipient":"0x525D59654479cFaED622C1Ca06f237ce1072c2AB","depositor":"0x269727F088F16E1Aea52Cf5a97B1CD41DAA3f02D","exclusiveRelayer":"0x269727F088F16E1Aea52Cf5a97B1CD41DAA3f02D","message":"0x","blockNumber":139874261,"transactionIndex":1,"logIndex":1,"transactionHash":"0x4c4273f4cceb288a76aa7d6c057a8e3ab571a19711a59a965726e06b04e6b821","realizedLpFeePct":{"type":"BigNumber","hex":"0x016b90ac8ef5b9"},"outputToken":"0x4200000000000000000000000000000000000006","quoteBlockNumber":18332204}', + '{"inputAmount":{"type":"BigNumber","hex":"0x038d7ea4c68000"},"outputAmount":{"type":"BigNumber","hex":"0x038d7ea4c68000"},"originChainId":42161,"destinationChainId":10,"exclusivityDeadline":1697088000,"fillDeadline":1697088000,"depositId":{"type":"BigNumber","hex":"0x11b4ae"},"quoteTimestamp":1697088000,"inputToken":"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1","recipient":"0x525D59654479cFaED622C1Ca06f237ce1072c2AB","depositor":"0x269727F088F16E1Aea52Cf5a97B1CD41DAA3f02D","exclusiveRelayer":"0x269727F088F16E1Aea52Cf5a97B1CD41DAA3f02D","message":"0x","blockNumber":139874261,"transactionIndex":1,"logIndex":1,"transactionHash":"0x4c4273f4cceb288a76aa7d6c057a8e3ab571a19711a59a965726e06b04e6b821","realizedLpFeePct":{"type":"BigNumber","hex":"0x016b90ac8ef5b9"},"outputToken":"0x4200000000000000000000000000000000000006","quoteBlockNumber":18332204}', ]; const rehydratedDeposits = deposits.map((d) => JSON.parse(d, objectWithBigNumberReviver) as Deposit); expect(rehydratedDeposits.every(utils.isDepositFormedCorrectly)).to.be.true;