diff --git a/.eslintrc.js b/.eslintrc.js index 8156c11f4..dd1052aae 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,7 @@ module.exports = { parser: "@typescript-eslint/parser", parserOptions: { ecmaVersion: 12, + project: "./tsconfig.eslint.json", }, rules: { "prettier/prettier": ["warn"], @@ -35,6 +36,8 @@ module.exports = { "@typescript-eslint/no-unused-vars": ["error", { ignoreRestSiblings: true }], "chai-expect/missing-assertion": 2, "no-duplicate-imports": "error", + // "require-await": "error", + "@typescript-eslint/no-floating-promises": ["error"], }, settings: { node: { diff --git a/package.json b/package.json index e62ff5b7d..4f7456d2d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@across-protocol/contracts-v2": "2.4.3", - "@across-protocol/sdk-v2": "0.15.20", + "@across-protocol/sdk-v2": "0.15.21", "@arbitrum/sdk": "^3.1.3", "@defi-wonderland/smock": "^2.3.5", "@eth-optimism/sdk": "^3.1.0", @@ -54,11 +54,13 @@ "test": "hardhat test", "build": "tsc --build", "watch": "tsc --build --incremental --watch", + "build:test": "tsc --project tsconfig.test.json", "clean": "dir=\"./node_modules\"; mv \"${dir}\" \"${dir}_\" 2>/dev/null && rm -r \"${dir}_\" &", "reinstall": "yarn clean && yarn install && yarn build", "update": "git pull && yarn reinstall && yarn version --non-interactive && git show --quiet", "relay": "HARDHAT_CONFIG=./dist/hardhat.config.js node ./dist/index.js --relayer", - "deposit": "yarn ts-node ./scripts/spokepool.ts deposit" + "deposit": "yarn ts-node ./scripts/spokepool.ts deposit", + "dispute": "yarn ts-node ./scripts/hubpool.ts dispute" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.2.3", @@ -72,6 +74,7 @@ "@types/minimist": "^1.2.2", "@types/mocha": "^10.0.1", "@types/node": "^20.3.1", + "@types/sinon": "^10.0.16", "@typescript-eslint/eslint-plugin": "^4.29.1", "@typescript-eslint/parser": "^4.29.1", "chai": "^4.3.7", diff --git a/scripts/hubpool.ts b/scripts/hubpool.ts new file mode 100644 index 000000000..d78a4c3b6 --- /dev/null +++ b/scripts/hubpool.ts @@ -0,0 +1,274 @@ +import minimist from "minimist"; +import { WETH9__factory as WETH9 } from "@across-protocol/contracts-v2"; +import { BigNumber, ethers, Wallet } from "ethers"; +import { config } from "dotenv"; +import { getNetworkName, getSigner } from "../src/utils"; +import * as utils from "./utils"; + +const { MaxUint256, One: bnOne } = ethers.constants; +const { formatEther, formatUnits } = ethers.utils; + +// https://nodejs.org/api/process.html#exit-codes +const NODE_SUCCESS = 0; +const NODE_INPUT_ERR = 9; +const NODE_APP_ERR = 127; // user-defined + +function bnMax(a: BigNumber, b: BigNumber): BigNumber { + const result = a.sub(b); + return result.isZero() || result.gt(0) ? a : b; +} + +async function dispute(args: Record, signer: Wallet): Promise { + const ethBuffer = "0.1"; // Spare ether required to pay for gas. + + const chainId = Number(args.chainId); + const { force, txnHash } = args; + + const network = getNetworkName(chainId); + const hubPool = await utils.getContract(chainId, "HubPool"); + signer = signer.connect(hubPool.provider); + const [bondTokenAddress, bondAmount, proposal, liveness, latestBlock] = await Promise.all([ + hubPool.bondToken(), + hubPool.bondAmount(), + hubPool.rootBundleProposal(), + hubPool.liveness(), + hubPool.provider.getBlock("latest"), + ]); + + const filter = hubPool.filters.ProposeRootBundle(); + const avgBlockTime = 12.5; // @todo import + const fromBlock = Math.floor(latestBlock.number - (liveness - avgBlockTime)); + const bondToken = WETH9.connect(bondTokenAddress, hubPool.provider); + const [bondBalance, decimals, symbol, allowance, proposals] = await Promise.all([ + bondToken.balanceOf(signer.address), + bondToken.decimals(), + bondToken.symbol(), + bondToken.allowance(signer.address, hubPool.address), + hubPool.queryFilter(filter, fromBlock, latestBlock.number), + ]); + + /* Resolve the existing proposal to dump its information. */ + const { poolRebalanceRoot, relayerRefundRoot, slowRelayRoot, challengePeriodEndTimestamp } = proposal; + const rootBundleProposal = proposals.find(({ args }) => { + return ( + args.poolRebalanceRoot === poolRebalanceRoot && + args.relayerRefundRoot === relayerRefundRoot && + args.slowRelayRoot === slowRelayRoot + ); + }); + const fields = { + address: bondToken.address, + symbol, + amount: formatUnits(bondAmount, decimals), + balance: formatUnits(bondBalance, decimals), + }; + + // @dev This works fine but is hackish. Might be nice to refactor later. + const proposalKeys = Object.keys(proposal).filter((key) => isNaN(Number(key))); + const _proposal = { + blockNumber: rootBundleProposal?.blockNumber, + transactionHash: rootBundleProposal?.transactionHash, + ...Object.fromEntries(proposalKeys.map((k) => [k, proposal[k]])), + }; + + const padLeft = [...Object.keys(fields), ...Object.keys(_proposal)].reduce( + (acc, cur) => (cur.length > acc ? cur.length : acc), + 0 + ); + console.log( + `${network} HubPool Dispute Bond:\n` + + Object.entries(fields) + .map(([k, v]) => `\t${k.padEnd(padLeft)} : ${v}`) + .join("\n") + + "\n" + ); + + if (rootBundleProposal === undefined) { + console.log( + `Warning: No matching root bundle proposal found between ${network} blocks ${fromBlock}, ${latestBlock.number}.` + ); + } else { + console.log( + `${network} Root Bundle Proposal:\n` + + Object.entries(_proposal) + .map(([k, v]) => `\t${k.padEnd(padLeft)} : ${v}`) + .join("\n") + + "\n" + ); + } + + if (allowance.lt(bondAmount)) { + console.log(`Approving ${network} HubPool @ ${hubPool.address} to transfer ${symbol}.`); + const approval = await bondToken.connect(signer).approve(hubPool.address, MaxUint256); + console.log(`Approval: ${approval.hash}...`); + await approval.wait(); + } + + if (bondBalance.lt(bondAmount)) { + const buffer = ethers.utils.parseEther(ethBuffer); + const ethBalance = await signer.getBalance(); + if (ethBalance.lt(bondAmount.add(buffer))) { + const minDeposit = bondAmount.add(buffer).sub(ethBalance).sub(bondBalance); + console.log( + `Cannot dispute - insufficient ${symbol} balance.` + ` Deposit at least ${formatUnits(minDeposit, 18)} ETH.` + ); + return false; + } + const depositAmount = bnMax(bondAmount.sub(bondBalance), bnOne); // Enforce minimum 1 Wei for test. + console.log(`Depositing ${formatEther(depositAmount)} @ ${bondToken.address}.`); + const deposit = await bondToken.connect(signer).deposit({ value: depositAmount }); + console.log(`Deposit: ${deposit.hash}...`); + await deposit.wait(); + } + if (latestBlock.timestamp >= challengePeriodEndTimestamp && !force) { + console.log("Nothing to dispute: no active propopsal."); + return txnHash === undefined; + } + + // The txn hash of the proposal must be supplied in order to dispute. + // If no hash was supplied, request the user to re-run with the applicable hash. + if (txnHash !== rootBundleProposal.transactionHash && !force) { + if (txnHash !== undefined) { + console.log(`Invalid proposal transaction hash supplied: ${txnHash}.`); + } + console.log( + "To dispute, re-run with the following transaction hash (WARNING: THIS *WILL* SUBMIT A DISPUTE):\n" + + `\n\t--txnHash ${rootBundleProposal.transactionHash}\n` + + "\nFor example:\n" + + `\n\tyarn dispute --txnHash ${rootBundleProposal.transactionHash}\n` + ); + return txnHash === undefined; + } + + const dispute = await hubPool.connect(signer).disputeRootBundle(); + console.log(`Disputing ${network} HubPool proposal: ${dispute.hash}.`); + await dispute.wait(); + console.log("Disputed HubPool proposal."); + + return true; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +async function search(args: Record, _signer: Wallet): Promise { + const eventName = args.event as string; + const fromBlock = Number(args.fromBlock) || undefined; + const toBlock = Number(args.toBlock) || undefined; + const chainId = Number(args.chainId); + + if (!isNaN(fromBlock) && !isNaN(toBlock) && toBlock < fromBlock) { + throw new Error(`Invalid block range: ${fromBlock}, ${toBlock}`); + } + + const [configStore, hubPool] = await Promise.all([ + utils.getContract(chainId, "ConfigStore"), + utils.getContract(chainId, "HubPool"), + ]); + + const filter = hubPool.filters[eventName]?.(); + if (filter === undefined) { + throw new Error(`Unrecognised HubPool event (${eventName})`); + } + + const events = await hubPool.queryFilter(filter, fromBlock, toBlock); + const CHAIN_ID_INDICES = ethers.utils.formatBytes32String("CHAIN_ID_INDICES"); + for (const { transactionHash, blockNumber, data, topics } of events) { + const [block, liveness, _chainIds] = await Promise.all([ + hubPool.provider.getBlock(blockNumber), + hubPool.liveness({ blockTag: blockNumber }), + configStore.globalConfig(CHAIN_ID_INDICES, { blockTag: blockNumber }), + ]); + + const DEFAULT_CHAIN_IDS = chainId === 1 ? utils.chains : utils.testChains; + const chainIds = _chainIds.length > 0 ? JSON.parse(_chainIds.replaceAll('"', "")) : DEFAULT_CHAIN_IDS; + + const args = hubPool.interface.parseLog({ data, topics }).args; + const eventArgs = Object.keys(args).filter((key) => isNaN(Number(key))); + const dateStr = new Date(Number(block.timestamp * 1000)).toUTCString(); + + const fields = { + blockNumber, + timestamp: `${block.timestamp} (${dateStr})`, + transactionHash, + liveness, + chainIds: chainIds.join(","), + ...Object.fromEntries(eventArgs.map((arg) => [arg, args[arg]])), + }; + const padLeft = Object.keys(fields).reduce((acc, cur) => (cur.length > acc ? cur.length : acc), 0); + console.log( + Object.entries(fields) + .map(([k, v]) => `${k.padEnd(padLeft)} : ${v}`) + .join("\n") + "\n" + ); + } + + return true; +} + +function usage(badInput?: string): boolean { + let usageStr = badInput ? `\nUnrecognized input: "${badInput}".\n\n` : ""; + const walletOpts = "mnemonic|privateKey"; + const runtimeArgs = { + dispute: ["--chainId", "[--txnHash ]"], + search: ["--chainId", "--event ", "[--fromBlock ]", "[--toBlock ]"], + }; + + usageStr += "Usage:\n"; + usageStr += Object.entries(runtimeArgs) + .map(([k, v]) => `\tyarn hubpool --wallet <${walletOpts}> ${k} ${v.join(" ")}`) + .join("\n"); + + console.log(usageStr); + return badInput === undefined ? false : true; +} + +async function run(argv: string[]): Promise { + const opts = { + string: ["chainId", "transactionHash", "event", "fromBlock", "toBlock", "wallet"], + boolean: ["force"], + default: { + chainId: 1, + event: "ProposeRootBundle", + wallet: "mnemonic", + force: false, + }, + alias: { + transactionHash: "txnHash", + }, + unknown: usage, + }; + const args = minimist(argv.slice(1), opts); + + config(); + + let signer: Wallet; + try { + signer = await getSigner({ keyType: args.wallet, cleanEnv: true }); + } catch (err) { + return usage(args.wallet) ? NODE_SUCCESS : NODE_INPUT_ERR; + } + + let result: boolean; + switch (argv[0]) { + case "dispute": + result = await dispute(args, signer); + break; + case "search": + result = await search(args, signer); + break; + default: + return usage() ? NODE_SUCCESS : NODE_INPUT_ERR; + } + + return result ? NODE_SUCCESS : NODE_APP_ERR; +} + +if (require.main === module) { + run(process.argv.slice(2)) + .then(async (result) => { + process.exitCode = result; + }) + .catch(async (error) => { + console.error("Process exited with", error); + process.exitCode = NODE_APP_ERR; + }); +} diff --git a/scripts/spokepool.ts b/scripts/spokepool.ts index c6e284328..c68c22ef7 100644 --- a/scripts/spokepool.ts +++ b/scripts/spokepool.ts @@ -1,35 +1,15 @@ -import assert from "assert"; -import * as contracts from "@across-protocol/contracts-v2"; +import minimist from "minimist"; +import { ExpandedERC20__factory as ERC20 } from "@across-protocol/contracts-v2"; import { LogDescription } from "@ethersproject/abi"; import { Contract, ethers, Wallet } from "ethers"; -import minimist from "minimist"; import { groupBy } from "lodash"; import { config } from "dotenv"; -import { getDeployedContract, getNetworkName, getNodeUrlList, getSigner, resolveTokenSymbols } from "../src/utils"; - -type ERC20 = { - address: string; - decimals: number; - symbol: string; -}; +import { getNetworkName, getSigner, resolveTokenSymbols } from "../src/utils"; +import * as utils from "./utils"; const { MaxUint256, Zero } = ethers.constants; const { isAddress } = ethers.utils; -const testChains = [5, 280]; -const chains = [1, 10, 137, 324, 8453, 42161]; - -function validateChainIds(chainIds: number[]): boolean { - const knownChainIds = [...chains, ...testChains]; - return chainIds.every((chainId) => { - const ok = knownChainIds.includes(chainId); - if (!ok) { - console.log(`Invalid chain ID: ${chainId}`); - } - return ok; - }); -} - function printDeposit(log: LogDescription): void { const { originChainId, originToken } = log.args; const eventArgs = Object.keys(log.args).filter((key) => isNaN(Number(key))); @@ -67,62 +47,12 @@ function printFill(log: LogDescription): void { ); } -/** - * Resolves an ERC20 type from a chain ID, and symbol or address. - * @param token The address or symbol of the token to resolve. - * @param chainId The chain ID to resolve the token on. - * @returns The ERC20 attributes of the token. - */ -function resolveToken(token: string, chainId: number): ERC20 { - // `token` may be an address or a symbol. Normalise it to a symbol for easy lookup. - const symbol = !isAddress(token) - ? token.toUpperCase() - : Object.values(contracts.TOKEN_SYMBOLS_MAP).find(({ addresses }) => addresses[chainId] === token)?.symbol; - - const _token = contracts.TOKEN_SYMBOLS_MAP[symbol]; - if (_token === undefined) { - throw new Error(`Token ${token} on chain ID ${chainId} unrecognised`); - } - - return { - address: _token.addresses[chainId], - decimals: _token.decimals, - symbol: _token.symbol, - }; -} - -function resolveHubChainId(spokeChainId: number): number { - if (chains.includes(spokeChainId)) { - return 1; - } - - assert(testChains.includes(spokeChainId), `Unsupported SpokePool chain ID: ${spokeChainId}`); - return 5; -} - -async function getHubPoolContract(chainId: number): Promise { - const contractName = "HubPool"; - const hubPoolChainId = resolveHubChainId(chainId); - - const hubPool = getDeployedContract(contractName, hubPoolChainId); - const provider = new ethers.providers.StaticJsonRpcProvider(getNodeUrlList(hubPoolChainId, 1)[0]); - return hubPool.connect(provider); -} - -async function getSpokePoolContract(chainId: number): Promise { - const hubPool = await getHubPoolContract(chainId); - const spokePoolAddr = (await hubPool.crossChainContracts(chainId))[1]; - - const contract = new Contract(spokePoolAddr, contracts.SpokePool__factory.abi); - return contract; -} - async function deposit(args: Record, signer: Wallet): Promise { const depositor = await signer.getAddress(); const [fromChainId, toChainId, baseAmount] = [Number(args.from), Number(args.to), Number(args.amount)]; const recipient = (args.recipient as string) ?? depositor; - if (!validateChainIds([fromChainId, toChainId])) { + if (!utils.validateChainIds([fromChainId, toChainId])) { usage(); // no return } const network = getNetworkName(fromChainId); @@ -132,15 +62,15 @@ async function deposit(args: Record, signer: Wallet): P usage(); // no return } - const token = resolveToken(args.token as string, fromChainId); + const token = utils.resolveToken(args.token as string, fromChainId); const tokenSymbol = token.symbol.toUpperCase(); const amount = ethers.utils.parseUnits(baseAmount.toString(), args.decimals ? 0 : token.decimals); - const provider = new ethers.providers.StaticJsonRpcProvider(getNodeUrlList(fromChainId, 1)[0]); + const provider = new ethers.providers.StaticJsonRpcProvider(utils.getProviderUrl(fromChainId)); signer = signer.connect(provider); - const spokePool = (await getSpokePoolContract(fromChainId)).connect(signer); + const spokePool = (await utils.getSpokePoolContract(fromChainId)).connect(signer); - const erc20 = new Contract(token.address, contracts.ExpandedERC20__factory.abi, signer); + const erc20 = new Contract(token.address, ERC20.abi, signer); const allowance = await erc20.allowance(depositor, spokePool.address); if (amount.gt(allowance)) { const approvalAmount = amount.mul(5); @@ -178,10 +108,10 @@ async function deposit(args: Record, signer: Wallet): P // eslint-disable-next-line @typescript-eslint/no-unused-vars async function dumpConfig(args: Record, _signer: Wallet): Promise { const chainId = Number(args.chainId); - const _spokePool = await getSpokePoolContract(chainId); + const _spokePool = await utils.getSpokePoolContract(chainId); - const hubChainId = resolveHubChainId(chainId); - const spokeProvider = new ethers.providers.StaticJsonRpcProvider(getNodeUrlList(chainId, 1)[0]); + const hubChainId = utils.resolveHubChainId(chainId); + const spokeProvider = new ethers.providers.StaticJsonRpcProvider(utils.getProviderUrl(chainId)); const spokePool = _spokePool.connect(spokeProvider); const [spokePoolChainId, hubPool, crossDomainAdmin, weth, _currentTime] = await Promise.all([ @@ -225,7 +155,7 @@ async function fetchTxn(args: Record, _signer: Wallet): const { txnHash } = args; const chainId = Number(args.chainId); - if (!validateChainIds([chainId])) { + if (!utils.validateChainIds([chainId])) { usage(); // no return } @@ -233,8 +163,8 @@ async function fetchTxn(args: Record, _signer: Wallet): throw new Error(`Missing or malformed transaction hash: ${txnHash}`); } - const provider = new ethers.providers.StaticJsonRpcProvider(getNodeUrlList(chainId, 1)[0]); - const spokePool = await getSpokePoolContract(chainId); + const provider = new ethers.providers.StaticJsonRpcProvider(utils.getProviderUrl(chainId)); + const spokePool = await utils.getSpokePoolContract(chainId); const txn = await provider.getTransactionReceipt(txnHash); const fundsDeposited = spokePool.interface.getEventTopic("FundsDeposited"); diff --git a/scripts/utils.ts b/scripts/utils.ts index 7a104888e..99ab46d7f 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -1,4 +1,23 @@ +import assert from "assert"; +import { Contract, ethers, utils as ethersUtils } from "ethers"; import readline from "readline"; +import * as contracts from "@across-protocol/contracts-v2"; +import { getDeployedContract, getNodeUrlList } from "../src/utils"; + +type ERC20 = { + address: string; + decimals: number; + symbol: string; +}; + +export const testChains = [5, 280]; +export const chains = [1, 10, 137, 324, 8453, 42161]; + +// Public RPC endpoints to be used if preferred providers are not defined in the environment. +const fallbackProviders: { [chainId: number]: string } = { + 1: "https://eth.llamarpc.com", + 5: "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", +}; async function askQuestion(query: string) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); @@ -21,3 +40,97 @@ export async function askYesNoQuestion(query: string): Promise { } return askYesNoQuestion(query); } + +/** + * Resolves an ERC20 type from a chain ID, and symbol or address. + * @param token The address or symbol of the token to resolve. + * @param chainId The chain ID to resolve the token on. + * @returns The ERC20 attributes of the token. + */ +export function resolveToken(token: string, chainId: number): ERC20 { + // `token` may be an address or a symbol. Normalise it to a symbol for easy lookup. + const symbol = !ethersUtils.isAddress(token) + ? token.toUpperCase() + : Object.values(contracts.TOKEN_SYMBOLS_MAP).find(({ addresses }) => addresses[chainId] === token)?.symbol; + + const _token = contracts.TOKEN_SYMBOLS_MAP[symbol]; + if (_token === undefined) { + throw new Error(`Token ${token} on chain ID ${chainId} unrecognised`); + } + + return { + address: _token.addresses[chainId], + decimals: _token.decimals, + symbol: _token.symbol, + }; +} + +/** + * @description Verify that an array of chain IDs have known Across deployments. + * @dev This function does not detect if the test and production chain IDs have been mixed. + * @param chainIds Array of chain IDs to validate. + * @returns True if all chainIds are known. + */ +export function validateChainIds(chainIds: number[]): boolean { + const knownChainIds = [...chains, ...testChains]; + return chainIds.every((chainId) => { + const ok = knownChainIds.includes(chainId); + if (!ok) { + console.log(`Invalid chain ID: ${chainId}`); + } + return ok; + }); +} + +/** + * @description Resolve a default provider URL. + * @param chainId Chain ID for the provider to select. + * @returns URL of the provider endpoint. + */ +export function getProviderUrl(chainId: number): string { + try { + return getNodeUrlList(chainId, 1)[0]; + } catch { + return fallbackProviders[chainId]; + } +} + +/** + * @description For a SpokePool chain ID, resolve its corresponding HubPool chain ID. + * @param spokeChainId Chain ID of the SpokePool. + * @returns Chain ID for the corresponding HubPool. + */ +export function resolveHubChainId(spokeChainId: number): number { + if (chains.includes(spokeChainId)) { + return 1; + } + + assert(testChains.includes(spokeChainId), `Unsupported SpokePool chain ID: ${spokeChainId}`); + return 5; +} + +/** + * @description Instantiate an ethers Contract instance. + * @param chainId Chain ID for the contract deployment. + * @param contractName Name of the deployed contract. + * @returns ethers Contract instance. + */ +export async function getContract(chainId: number, contractName: string): Promise { + const contract = getDeployedContract(contractName, chainId); + const provider = new ethers.providers.StaticJsonRpcProvider(getProviderUrl(chainId)); + return contract.connect(provider); +} + +/** + * @description Instantiate an Across SpokePool contract instance. + * @param chainId Chain ID for the SpokePool deployment. + * @returns SpokePool contract instance. + */ +export async function getSpokePoolContract(chainId: number): Promise { + const hubChainId = resolveHubChainId(chainId); + const hubPool = await getContract(hubChainId, "HubPool"); + const spokePoolAddr = (await hubPool.crossChainContracts(chainId))[1]; + + const contract = new Contract(spokePoolAddr, contracts.SpokePool__factory.abi); + return contract; +} diff --git a/src/clients/BundleDataClient.ts b/src/clients/BundleDataClient.ts index ff792ef40..3d74d4704 100644 --- a/src/clients/BundleDataClient.ts +++ b/src/clients/BundleDataClient.ts @@ -20,6 +20,7 @@ import { flattenAndFilterUnfilledDepositsByOriginChain, updateUnfilledDepositsWithMatchedDeposit, getUniqueDepositsInRange, + getUniqueEarlyDepositsInRange, queryHistoricalDepositForFill, } from "../utils"; import { Clients } from "../common"; @@ -30,7 +31,7 @@ import { prettyPrintSpokePoolEvents, } from "../dataworker/DataworkerUtils"; import { getWidestPossibleExpectedBlockRange, isChainDisabled } from "../dataworker/PoolRebalanceUtils"; -import { clients } from "@across-protocol/sdk-v2"; +import { clients, typechain } from "@across-protocol/sdk-v2"; const { refundRequestIsValid, isUBAActivatedAtBlock } = clients; type DataCacheValue = { @@ -38,6 +39,7 @@ type DataCacheValue = { fillsToRefund: FillsToRefund; allValidFills: FillWithBlock[]; deposits: DepositWithBlock[]; + earlyDeposits: typechain.FundsDepositedEvent[]; }; type DataCache = Record; @@ -175,6 +177,7 @@ export class BundleDataClient { fillsToRefund: FillsToRefund; allValidFills: FillWithBlock[]; deposits: DepositWithBlock[]; + earlyDeposits: typechain.FundsDepositedEvent[]; }> { const mainnetStartBlock = getBlockRangeForChain( blockRangesForChains, @@ -197,6 +200,7 @@ export class BundleDataClient { fillsToRefund: FillsToRefund; allValidFills: FillWithBlock[]; deposits: DepositWithBlock[]; + earlyDeposits: typechain.FundsDepositedEvent[]; }> { const key = JSON.stringify(blockRangesForChains); @@ -222,6 +226,7 @@ export class BundleDataClient { const deposits: DepositWithBlock[] = []; const allValidFills: FillWithBlock[] = []; const allInvalidFills: FillWithBlock[] = []; + const earlyDeposits: typechain.FundsDepositedEvent[] = []; // Save refund in-memory for validated fill. const addRefundForValidFill = ( @@ -359,6 +364,19 @@ export class BundleDataClient { ) ); + // TODO: replace this logic with something more clear where all deposits can be queried at once, + // but separated into early and not after the initial filter/query. + earlyDeposits.push( + ...getUniqueEarlyDepositsInRange( + blockRangesForChains, + Number(originChainId), + Number(destinationChainId), + this.chainIdListForBundleEvaluationBlockNumbers, + originClient, + earlyDeposits + ) + ); + const blockRangeForChain = getBlockRangeForChain( blockRangesForChains, Number(destinationChainId), @@ -432,7 +450,7 @@ export class BundleDataClient { }); } - this.loadDataCache[key] = { fillsToRefund, deposits, unfilledDeposits, allValidFills }; + this.loadDataCache[key] = { fillsToRefund, deposits, unfilledDeposits, allValidFills, earlyDeposits }; return this.loadDataFromCache(key); } diff --git a/src/clients/ConfigStoreClient.ts b/src/clients/ConfigStoreClient.ts index 150bfe042..8ea0b50c1 100644 --- a/src/clients/ConfigStoreClient.ts +++ b/src/clients/ConfigStoreClient.ts @@ -1,5 +1,6 @@ import { clients, constants, utils } from "@across-protocol/sdk-v2"; import { Contract, EventSearchConfig, MakeOptional, isDefined, sortEventsDescending, winston } from "../utils"; +import { CONFIG_STORE_VERSION } from "../common"; export const { UBA_MIN_CONFIG_STORE_VERSION } = utils; export const GLOBAL_CONFIG_STORE_KEYS = clients.GLOBAL_CONFIG_STORE_KEYS; @@ -15,7 +16,7 @@ export class ConfigStoreClient extends clients.AcrossConfigStoreClient { readonly logger: winston.Logger, readonly configStore: Contract, readonly eventSearchConfig: MakeOptional = { fromBlock: 0, maxBlockLookBack: 0 }, - readonly configStoreVersion: number + readonly configStoreVersion: number = CONFIG_STORE_VERSION ) { super(logger, configStore, eventSearchConfig, configStoreVersion); diff --git a/src/clients/bridges/BaseAdapter.ts b/src/clients/bridges/BaseAdapter.ts index b9c12a94e..4a2f8867c 100644 --- a/src/clients/bridges/BaseAdapter.ts +++ b/src/clients/bridges/BaseAdapter.ts @@ -30,7 +30,7 @@ import { import { CONTRACT_ADDRESSES } from "../../common"; import { OutstandingTransfers, SortableEvent } from "../../interfaces"; -interface DepositEvent extends SortableEvent { +export interface DepositEvent extends SortableEvent { amount: BigNumber; to: string; } diff --git a/src/clients/bridges/op-stack/OpStackBridgeInterface.ts b/src/clients/bridges/op-stack/OpStackBridgeInterface.ts index 9774032dc..bd4b7bc38 100644 --- a/src/clients/bridges/op-stack/OpStackBridgeInterface.ts +++ b/src/clients/bridges/op-stack/OpStackBridgeInterface.ts @@ -3,7 +3,7 @@ import { Contract, BigNumber, Event, EventSearchConfig } from "../../../utils"; export interface BridgeTransactionDetails { readonly contract: Contract; readonly method: string; - readonly args: any[]; + readonly args: unknown[]; } export interface OpStackBridge { diff --git a/src/dataworker/Dataworker.ts b/src/dataworker/Dataworker.ts index e0c91dea3..0f9bc6dde 100644 --- a/src/dataworker/Dataworker.ts +++ b/src/dataworker/Dataworker.ts @@ -167,10 +167,8 @@ export class Dataworker { spokePoolClients: SpokePoolClientsByChain, latestMainnetBlock?: number ): Promise { - const { fillsToRefund, deposits, allValidFills, unfilledDeposits } = await this.clients.bundleDataClient.loadData( - blockRangesForChains, - spokePoolClients - ); + const { fillsToRefund, deposits, allValidFills, unfilledDeposits, earlyDeposits } = + await this.clients.bundleDataClient.loadData(blockRangesForChains, spokePoolClients); const mainnetBundleEndBlock = getBlockRangeForChain( blockRangesForChains, @@ -193,6 +191,7 @@ export class Dataworker { allValidFills, allValidFillsInRange, unfilledDeposits, + earlyDeposits, true ); } @@ -492,12 +491,8 @@ export class Dataworker { logData = false ): Promise { const timerStart = Date.now(); - const { fillsToRefund, deposits, allValidFills, unfilledDeposits } = await this.clients.bundleDataClient._loadData( - blockRangesForProposal, - spokePoolClients, - false, - logData - ); + const { fillsToRefund, deposits, allValidFills, unfilledDeposits, earlyDeposits } = + await this.clients.bundleDataClient._loadData(blockRangesForProposal, spokePoolClients, false, logData); const allValidFillsInRange = getFillsInRange( allValidFills, blockRangesForProposal, @@ -520,6 +515,7 @@ export class Dataworker { allValidFills, allValidFillsInRange, unfilledDeposits, + earlyDeposits, true ); const relayerRefundRoot = _buildRelayerRefundRoot( @@ -2226,6 +2222,7 @@ export class Dataworker { allValidFills: FillWithBlock[], allValidFillsInRange: FillWithBlock[], unfilledDeposits: UnfilledDeposit[], + earlyDeposits: sdk.typechain.FundsDepositedEvent[], logSlowFillExcessData = false ): Promise { const key = JSON.stringify(blockRangesForChains); @@ -2241,6 +2238,7 @@ export class Dataworker { allValidFills, allValidFillsInRange, unfilledDeposits, + earlyDeposits, this.clients, spokePoolClients, this.chainIdListForBundleEvaluationBlockNumbers, diff --git a/src/dataworker/DataworkerUtils.ts b/src/dataworker/DataworkerUtils.ts index 1d823d175..885b19b49 100644 --- a/src/dataworker/DataworkerUtils.ts +++ b/src/dataworker/DataworkerUtils.ts @@ -1,4 +1,4 @@ -import { utils } from "@across-protocol/sdk-v2"; +import { utils, typechain } from "@across-protocol/sdk-v2"; import { SpokePoolClient } from "../clients"; import { spokesThatHoldEthAndWeth } from "../common/Constants"; import { CONTRACT_ADDRESSES } from "../common/ContractAddresses"; @@ -38,6 +38,7 @@ import { initializeRunningBalancesFromRelayerRepayments, subtractExcessFromPreviousSlowFillsFromRunningBalances, updateRunningBalanceForDeposit, + updateRunningBalanceForEarlyDeposit, } from "./PoolRebalanceUtils"; import { getAmountToReturnForRelayerRefundLeaf, @@ -328,6 +329,7 @@ export async function _buildPoolRebalanceRoot( allValidFills: FillWithBlock[], allValidFillsInRange: FillWithBlock[], unfilledDeposits: UnfilledDeposit[], + earlyDeposits: typechain.FundsDepositedEvent[], clients: DataworkerClients, spokePoolClients: SpokePoolClientsByChain, chainIdListForBundleEvaluationBlockNumbers: number[], @@ -383,6 +385,19 @@ export async function _buildPoolRebalanceRoot( updateRunningBalanceForDeposit(runningBalances, clients.hubPoolClient, deposit, deposit.amount.mul(toBN(-1))); }); + earlyDeposits.forEach((earlyDeposit) => { + updateRunningBalanceForEarlyDeposit( + runningBalances, + clients.hubPoolClient, + earlyDeposit, + // TODO: fix this. + // Because cloneDeep drops the non-array elements of args, we have to use the index rather than the name. + // As a fix, earlyDeposits should be treated similarly to other events and transformed at ingestion time + // into a type that is more digestable rather than a raw event. + earlyDeposit.args[0].mul(toBN(-1)) + ); + }); + // Add to the running balance value from the last valid root bundle proposal for {chainId, l1Token} // combination if found. addLastRunningBalance(latestMainnetBlock, runningBalances, clients.hubPoolClient); diff --git a/src/dataworker/PoolRebalanceUtils.ts b/src/dataworker/PoolRebalanceUtils.ts index e2ef9b8e6..8be708a54 100644 --- a/src/dataworker/PoolRebalanceUtils.ts +++ b/src/dataworker/PoolRebalanceUtils.ts @@ -1,3 +1,4 @@ +import { typechain } from "@across-protocol/sdk-v2"; import { ConfigStoreClient, HubPoolClient, SpokePoolClient } from "../clients"; import { Clients } from "../common"; import * as interfaces from "../interfaces"; @@ -77,6 +78,28 @@ export function updateRunningBalanceForDeposit( updateRunningBalance(runningBalances, deposit.originChainId, l1TokenCounterpart, updateAmount); } +export function updateRunningBalanceForEarlyDeposit( + runningBalances: interfaces.RunningBalances, + hubPoolClient: HubPoolClient, + deposit: typechain.FundsDepositedEvent, + updateAmount: BigNumber +): void { + const l1TokenCounterpart = hubPoolClient.getL1TokenCounterpartAtBlock( + Number(deposit.args.originChainId.toString()), + deposit.args.originToken, + // TODO: this must be handled s.t. it doesn't depend on when this is run. + // For now, tokens do not change their mappings often, so this will work, but + // to keep the system resilient, this must be updated. + hubPoolClient.latestBlockNumber + ); + updateRunningBalance( + runningBalances, + Number(deposit.args.originChainId.toString()), + l1TokenCounterpart, + updateAmount + ); +} + export function addLastRunningBalance( latestMainnetBlock: number, runningBalances: interfaces.RunningBalances, diff --git a/src/scripts/testUBAClient.ts b/src/scripts/testUBAClient.ts index ae1040966..6f02cb7c9 100644 --- a/src/scripts/testUBAClient.ts +++ b/src/scripts/testUBAClient.ts @@ -146,4 +146,4 @@ export async function run(_logger: winston.Logger): Promise { } // eslint-disable-next-line no-process-exit -run(Logger).then(async () => process.exit(0)); +void run(Logger).then(async () => process.exit(0)); diff --git a/src/scripts/validateRootBundle.ts b/src/scripts/validateRootBundle.ts index 2d9642bd2..3a61434e6 100644 --- a/src/scripts/validateRootBundle.ts +++ b/src/scripts/validateRootBundle.ts @@ -207,4 +207,4 @@ export async function run(_logger: winston.Logger): Promise { } // eslint-disable-next-line no-process-exit -run(Logger).then(async () => process.exit(0)); +void run(Logger).then(async () => process.exit(0)); diff --git a/src/scripts/validateRunningBalances.ts b/src/scripts/validateRunningBalances.ts index 6c0f18982..e8d43985d 100644 --- a/src/scripts/validateRunningBalances.ts +++ b/src/scripts/validateRunningBalances.ts @@ -456,4 +456,4 @@ export async function run(_logger: winston.Logger): Promise { } // eslint-disable-next-line no-process-exit -run(Logger).then(() => process.exit(0)); +void run(Logger).then(() => process.exit(0)); diff --git a/src/utils/DepositUtils.ts b/src/utils/DepositUtils.ts index 0d64bec88..517448b8b 100644 --- a/src/utils/DepositUtils.ts +++ b/src/utils/DepositUtils.ts @@ -3,7 +3,7 @@ import { Deposit, DepositWithBlock, Fill, UnfilledDeposit, UnfilledDepositsForOr import { SpokePoolClient } from "../clients"; import { assign, toBN, isFirstFillForDeposit } from "./"; import { getBlockRangeForChain } from "../dataworker/DataworkerUtils"; -import { utils } from "@across-protocol/sdk-v2"; +import { utils, typechain } from "@across-protocol/sdk-v2"; const { validateFillForDeposit } = utils; export function getDepositPath(deposit: Deposit): string { @@ -93,6 +93,32 @@ export function getUniqueDepositsInRange( ) as DepositWithBlock[]; } +export function getUniqueEarlyDepositsInRange( + blockRangesForChains: number[][], + originChain: number, + destinationChain: number, + chainIdListForBundleEvaluationBlockNumbers: number[], + originClient: SpokePoolClient, + existingUniqueDeposits: typechain.FundsDepositedEvent[] +): typechain.FundsDepositedEvent[] { + const originChainBlockRange = getBlockRangeForChain( + blockRangesForChains, + originChain, + chainIdListForBundleEvaluationBlockNumbers + ); + return (originClient["earlyDeposits"] as unknown as typechain.FundsDepositedEvent[]).filter( + (deposit: typechain.FundsDepositedEvent) => + deposit.blockNumber <= originChainBlockRange[1] && + deposit.blockNumber >= originChainBlockRange[0] && + deposit.args.destinationChainId.toString() === destinationChain.toString() && + !existingUniqueDeposits.some( + (existingDeposit) => + existingDeposit.args.originChainId.toString() === deposit.args.originChainId.toString() && + existingDeposit.args.depositId.toString() === deposit.args.depositId.toString() + ) + ); +} + export function isDepositSpedUp(deposit: Deposit): boolean { return deposit.speedUpSignature !== undefined && deposit.newRelayerFeePct !== undefined; } diff --git a/src/utils/ProviderUtils.ts b/src/utils/ProviderUtils.ts index 9f60c3dcd..4808790e8 100644 --- a/src/utils/ProviderUtils.ts +++ b/src/utils/ProviderUtils.ts @@ -85,13 +85,21 @@ function compareRpcResults(method: string, rpcResultA: any, rpcResultB: any): bo if (method === "eth_getBlockByNumber") { // We've seen RPC's disagree on the miner field, for example when Polygon nodes updated software that // led alchemy and quicknode to disagree on the the miner field's value. - return compareResultsAndFilterIgnoredKeys(["miner"], rpcResultA, rpcResultB); + return compareResultsAndFilterIgnoredKeys( + [ + "miner", // polygon (sometimes) + "l1BatchNumber", // zkSync + "l1BatchTimestamp", // zkSync + ], + rpcResultA, + rpcResultB + ); } else if (method === "eth_getLogs") { // We've seen some RPC's like QuickNode add in transactionLogIndex which isn't in the // JSON RPC spec: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getfilterchanges // Additional reference: https://github.com/ethers-io/ethers.js/issues/1721 // 2023-08-31 Added blockHash because of upstream zkSync provider disagreements. Consider removing later. - return compareResultsAndFilterIgnoredKeys(["blockHash", "transactionLogIndex"], rpcResultA, rpcResultB); + return compareResultsAndFilterIgnoredKeys(["transactionLogIndex"], rpcResultA, rpcResultB); } else { return lodash.isEqual(rpcResultA, rpcResultB); } diff --git a/src/utils/RedisUtils.ts b/src/utils/RedisUtils.ts index e0ff3c3d5..4f70b54fb 100644 --- a/src/utils/RedisUtils.ts +++ b/src/utils/RedisUtils.ts @@ -84,7 +84,7 @@ export async function disconnectRedisClient(logger?: winston.Logger): Promise + obj as { + l2Gas: number; + l2GasLimit: number; + l2GasPrice: number; + transactionSubmissionData: number; + }; + describe("AdapterManager: Send tokens cross-chain", async function () { beforeEach(async function () { [relayer, owner] = await ethers.getSigners(); @@ -110,7 +118,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens["usdc"], // l1 token getL2TokenAddresses(mainnetTokens["usdc"])[chainId], // l2 token amountToSend, // amount - (adapterManager.adapters[chainId] as any).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas "0x" // data ); @@ -127,7 +135,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens["dai"], // l1 token getL2TokenAddresses(mainnetTokens["dai"])[chainId], // l2 token amountToSend, // amount - (adapterManager.adapters[chainId] as any)?.l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas "0x" // data ); @@ -136,7 +144,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { expect(l1AtomicDepositor.bridgeWethToOvm).to.have.been.calledWith( relayer.address, // to amountToSend, // amount - (adapterManager.adapters[chainId] as any).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas chainId // chainId ); }); @@ -181,27 +189,27 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens["usdc"], // token relayer.address, // to amountToSend, // amount - (adapterManager.adapters[chainId] as any).l2GasLimit, // maxGas - (adapterManager.adapters[chainId] as any).l2GasPrice, // gasPriceBid - (adapterManager.adapters[chainId] as any).transactionSubmissionData // data + addAttrib(adapterManager.adapters[chainId]).l2GasLimit, // maxGas + addAttrib(adapterManager.adapters[chainId]).l2GasPrice, // gasPriceBid + addAttrib(adapterManager.adapters[chainId]).transactionSubmissionData // data ); await adapterManager.sendTokenCrossChain(relayer.address, chainId, mainnetTokens["wbtc"], amountToSend); expect(l1ArbitrumBridge.outboundTransfer).to.have.been.calledWith( mainnetTokens["wbtc"], // token relayer.address, // to amountToSend, // amount - (adapterManager.adapters[chainId] as any).l2GasLimit, // maxGas - (adapterManager.adapters[chainId] as any).l2GasPrice, // gasPriceBid - (adapterManager.adapters[chainId] as any).transactionSubmissionData // data + addAttrib(adapterManager.adapters[chainId]).l2GasLimit, // maxGas + addAttrib(adapterManager.adapters[chainId]).l2GasPrice, // gasPriceBid + addAttrib(adapterManager.adapters[chainId]).transactionSubmissionData // data ); await adapterManager.sendTokenCrossChain(relayer.address, chainId, mainnetTokens["dai"], amountToSend); expect(l1ArbitrumBridge.outboundTransfer).to.have.been.calledWith( mainnetTokens["dai"], // token relayer.address, // to amountToSend, // amount - (adapterManager.adapters[chainId] as any).l2GasLimit, // maxGas - (adapterManager.adapters[chainId] as any).l2GasPrice, // gasPriceBid - (adapterManager.adapters[chainId] as any).transactionSubmissionData // data + addAttrib(adapterManager.adapters[chainId]).l2GasLimit, // maxGas + addAttrib(adapterManager.adapters[chainId]).l2GasPrice, // gasPriceBid + addAttrib(adapterManager.adapters[chainId]).transactionSubmissionData // data ); // Weth can be bridged like a standard ERC20 token to arbitrum. await adapterManager.sendTokenCrossChain(relayer.address, chainId, mainnetTokens["weth"], amountToSend); @@ -209,9 +217,9 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens["weth"], // token relayer.address, // to amountToSend, // amount - (adapterManager.adapters[chainId] as any).l2GasLimit, // maxGas - (adapterManager.adapters[chainId] as any).l2GasPrice, // gasPriceBid - (adapterManager.adapters[chainId] as any).transactionSubmissionData // data + addAttrib(adapterManager.adapters[chainId]).l2GasLimit, // maxGas + addAttrib(adapterManager.adapters[chainId]).l2GasPrice, // gasPriceBid + addAttrib(adapterManager.adapters[chainId]).transactionSubmissionData // data ); }); @@ -248,7 +256,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { zksync.utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, relayer.address ); - expect(l1AtomicDepositor.bridgeWethToZkSync).to.have.been.calledWithValue(0); + expect(l1AtomicDepositor.bridgeWethToZkSync).to.have.been.calledWithValue(toBN(0)); }); it("Correctly sends tokens to chain: Base", async function () { const chainId = 8453; // Base ChainId @@ -258,7 +266,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens["usdc"], // l1 token getL2TokenAddresses(mainnetTokens["usdc"])[chainId], // l2 token amountToSend, // amount - (adapterManager.adapters[chainId] as any).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas "0x" // data ); @@ -268,7 +276,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens["dai"], // l1 token getL2TokenAddresses(mainnetTokens["dai"])[chainId], // l2 token amountToSend, // amount - (adapterManager.adapters[chainId] as any).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas "0x" // data ); @@ -277,7 +285,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { expect(l1AtomicDepositor.bridgeWethToOvm).to.have.been.calledWith( relayer.address, // to amountToSend, // amount - (adapterManager.adapters[chainId] as any).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas chainId // chainId ); }); @@ -304,31 +312,31 @@ async function seedMocks() { async function constructChainSpecificFakes() { // Shared contracts. - l1AtomicDepositor = await makeFake("atomicDepositor", CONTRACT_ADDRESSES[1].atomicDepositor.address!); + l1AtomicDepositor = await makeFake("atomicDepositor", CONTRACT_ADDRESSES[1].atomicDepositor.address); // Optimism contracts - l1OptimismBridge = await makeFake("ovmStandardBridge_10", CONTRACT_ADDRESSES[1].ovmStandardBridge_10.address!); - l1OptimismDaiBridge = await makeFake("daiOptimismBridge", CONTRACT_ADDRESSES[1].daiOptimismBridge.address!); - l1OptimismSnxBridge = await makeFake("snxOptimismBridge", CONTRACT_ADDRESSES[1].snxOptimismBridge.address!); + l1OptimismBridge = await makeFake("ovmStandardBridge_10", CONTRACT_ADDRESSES[1].ovmStandardBridge_10.address); + l1OptimismDaiBridge = await makeFake("daiOptimismBridge", CONTRACT_ADDRESSES[1].daiOptimismBridge.address); + l1OptimismSnxBridge = await makeFake("snxOptimismBridge", CONTRACT_ADDRESSES[1].snxOptimismBridge.address); // Polygon contracts l1PolygonRootChainManager = await makeFake( "polygonRootChainManager", - CONTRACT_ADDRESSES[1].polygonRootChainManager.address! + CONTRACT_ADDRESSES[1].polygonRootChainManager.address ); // Arbitrum contracts l1ArbitrumBridge = await makeFake( "arbitrumErc20GatewayRouter", - CONTRACT_ADDRESSES[1].arbitrumErc20GatewayRouter.address! + CONTRACT_ADDRESSES[1].arbitrumErc20GatewayRouter.address ); // zkSync contracts - l1ZkSyncBridge = await makeFake("zkSyncDefaultErc20Bridge", CONTRACT_ADDRESSES[1].zkSyncDefaultErc20Bridge.address!); - l1MailboxContract = await makeFake("zkSyncMailbox", CONTRACT_ADDRESSES[1].zkSyncMailbox.address!); + l1ZkSyncBridge = await makeFake("zkSyncDefaultErc20Bridge", CONTRACT_ADDRESSES[1].zkSyncDefaultErc20Bridge.address); + l1MailboxContract = await makeFake("zkSyncMailbox", CONTRACT_ADDRESSES[1].zkSyncMailbox.address); // Base contracts - l1BaseBridge = await makeFake("ovmStandardBridge_8453", CONTRACT_ADDRESSES[1].ovmStandardBridge_8453.address!); + l1BaseBridge = await makeFake("ovmStandardBridge_8453", CONTRACT_ADDRESSES[1].ovmStandardBridge_8453.address); } async function makeFake(contractName: string, address: string) { diff --git a/test/AdapterManager.getOutstandingCrossChainTokenTransferAmount.ts b/test/AdapterManager.getOutstandingCrossChainTokenTransferAmount.ts index 1e7c68409..095963200 100644 --- a/test/AdapterManager.getOutstandingCrossChainTokenTransferAmount.ts +++ b/test/AdapterManager.getOutstandingCrossChainTokenTransferAmount.ts @@ -1,5 +1,6 @@ +import { TransactionResponse } from "@ethersproject/abstract-provider"; import { SpokePoolClient } from "../src/clients"; -import { BaseAdapter } from "../src/clients/bridges"; +import { BaseAdapter, DepositEvent } from "../src/clients/bridges"; import { OutstandingTransfers } from "../src/interfaces"; import { createSpyLogger, expect, toBN } from "./utils"; @@ -11,7 +12,8 @@ class TestAdapter extends BaseAdapter { }, 1, ["0xmonitored"], - createSpyLogger().spyLogger + createSpyLogger().spyLogger, + [] ); } @@ -19,14 +21,27 @@ class TestAdapter extends BaseAdapter { const deposits = amounts.map((amount) => { return { amount: toBN(amount) }; }); - this.l1DepositInitiatedEvents = { "0xmonitored": { token: deposits } }; + this.l1DepositInitiatedEvents = { "0xmonitored": { token: deposits as unknown as DepositEvent[] } }; } public setFinalizationEvents(amounts: number[]) { const deposits = amounts.map((amount) => { return { amount: toBN(amount) }; }); - this.l2DepositFinalizedEvents = { "0xmonitored": { token: deposits } }; + this.l2DepositFinalizedEvents = { "0xmonitored": { token: deposits as unknown as DepositEvent[] } }; + } + + getOutstandingCrossChainTransfers(): Promise { + throw new Error("This Test Adapter has not implemented this FN."); + } + sendTokenToTargetChain(): Promise { + throw new Error("This Test Adapter has not implemented this FN."); + } + checkTokenApprovals(): Promise { + throw new Error("This Test Adapter has not implemented this FN."); + } + wrapEthIfAboveThreshold(): Promise { + throw new Error("This Test Adapter has not implemented this FN."); } } diff --git a/test/Dataworker.buildRoots.ts b/test/Dataworker.buildRoots.ts index aa1761042..10046fee4 100644 --- a/test/Dataworker.buildRoots.ts +++ b/test/Dataworker.buildRoots.ts @@ -1,5 +1,5 @@ import { ConfigStoreClient, HubPoolClient, SpokePoolClient } from "../src/clients"; -import { Deposit, Fill, RunningBalances } from "../src/interfaces"; +import { Deposit, DepositWithBlock, Fill, RunningBalances } from "../src/interfaces"; import { EMPTY_MERKLE_ROOT, compareAddresses, @@ -45,6 +45,7 @@ import { lastSpyLogIncludes, sampleRateModel, setupTokensForWallet, + sinon, toBN, toBNWei, } from "./utils"; @@ -67,6 +68,8 @@ let updateAllClients: () => Promise; describe("Dataworker: Build merkle roots", async function () { beforeEach(async function () { + const fastDataworkerResult = await setupFastDataworker(ethers); + configStoreClient = fastDataworkerResult.configStoreClient as unknown as ConfigStoreClient; ({ hubPool, spokePool_1, @@ -74,7 +77,6 @@ describe("Dataworker: Build merkle roots", async function () { spokePool_2, erc20_2, configStore, - configStoreClient, hubPoolClient, l1Token_1, depositor, @@ -87,7 +89,7 @@ describe("Dataworker: Build merkle roots", async function () { spokePoolClient_1, spokePoolClient_2, updateAllClients, - } = await setupFastDataworker(ethers)); + } = fastDataworkerResult); }); it("Build slow relay root", async function () { await updateAllClients(); @@ -150,8 +152,8 @@ describe("Dataworker: Build merkle roots", async function () { const newRelayerFeePct = toBNWei(0.1337); const speedUpSignature = await modifyRelayHelper( newRelayerFeePct, - deposit1.depositId, - deposit1.originChainId!.toString(), + deposit1.depositId.toString(), + deposit1.originChainId.toString(), depositor, deposit1.recipient, "0x" @@ -903,7 +905,7 @@ describe("Dataworker: Build merkle roots", async function () { const destinationChainSpokePoolClient = new SpokePoolClient( createSpyLogger().spyLogger, spokePool_2, - configStoreClient, + hubPoolClient, destinationChainId, spokePoolClients[destinationChainId].deploymentBlock, { fromBlock: fill2Block + 1 } @@ -1446,6 +1448,7 @@ describe("Dataworker: Build merkle roots", async function () { { flow: { ...spokePoolClient_1.getFills()[0], + matchedDeposit: {} as unknown as DepositWithBlock, }, runningBalance: toBNWei("2"), incentiveBalance: toBNWei("2"), @@ -1456,6 +1459,7 @@ describe("Dataworker: Build merkle roots", async function () { { flow: { ...spokePoolClient_1.getFills()[0], + matchedDeposit: {} as unknown as DepositWithBlock, }, runningBalance: toBNWei("1"), incentiveBalance: toBNWei("1"), @@ -1508,7 +1512,7 @@ describe("Dataworker: Build merkle roots", async function () { it("amountToReturn is 0", async function () { await updateAllClients(); // No UBA flows in this test so all amounts to return will be 0 - const { poolRebalanceLeaves } = await dataworkerInstance._UBA_buildPoolRebalanceLeaves( + const { poolRebalanceLeaves } = dataworkerInstance._UBA_buildPoolRebalanceLeaves( getDefaultBlockRange(0), [originChainId, destinationChainId], ubaClient @@ -1519,14 +1523,12 @@ describe("Dataworker: Build merkle roots", async function () { true ); expect( - ( - await dataworkerInstance._UBA_buildRelayerRefundLeaves( - data1.fillsToRefund, - poolRebalanceLeaves, - getDefaultBlockRange(0), - [originChainId, destinationChainId], - ubaClient - ) + dataworkerInstance._UBA_buildRelayerRefundLeaves( + data1.fillsToRefund, + poolRebalanceLeaves, + getDefaultBlockRange(0), + [originChainId, destinationChainId], + ubaClient ).leaves ).to.deep.equal([]); @@ -1580,7 +1582,7 @@ describe("Dataworker: Build merkle roots", async function () { spokePoolClients, true ); - const relayerRefundLeaves1 = await dataworkerInstance._UBA_buildRelayerRefundLeaves( + const relayerRefundLeaves1 = dataworkerInstance._UBA_buildRelayerRefundLeaves( data2.fillsToRefund, poolRebalanceLeaves, getDefaultBlockRange(1), @@ -1619,7 +1621,7 @@ describe("Dataworker: Build merkle roots", async function () { spokePoolClients, true ); - const relayerRefundLeaves3 = await dataworkerInstance._UBA_buildRelayerRefundLeaves( + const relayerRefundLeaves3 = dataworkerInstance._UBA_buildRelayerRefundLeaves( data4.fillsToRefund, poolRebalanceLeaves, getDefaultBlockRange(3), @@ -1661,7 +1663,6 @@ describe("Dataworker: Build merkle roots", async function () { ...spokePoolClient_1.getFills()[0], matchedDeposit: spokePoolClient_2.getDeposits()[0], }, - balancingFee: toBNWei("0.2"), lpFee: toBNWei("0.5"), balancingFee: toBNWei("0.2"), runningBalance: toBNWei("1"), @@ -1756,7 +1757,7 @@ describe("Dataworker: Build merkle roots", async function () { spokePoolClients, true ); - const relayerRefundLeaves2 = await dataworkerInstance._UBA_buildRelayerRefundLeaves( + const relayerRefundLeaves2 = dataworkerInstance._UBA_buildRelayerRefundLeaves( data1.fillsToRefund, poolRebalanceLeaves, getDefaultBlockRange(1), @@ -1769,7 +1770,7 @@ describe("Dataworker: Build merkle roots", async function () { }); it("Refunds are included in UBA mode", async function () { await updateAllClients(); - const { poolRebalanceLeaves } = await dataworkerInstance._UBA_buildPoolRebalanceLeaves( + const { poolRebalanceLeaves } = dataworkerInstance._UBA_buildPoolRebalanceLeaves( getDefaultBlockRange(0), [originChainId, destinationChainId], ubaClient @@ -1805,7 +1806,7 @@ describe("Dataworker: Build merkle roots", async function () { spokePoolClients, true ); - const relayerRefundLeaves1 = await dataworkerInstance._UBA_buildRelayerRefundLeaves( + const relayerRefundLeaves1 = dataworkerInstance._UBA_buildRelayerRefundLeaves( data1.fillsToRefund, poolRebalanceLeaves, getDefaultBlockRange(1), @@ -1822,7 +1823,7 @@ describe("Dataworker: Build merkle roots", async function () { spokePoolClients, true ); - const relayerRefundLeaves2 = await dataworkerInstance._UBA_buildRelayerRefundLeaves( + const relayerRefundLeaves2 = dataworkerInstance._UBA_buildRelayerRefundLeaves( data2.fillsToRefund, poolRebalanceLeaves, getDefaultBlockRange(2), @@ -1971,7 +1972,7 @@ describe("Dataworker: Build merkle roots", async function () { }, ]); - const relayerRefundLeaves3 = await dataworkerInstance._UBA_buildRelayerRefundLeaves( + const relayerRefundLeaves3 = dataworkerInstance._UBA_buildRelayerRefundLeaves( {}, poolRebalanceLeaves, blockRanges, @@ -2031,6 +2032,7 @@ describe("Dataworker: Build merkle roots", async function () { { flow: { ...spokePoolClient_2.getFills()[0], + matchedDeposit: {} as unknown as DepositWithBlock, // Not used in this test. }, lpFee: toBNWei("0.5"), balancingFee: expectedRelayerBalancingFee, diff --git a/test/Dataworker.customSpokePoolClients.ts b/test/Dataworker.customSpokePoolClients.ts index ebe0b5d96..73b8d7e79 100644 --- a/test/Dataworker.customSpokePoolClients.ts +++ b/test/Dataworker.customSpokePoolClients.ts @@ -2,7 +2,16 @@ import { MultiCallerClient, SpokePoolClient } from "../src/clients"; import { MAX_UINT_VAL } from "../src/utils"; import { CHAIN_ID_TEST_LIST, utf8ToHex } from "./constants"; import { setupFastDataworker } from "./fixtures/Dataworker.Fixture"; -import { Contract, ethers, expect, lastSpyLogIncludes, lastSpyLogLevel, spyLogIncludes, spyLogLevel } from "./utils"; +import { + Contract, + ethers, + expect, + lastSpyLogIncludes, + lastSpyLogLevel, + sinon, + spyLogIncludes, + spyLogLevel, +} from "./utils"; // Tested import { Dataworker } from "../src/dataworker/Dataworker"; diff --git a/test/Dataworker.loadData.ts b/test/Dataworker.loadData.ts index 5fe5bf105..7cb4c347b 100644 --- a/test/Dataworker.loadData.ts +++ b/test/Dataworker.loadData.ts @@ -37,7 +37,7 @@ import { import { spokePoolClientsToProviders } from "../src/common"; import { Dataworker } from "../src/dataworker/Dataworker"; // Tested -import { DepositWithBlock, Fill } from "../src/interfaces"; +import { Deposit, DepositWithBlock, Fill } from "../src/interfaces"; import { MAX_UINT_VAL, getRealizedLpFeeForFills, getRefundForFills, toBN } from "../src/utils"; let spokePool_1: Contract, erc20_1: Contract, spokePool_2: Contract, erc20_2: Contract; @@ -75,7 +75,6 @@ describe("Dataworker: Load data used in all functions", async function () { spokePoolClient_1, spokePoolClient_2, spokePoolClients, - configStoreClient, updateAllClients, spy, } = await setupDataworker(ethers, 25, 25, toBN(0), 0)); @@ -104,10 +103,13 @@ describe("Dataworker: Load data used in all functions", async function () { deposits: [], fillsToRefund: {}, allValidFills: [], + earlyDeposits: [], }); }); describe("Computing refunds for bundles", function () { - let fill1: Fill, deposit1; + let fill1: Fill; + let deposit1: Deposit; + beforeEach(async function () { await updateAllClients(); @@ -580,7 +582,7 @@ describe("Dataworker: Load data used in all functions", async function () { erc20_1, depositor, relayer, - { ...deposit2, realizedLpFeePct: deposit2.realizedLpFeePct.div(toBN(2)) }, + { ...deposit2, realizedLpFeePct: deposit2.realizedLpFeePct?.div(toBN(2)) }, 0.25 ); // Note: This fill has identical deposit data to fill2 except for the destination token being different @@ -729,7 +731,10 @@ describe("Dataworker: Load data used in all functions", async function () { ); const blockNumber = await spokePool_2.provider.getBlockNumber(); const blockTimestamp = (await spokePool_2.provider.getBlock(blockNumber)).timestamp; - const realizedLpFeePctData = await hubPoolClient.computeRealizedLpFeePct(deposit1, l1Token_1.address); + const realizedLpFeePctData = await hubPoolClient.computeRealizedLpFeePct( + { ...deposit1, blockNumber }, + l1Token_1.address + ); // Should include all deposits, even those not matched by a relay await updateAllClients(); diff --git a/test/Dataworker.proposeRootBundle.ts b/test/Dataworker.proposeRootBundle.ts index eb9e067a2..853283688 100644 --- a/test/Dataworker.proposeRootBundle.ts +++ b/test/Dataworker.proposeRootBundle.ts @@ -11,6 +11,7 @@ import { expect, lastSpyLogIncludes, lastSpyLogLevel, + sinon, toBNWei, } from "./utils"; @@ -38,7 +39,7 @@ describe("Dataworker: Propose root bundle", async function () { erc20_1, spokePool_2, erc20_2, - configStoreClient, + mockedConfigStoreClient: configStoreClient, configStore, hubPoolClient, l1Token_1, @@ -57,8 +58,8 @@ describe("Dataworker: Propose root bundle", async function () { const getMostRecentLog = (_spy: sinon.SinonSpy, message: string) => { return spy .getCalls() - .sort((logA: any, logB: any) => logB.callId - logA.callId) // Sort by callId in descending order - .find((log: any) => log.lastArg.message.includes(message)).lastArg; + .sort((logA: unknown, logB: unknown) => logB["callId"] - logA["callId"]) // Sort by callId in descending order + .find((log: unknown) => log["lastArg"]["message"].includes(message)).lastArg; }; // TEST 1: diff --git a/test/Dataworker.validateRootBundle.ts b/test/Dataworker.validateRootBundle.ts index a33c566a3..e9dd617ba 100644 --- a/test/Dataworker.validateRootBundle.ts +++ b/test/Dataworker.validateRootBundle.ts @@ -20,6 +20,7 @@ import { expect, lastSpyLogIncludes, lastSpyLogLevel, + sinon, spyLogIncludes, } from "./utils"; @@ -45,7 +46,7 @@ describe("Dataworker: Validate pending root bundle", async function () { spokePool_1, erc20_1, spokePool_2, - configStoreClient, + mockedConfigStoreClient: configStoreClient, configStore, hubPoolClient, l1Token_1, diff --git a/test/HubPoolClient.DepositToDestinationToken.ts b/test/HubPoolClient.DepositToDestinationToken.ts index cde36b6d1..1be88a8dd 100644 --- a/test/HubPoolClient.DepositToDestinationToken.ts +++ b/test/HubPoolClient.DepositToDestinationToken.ts @@ -1,5 +1,11 @@ import { ConfigStoreClient, HubPoolClient } from "../src/clients"; -import { randomDestinationToken, randomDestinationToken2, randomL1Token, randomOriginToken } from "./constants"; +import { + CONFIG_STORE_VERSION, + randomDestinationToken, + randomDestinationToken2, + randomL1Token, + randomOriginToken, +} from "./constants"; import { Contract, SignerWithAddress, @@ -33,7 +39,7 @@ describe("HubPoolClient: Deposit to Destination Token", async function () { const logger = createSpyLogger().spyLogger; const { configStore } = await deployConfigStore(owner, []); - const configStoreClient = new ConfigStoreClient(logger, configStore); + const configStoreClient = new ConfigStoreClient(logger, configStore, { fromBlock: 0 }, CONFIG_STORE_VERSION); await configStoreClient.update(); hubPoolClient = new HubPoolClient(logger, hubPool, configStoreClient); diff --git a/test/HubPoolClient.L1Tokens.ts b/test/HubPoolClient.L1Tokens.ts index 34481b3d3..c55a5faef 100644 --- a/test/HubPoolClient.L1Tokens.ts +++ b/test/HubPoolClient.L1Tokens.ts @@ -9,6 +9,7 @@ import { createSpyLogger, } from "./utils"; import { ConfigStoreClient, HubPoolClient } from "../src/clients"; +import { CONFIG_STORE_VERSION } from "./constants"; let hubPool: Contract, lpTokenFactory: Contract; let owner: SignerWithAddress; @@ -27,7 +28,7 @@ describe("HubPoolClient: L1Tokens", async function () { const logger = createSpyLogger().spyLogger; const { configStore } = await deployConfigStore(owner, []); - const configStoreClient = new ConfigStoreClient(logger, configStore); + const configStoreClient = new ConfigStoreClient(logger, configStore, { fromBlock: 0 }, CONFIG_STORE_VERSION); await configStoreClient.update(); hubPoolClient = new HubPoolClient(logger, hubPool, configStoreClient); diff --git a/test/HubPoolClient.Utilization.ts b/test/HubPoolClient.Utilization.ts index f3aae8c61..bc5539187 100644 --- a/test/HubPoolClient.Utilization.ts +++ b/test/HubPoolClient.Utilization.ts @@ -121,6 +121,7 @@ describe("HubPool Utilization", async function () { destinationChainId: repaymentChainId, relayerFeePct: toBN(0), quoteTimestamp: initialRateModelUpdateTime, + blockNumber: initialRateModelUpdate.blockNumber, // Quote time needs to be >= first rate model event time }; diff --git a/test/InventoryClient.InventoryRebalance.ts b/test/InventoryClient.InventoryRebalance.ts index d3f8a246b..a10de5a53 100644 --- a/test/InventoryClient.InventoryRebalance.ts +++ b/test/InventoryClient.InventoryRebalance.ts @@ -8,6 +8,7 @@ import { hubPoolFixture, lastSpyLogIncludes, randomAddress, + sinon, spyLogIncludes, toBN, toWei, @@ -78,7 +79,7 @@ describe("InventoryClient: Rebalancing inventory", async function () { const { hubPool, dai: l1Token } = await hubPoolFixture(); const { configStore } = await deployConfigStore(owner, [l1Token]); - const configStoreClient = new ConfigStoreClient(spyLogger, configStore); + const configStoreClient = new ConfigStoreClient(spyLogger, configStore, { fromBlock: 0 }, 0); await configStoreClient.update(); hubPoolClient = new MockHubPoolClient(spyLogger, hubPool, configStoreClient); diff --git a/test/InventoryClient.RefundChain.ts b/test/InventoryClient.RefundChain.ts index af785419f..0f379da82 100644 --- a/test/InventoryClient.RefundChain.ts +++ b/test/InventoryClient.RefundChain.ts @@ -9,6 +9,7 @@ import { hubPoolFixture, lastSpyLogIncludes, randomAddress, + sinon, toBN, toWei, winston, @@ -112,6 +113,7 @@ describe("InventoryClient: Refund chain selection", async function () { destinationChainId: 10, relayerFeePct: toBN(1337), quoteTimestamp: 1234, + message: "0x", }; hubPoolClient.setReturnedL1TokenForDeposit(mainnetWeth); }); diff --git a/test/Monitor.ts b/test/Monitor.ts index e1e20f38b..e553e02ac 100644 --- a/test/Monitor.ts +++ b/test/Monitor.ts @@ -98,7 +98,7 @@ describe("Monitor", async function () { // @dev: Force maxRelayerLookBack to undefined to skip lookback block resolution. // This overrules the readonly property for MonitorConfig.maxRelayerLookBack (...bodge!!). // @todo: Relocate getUnfilledDeposits() into a class to permit it to be overridden in test. - monitorConfig["maxRelayerLookBack"] = undefined; + (monitorConfig as unknown)["maxRelayerLookBack"] = undefined; // Set the config store version to 0 to match the default version in the ConfigStoreClient. process.env.CONFIG_STORE_VERSION = "0"; diff --git a/test/MultiCallerClient.ts b/test/MultiCallerClient.ts index ad79799f8..b3ce691fd 100644 --- a/test/MultiCallerClient.ts +++ b/test/MultiCallerClient.ts @@ -50,7 +50,7 @@ class DummyMultiCallerClient extends MockedMultiCallerClient { } // encodeFunctionData is called from within MultiCallerClient.buildMultiCallBundle. -function encodeFunctionData(_method: string, args: ReadonlyArray = []): string { +function encodeFunctionData(_method: string, args: ReadonlyArray = []): string { return args.join(" "); } @@ -343,7 +343,7 @@ describe("MultiCallerClient", async function () { const multicallerWithMultisend = new DummyMultiCallerClient(spyLogger, {}, fakeMultisender as unknown as Contract); // Can't pass any transactions to multisender bundler that are permissioned or different chains: - assertPromiseError( + void assertPromiseError( multicallerWithMultisend.buildMultiSenderBundle([ { chainId: 1, @@ -358,7 +358,7 @@ describe("MultiCallerClient", async function () { ] as AugmentedTransaction[]), "Multisender bundle data mismatch" ); - assertPromiseError( + void assertPromiseError( multicallerWithMultisend.buildMultiSenderBundle([ { chainId: 1, diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index 0c81c6715..d33c06899 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -96,7 +96,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { [originChainId, destinationChainId], originChainId, false - ); + ) as unknown as ConfigStoreClient; await configStoreClient.update(); hubPoolClient = new HubPoolClient(spyLogger, hubPool, configStoreClient); @@ -293,8 +293,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { const newRecipient = randomAddress(); const speedUpSignature = await modifyRelayHelper( newRelayerFeePct, - deposit1.depositId, - deposit1.originChainId!.toString(), + deposit1.depositId.toString(), + deposit1.originChainId.toString(), depositor, newRecipient, newMessage @@ -307,8 +307,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { }; const unusedSpeedUpSignature = await modifyRelayHelper( unusedSpeedUp.relayerFeePct, - deposit1.depositId, - deposit1.originChainId!.toString(), + deposit1.depositId.toString(), + deposit1.originChainId.toString(), depositor, unusedSpeedUp.recipient, unusedSpeedUp.message @@ -351,8 +351,8 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { }; const emptyMessageSpeedUpSignature = await modifyRelayHelper( emptyMessageSpeedUp.relayerFeePct, - deposit1.depositId, - deposit1.originChainId!.toString(), + deposit1.depositId.toString(), + deposit1.originChainId.toString(), depositor, emptyMessageSpeedUp.recipient, emptyMessageSpeedUp.message diff --git a/test/Relayer.RefundRequests.ts b/test/Relayer.RefundRequests.ts index 27b222739..0e113079a 100644 --- a/test/Relayer.RefundRequests.ts +++ b/test/Relayer.RefundRequests.ts @@ -121,7 +121,7 @@ describe("Relayer: Request refunds for cross-chain repayments", async function ( [originChainId, destinationChainId], originChainId, false - ); + ) as unknown as ConfigStoreClient; await configStoreClient.update(); hubPoolClient = new HubPoolClient(spyLogger, hubPool, configStoreClient); diff --git a/test/Relayer.SlowFill.ts b/test/Relayer.SlowFill.ts index 550c9072c..3b15e222c 100644 --- a/test/Relayer.SlowFill.ts +++ b/test/Relayer.SlowFill.ts @@ -126,6 +126,7 @@ describe("Relayer: Zero sized fill for slow relay", async function () { multiCallerClient, inventoryClient: new MockInventoryClient(), acrossApiClient: new AcrossApiClient(spyLogger, hubPoolClient, spokePoolClients), + ubaClient: null, }, { relayerTokens: [], diff --git a/test/Relayer.TokenShortfall.ts b/test/Relayer.TokenShortfall.ts index 7e178f06c..3adfa6a88 100644 --- a/test/Relayer.TokenShortfall.ts +++ b/test/Relayer.TokenShortfall.ts @@ -123,6 +123,7 @@ describe("Relayer: Token balance shortfall", async function () { multiCallerClient, inventoryClient: new MockInventoryClient(), acrossApiClient: new AcrossApiClient(spyLogger, hubPoolClient, spokePoolClients), + ubaClient: null, }, { relayerTokens: [], diff --git a/test/Relayer.UnfilledDeposits.ts b/test/Relayer.UnfilledDeposits.ts index fa478481d..ae7958382 100644 --- a/test/Relayer.UnfilledDeposits.ts +++ b/test/Relayer.UnfilledDeposits.ts @@ -1,4 +1,12 @@ -import { AcrossApiClient, HubPoolClient, MultiCallerClient, SpokePoolClient, TokenClient } from "../src/clients"; +import { + AcrossApiClient, + ConfigStoreClient, + HubPoolClient, + MultiCallerClient, + SpokePoolClient, + TokenClient, + UBAClient, +} from "../src/clients"; import { CHAIN_ID_TEST_LIST, amountToLp, @@ -47,7 +55,7 @@ let spokePool1DeploymentBlock: number, spokePool2DeploymentBlock: number; let relayerInstance: Relayer; let unfilledDeposits: RelayerUnfilledDeposit[] = []; -let _getUnfilledDeposits: Promise; +let _getUnfilledDeposits: () => Promise; describe("Relayer: Unfilled Deposits", async function () { const sortableEventFields = [ @@ -115,12 +123,13 @@ describe("Relayer: Unfilled Deposits", async function () { { spokePoolClients, hubPoolClient, - configStoreClient, + configStoreClient: configStoreClient as unknown as ConfigStoreClient, profitClient, tokenClient, multiCallerClient, inventoryClient: new MockInventoryClient(), acrossApiClient: new AcrossApiClient(spyLogger, hubPoolClient, spokePoolClients), + ubaClient: {} as unknown as UBAClient, // We don't need this for this test. }, { relayerTokens: [], @@ -339,8 +348,8 @@ describe("Relayer: Unfilled Deposits", async function () { for (const deposit of [deposit1, deposit2]) { const speedUpSignature = await modifyRelayHelper( newRelayFeePct, - deposit.depositId, - deposit.originChainId!.toString(), + deposit.depositId.toString(), + deposit.originChainId.toString(), depositor, deposit.recipient, "0x" @@ -400,8 +409,8 @@ describe("Relayer: Unfilled Deposits", async function () { const newRelayerFeePct = toBNWei(0.1337); const speedUpSignature = await modifyRelayHelper( newRelayerFeePct, - deposit1.depositId, - deposit1.originChainId!.toString(), + deposit1.depositId.toString(), + deposit1.originChainId.toString(), depositor, deposit1.recipient, "0x" diff --git a/test/SpokePool.Utilities.ts b/test/SpokePool.Utilities.ts index 8648c9485..cecedf6c2 100644 --- a/test/SpokePool.Utilities.ts +++ b/test/SpokePool.Utilities.ts @@ -97,6 +97,7 @@ describe("SpokePoolClient: Event Filtering", async function () { {} as EventSearchConfig, DEFAULT_CONFIG_STORE_VERSION, chainIds, + undefined, mockUpdate ); await configStoreClient.update(); diff --git a/test/SpokePoolClient.SpeedUp.ts b/test/SpokePoolClient.SpeedUp.ts index ee1b1e728..c808aa483 100644 --- a/test/SpokePoolClient.SpeedUp.ts +++ b/test/SpokePoolClient.SpeedUp.ts @@ -53,8 +53,8 @@ describe("SpokePoolClient: SpeedUp", async function () { const newRelayFeePct = toBNWei(0.1337); const speedUpSignature = await modifyRelayHelper( newRelayFeePct, - deposit.depositId, - deposit.originChainId!.toString(), + deposit.depositId.toString(), + deposit.originChainId.toString(), depositor, deposit.recipient, "0x" @@ -111,8 +111,8 @@ describe("SpokePoolClient: SpeedUp", async function () { const newRelayFeePct = toBNWei(0.1337); const speedUpSignature = await modifyRelayHelper( newRelayFeePct, - deposit.depositId, - deposit.originChainId!.toString(), + deposit.depositId.toString(), + deposit.originChainId.toString(), depositor, deposit.recipient, "0x" @@ -164,8 +164,8 @@ describe("SpokePoolClient: SpeedUp", async function () { const newLowerRelayFeePct = depositRelayerFeePct.sub(toBNWei(0.01)); const speedUpSignature = await modifyRelayHelper( newLowerRelayFeePct, - deposit.depositId, - deposit.originChainId!.toString(), + deposit.depositId.toString(), + deposit.originChainId.toString(), depositor, deposit.recipient, "0x" @@ -198,8 +198,8 @@ describe("SpokePoolClient: SpeedUp", async function () { const speedupFast = toBNWei(0.1337); const speedUpFastSignature = await modifyRelayHelper( speedupFast, - deposit.depositId, - deposit.originChainId!.toString(), + deposit.depositId.toString(), + deposit.originChainId.toString(), depositor, deposit.recipient, "0x" @@ -215,8 +215,8 @@ describe("SpokePoolClient: SpeedUp", async function () { const speedupFaster = toBNWei(0.1338); const speedUpFasterSignature = await modifyRelayHelper( speedupFaster, - deposit.depositId, - deposit.originChainId!.toString(), + deposit.depositId.toString(), + deposit.originChainId.toString(), depositor, deposit.recipient, "0x" @@ -265,8 +265,8 @@ describe("SpokePoolClient: SpeedUp", async function () { const newRelayFeePct = toBNWei(0.1337); const speedUpSignature = await modifyRelayHelper( newRelayFeePct, - deposit.depositId, - deposit.originChainId!.toString(), + deposit.depositId.toString(), + deposit.originChainId.toString(), depositor, deposit.recipient, "0x" diff --git a/test/SpokePoolClient.ValidateFill.ts b/test/SpokePoolClient.ValidateFill.ts index 4098f9168..a2b1a9ec5 100644 --- a/test/SpokePoolClient.ValidateFill.ts +++ b/test/SpokePoolClient.ValidateFill.ts @@ -24,6 +24,7 @@ import { mineRandomBlocks, winston, lastSpyLogIncludes, + sinon, } from "./utils"; import { ConfigStoreClient, HubPoolClient, SpokePoolClient } from "../src/clients"; @@ -71,7 +72,13 @@ describe("SpokePoolClient: Fill Validation", async function () { ({ spy, spyLogger } = createSpyLogger()); ({ configStore } = await deployConfigStore(owner, [l1Token])); - configStoreClient = new MockConfigStoreClient(spyLogger, configStore, undefined, undefined, CHAIN_ID_TEST_LIST); + configStoreClient = new MockConfigStoreClient( + spyLogger, + configStore, + undefined, + undefined, + CHAIN_ID_TEST_LIST + ) as unknown as ConfigStoreClient; await configStoreClient.update(); hubPoolClient = new HubPoolClient(spyLogger, hubPool, configStoreClient); @@ -92,8 +99,8 @@ describe("SpokePoolClient: Fill Validation", async function () { spokePool2DeploymentBlock ); - await setupTokensForWallet(spokePool_1, depositor, [erc20_1], null, 10); - await setupTokensForWallet(spokePool_2, relayer, [erc20_2], null, 10); + await setupTokensForWallet(spokePool_1, depositor, [erc20_1], undefined, 10); + await setupTokensForWallet(spokePool_2, relayer, [erc20_2], undefined, 10); // Set the spokePool's time to the provider time. This is done to enable the block utility time finder identify a // "reasonable" block number based off the block time when looking at quote timestamps. We only need to do @@ -248,7 +255,7 @@ describe("SpokePoolClient: Fill Validation", async function () { expect(searchRange2.low).to.be.lessThanOrEqual(deposit1Block); // Searching for deposit ID 3 that doesn't exist yet should throw. - assertPromiseError( + void assertPromiseError( spokePoolClient1._getBlockRangeForDepositId(3, spokePool1DeploymentBlock, spokePoolClient1.latestBlockNumber, 10), "Failed to find deposit ID" ); diff --git a/test/TokenClient.Approval.ts b/test/TokenClient.Approval.ts index a4a70140f..2c09a7619 100644 --- a/test/TokenClient.Approval.ts +++ b/test/TokenClient.Approval.ts @@ -13,6 +13,7 @@ import { getContractFactory, lastSpyLogIncludes, originChainId, + sinon, toBNWei, utf8ToHex, winston, @@ -70,7 +71,7 @@ describe("TokenClient: Origin token approval", async function () { const spokePoolClients = { [originChainId]: spokePoolClient_1, [destinationChainId]: spokePoolClient_2 }; - const hubPoolClient = new HubPoolClient(createSpyLogger().spyLogger, hubPool); + const hubPoolClient = new HubPoolClient(createSpyLogger().spyLogger, hubPool, null); tokenClient = new TokenClient(spyLogger, owner.address, spokePoolClients, hubPoolClient); }); diff --git a/test/TokenClient.BalanceAlowance.ts b/test/TokenClient.BalanceAlowance.ts index 3f419e73e..81dfbed68 100644 --- a/test/TokenClient.BalanceAlowance.ts +++ b/test/TokenClient.BalanceAlowance.ts @@ -58,7 +58,7 @@ describe("TokenClient: Balance and Allowance", async function () { ); const spokePoolClients = { [destinationChainId]: spokePoolClient_1, [originChainId]: spokePoolClient_2 }; - const hubPoolClient = new HubPoolClient(createSpyLogger().spyLogger, hubPool); + const hubPoolClient = new HubPoolClient(createSpyLogger().spyLogger, hubPool, null); tokenClient = new TokenClient(spyLogger, owner.address, spokePoolClients, hubPoolClient); }); diff --git a/test/TokenClient.TokenShortfall.ts b/test/TokenClient.TokenShortfall.ts index aa2a1c5fc..9287f81d0 100644 --- a/test/TokenClient.TokenShortfall.ts +++ b/test/TokenClient.TokenShortfall.ts @@ -1,10 +1,12 @@ import { HubPoolClient, SpokePoolClient, TokenClient } from "../src/clients"; +import { MockConfigStoreClient } from "./mocks"; import { Contract, SignerWithAddress, createSpyLogger, deepEqualsWithBigNumber, deployAndConfigureHubPool, + deployConfigStore, deploySpokePoolWithToken, destinationChainId, ethers, @@ -37,6 +39,9 @@ describe("TokenClient: Token shortfall", async function () { deploymentBlock: spokePool2DeploymentBlock, } = await deploySpokePoolWithToken(destinationChainId, originChainId)); const { hubPool } = await deployAndConfigureHubPool(owner, [], zeroAddress, zeroAddress); + const { configStore } = await deployConfigStore(owner, []); + + const configStoreClient = new MockConfigStoreClient(createSpyLogger().spyLogger, configStore); spokePoolClient_1 = new SpokePoolClient( createSpyLogger().spyLogger, @@ -54,7 +59,7 @@ describe("TokenClient: Token shortfall", async function () { ); const spokePoolClients = { [destinationChainId]: spokePoolClient_1, [originChainId]: spokePoolClient_2 }; - const hubPoolClient = new HubPoolClient(createSpyLogger().spyLogger, hubPool); + const hubPoolClient = new HubPoolClient(createSpyLogger().spyLogger, hubPool, configStoreClient); tokenClient = new TokenClient(spyLogger, owner.address, spokePoolClients, hubPoolClient); }); diff --git a/test/UBAClient.validateFlow.ts b/test/UBAClient.validateFlow.ts index 30940ae31..06fdb2bb7 100644 --- a/test/UBAClient.validateFlow.ts +++ b/test/UBAClient.validateFlow.ts @@ -9,7 +9,7 @@ import { ethers, hubPoolFixture, } from "./utils"; -import { CHAIN_ID_TEST_LIST, expect, randomAddress, toBNWei } from "./constants"; +import { BigNumber, CHAIN_ID_TEST_LIST, expect, randomAddress, toBNWei } from "./constants"; import { SpokePoolClientsByChain, UbaFlow, UbaInflow, UbaOutflow } from "../src/interfaces"; import { MockConfigStoreClient, MockHubPoolClient, MockSpokePoolClient, MockUBAClient } from "./mocks"; import { UBA_MIN_CONFIG_STORE_VERSION } from "../src/common"; @@ -37,7 +37,23 @@ const realizedLpFeePct = toBNWei("0.13"); // Make these test flows partial types so we can leave some of the params undefined until we get to the individual // tests. -let partialInflow: Omit, partialOutflow: Omit; +let partialInflow: Omit; +let partialOutflow: Omit & { + fillAmount: BigNumber; + totalFilledAmount: BigNumber; + depositor: string; + destinationToken: string; + recipient: string; + relayerFeePct: BigNumber; + updatableRelayData: { + recipient: string; + isSlowRelay: boolean; + message: string; + payoutAdjustmentPct: BigNumber; + relayerFeePct: BigNumber; + }; + message: string; +}; describe("UBAClient: Flow validation", function () { beforeEach(async function () { @@ -243,7 +259,10 @@ describe("UBAClient: Flow validation", function () { const expectedLpFee = inflow.amount.mul(baselineFee).div(toBNWei(1)); expect(result?.lpFee).to.equal(expectedLpFee); - deepEqualsWithBigNumber(result?.flow, inflow); + deepEqualsWithBigNumber( + result?.flow as unknown as Record, + inflow as unknown as Record + ); }); }); describe("Outflow", function () { @@ -285,7 +304,10 @@ describe("UBAClient: Flow validation", function () { // LP fee is just the realizedLpFee applied to the amount const expectedLpFee = outflow.amount.mul(outflow.realizedLpFeePct).div(toBNWei(1)); expect(result?.lpFee).to.equal(expectedLpFee); - deepEqualsWithBigNumber(result?.flow, outflow); + deepEqualsWithBigNumber( + result?.flow as unknown as Record, + outflow as unknown as Record + ); // Now, we change the outflow's realizedLpFeePct such that it doesn't match with the deposit, it // should return undefined. @@ -400,11 +422,11 @@ describe("UBAClient: Flow validation", function () { // Don't add inflow to uba bundle state. // Throws errors if inflow is before, after, or in same bundle as outflow. - assertPromiseError(ubaClient.validateFlow(outflow), "Could not find matched deposit in same bundle"); + void assertPromiseError(ubaClient.validateFlow(outflow), "Could not find matched deposit in same bundle"); outflow.matchedDeposit.blockNumber = expectedBlockRanges[originChainId][0].start - 1; - assertPromiseError(ubaClient.validateFlow(outflow), "Could not find bundle block range containing flow"); + void assertPromiseError(ubaClient.validateFlow(outflow), "Could not find bundle block range containing flow"); outflow.matchedDeposit.blockNumber = expectedBlockRanges[originChainId][0].end + 1; - assertPromiseError(ubaClient.validateFlow(outflow), "Could not find bundle block range containing flow"); + void assertPromiseError(ubaClient.validateFlow(outflow), "Could not find bundle block range containing flow"); }); }); }); diff --git a/test/UBAClientUtilities.ts b/test/UBAClientUtilities.ts index eb05548f7..5e63f8e6e 100644 --- a/test/UBAClientUtilities.ts +++ b/test/UBAClientUtilities.ts @@ -383,7 +383,7 @@ describe("UBAClientUtilities", function () { spokePoolClient.addEvent(event); await spokePoolClient.update(); - assertPromiseError( + void assertPromiseError( clients.getUBAFlows(tokenSymbol, chainId, spokePoolClients, hubPoolClient), "Found a UBA deposit with a defined realizedLpFeePct" ); diff --git a/test/fixtures/Dataworker.Fixture.ts b/test/fixtures/Dataworker.Fixture.ts index a420a1625..b3898064c 100644 --- a/test/fixtures/Dataworker.Fixture.ts +++ b/test/fixtures/Dataworker.Fixture.ts @@ -13,6 +13,7 @@ import { SignerWithAddress, setupTokensForWallet, getLastBlockTime, + sinon, } from "../utils"; import * as clients from "../../src/clients"; import { @@ -29,6 +30,7 @@ import { Dataworker } from "../../src/dataworker/Dataworker"; // Tested import { BundleDataClient, TokenClient } from "../../src/clients"; import { DataworkerClients } from "../../src/dataworker/DataworkerClientHelper"; import { MockConfigStoreClient, MockedMultiCallerClient } from "../mocks"; +import { EthersTestLibrary } from "../types"; async function _constructSpokePoolClientsWithLookback( spokePools: Contract[], @@ -55,7 +57,7 @@ async function _constructSpokePoolClientsWithLookback( // Sets up all contracts neccessary to build and execute leaves in dataworker merkle roots: relayer refund, slow relay, // and pool rebalance roots. export async function setupDataworker( - ethers: any, + ethers: EthersTestLibrary, maxRefundPerRelayerRefundLeaf: number, maxL1TokensPerPoolRebalanceLeaf: number, defaultPoolRebalanceTokenTransferThreshold: BigNumber, @@ -78,7 +80,8 @@ export async function setupDataworker( spokePoolClient_3: clients.SpokePoolClient; spokePoolClient_4: clients.SpokePoolClient; spokePoolClients: { [chainId: number]: clients.SpokePoolClient }; - configStoreClient: MockConfigStoreClient; + mockedConfigStoreClient: MockConfigStoreClient; + configStoreClient: clients.ConfigStoreClient; hubPoolClient: clients.HubPoolClient; dataworkerInstance: Dataworker; spyLogger: winston.Logger; @@ -203,7 +206,7 @@ export async function setupDataworker( const bundleDataClient = new BundleDataClient( spyLogger, { - configStoreClient, + configStoreClient: configStoreClient as unknown as clients.ConfigStoreClient, multiCallerClient, hubPoolClient, }, @@ -216,7 +219,7 @@ export async function setupDataworker( tokenClient, hubPoolClient, multiCallerClient, - configStoreClient, + configStoreClient: configStoreClient as unknown as clients.ConfigStoreClient, profitClient, }; const dataworkerInstance = new Dataworker( @@ -264,7 +267,8 @@ export async function setupDataworker( spokePoolClient_3, spokePoolClient_4, spokePoolClients, - configStoreClient, + configStoreClient: configStoreClient as unknown as clients.ConfigStoreClient, + mockedConfigStoreClient: configStoreClient, hubPoolClient, dataworkerInstance, spyLogger, @@ -290,7 +294,7 @@ export async function setupDataworker( // Set up Dataworker with SpokePoolClients with custom lookbacks. All other params are set to defaults. export async function setupFastDataworker( - ethers: any, + ethers: EthersTestLibrary, lookbackForAllChains?: number ): Promise<{ hubPool: Contract; @@ -307,7 +311,8 @@ export async function setupFastDataworker( spokePoolClient_3: clients.SpokePoolClient; spokePoolClient_4: clients.SpokePoolClient; spokePoolClients: { [chainId: number]: clients.SpokePoolClient }; - configStoreClient: MockConfigStoreClient; + mockedConfigStoreClient: MockConfigStoreClient; + configStoreClient: clients.ConfigStoreClient; hubPoolClient: clients.HubPoolClient; dataworkerInstance: Dataworker; spyLogger: winston.Logger; diff --git a/test/mocks/MockAdapterManager.ts b/test/mocks/MockAdapterManager.ts index 65c8111fa..ef24f28e2 100644 --- a/test/mocks/MockAdapterManager.ts +++ b/test/mocks/MockAdapterManager.ts @@ -11,7 +11,12 @@ export class MockAdapterManager extends AdapterManager { } = {}; public mockedOutstandingCrossChainTransfers: { [chainId: number]: OutstandingTransfers } = {}; - async sendTokenCrossChain(address: string, chainId: number, l1Token: string, amount: BigNumber) { + async sendTokenCrossChain( + _address: string, + chainId: number, + l1Token: string, + amount: BigNumber + ): Promise { if (!this.tokensSentCrossChain[chainId]) { this.tokensSentCrossChain[chainId] = {}; } @@ -35,7 +40,7 @@ export class MockAdapterManager extends AdapterManager { return this.mockedOutstandingCrossChainTransfers[chainId]; } - setMockedOutstandingCrossChainTransfers(chainId: number, address: string, l1Token: string, amount: BigNumber) { + setMockedOutstandingCrossChainTransfers(chainId: number, address: string, l1Token: string, amount: BigNumber): void { if (!this.mockedOutstandingCrossChainTransfers[chainId]) { this.mockedOutstandingCrossChainTransfers[chainId] = {}; } diff --git a/test/mocks/MockBundleDataClient.ts b/test/mocks/MockBundleDataClient.ts index 8c2233faf..e4f6d60ac 100644 --- a/test/mocks/MockBundleDataClient.ts +++ b/test/mocks/MockBundleDataClient.ts @@ -5,19 +5,19 @@ export class MockBundleDataClient extends BundleDataClient { private pendingBundleRefunds: FillsToRefund = {}; private nextBundleRefunds: FillsToRefund = {}; - async getPendingRefundsFromValidBundles() { + async getPendingRefundsFromValidBundles(): Promise { return [this.pendingBundleRefunds]; } - async getNextBundleRefunds() { + async getNextBundleRefunds(): Promise { return this.nextBundleRefunds; } - setReturnedPendingBundleRefunds(refunds: FillsToRefund) { + setReturnedPendingBundleRefunds(refunds: FillsToRefund): void { this.pendingBundleRefunds = refunds; } - setReturnedNextBundleRefunds(refunds: FillsToRefund) { + setReturnedNextBundleRefunds(refunds: FillsToRefund): void { this.nextBundleRefunds = refunds; } } diff --git a/test/mocks/MockHubPoolClient.ts b/test/mocks/MockHubPoolClient.ts index 4621ccea5..52673a965 100644 --- a/test/mocks/MockHubPoolClient.ts +++ b/test/mocks/MockHubPoolClient.ts @@ -1,22 +1,20 @@ import { clients } from "@across-protocol/sdk-v2"; import { Contract, winston } from "../utils"; import { ConfigStoreClient } from "../../src/clients"; +import { MockConfigStoreClient } from "./MockConfigStoreClient"; // Adds functions to MockHubPoolClient to facilitate Dataworker unit testing. export class MockHubPoolClient extends clients.mocks.MockHubPoolClient { public latestBundleEndBlocks: { [chainId: number]: number } = {}; - public chainId: number; - constructor( logger: winston.Logger, hubPool: Contract, - configStoreClient: ConfigStoreClient, + configStoreClient: ConfigStoreClient | MockConfigStoreClient, deploymentBlock = 0, chainId = 1 ) { - super(logger, hubPool, configStoreClient, deploymentBlock); - this.chainId = chainId; + super(logger, hubPool, configStoreClient, deploymentBlock, chainId); } setLatestBundleEndBlockForChain(chainId: number, latestBundleEndBlock: number): void { diff --git a/test/mocks/MockInventoryClient.ts b/test/mocks/MockInventoryClient.ts index a18e24b3f..89b2944ab 100644 --- a/test/mocks/MockInventoryClient.ts +++ b/test/mocks/MockInventoryClient.ts @@ -5,7 +5,7 @@ export class MockInventoryClient extends InventoryClient { super(null, null, null, null, null, null, null, null, null); } // eslint-disable-next-line @typescript-eslint/no-unused-vars - async determineRefundChainId(_deposit: Deposit) { + async determineRefundChainId(_deposit: Deposit): Promise { return 1; } } diff --git a/test/mocks/MockTokenClient.ts b/test/mocks/MockTokenClient.ts index c9196edf3..fd90720c9 100644 --- a/test/mocks/MockTokenClient.ts +++ b/test/mocks/MockTokenClient.ts @@ -8,20 +8,20 @@ export class MockTokenClient extends TokenClient { [chainId: number]: { [token: string]: { deposits: number[]; totalRequirement: BigNumber } }; } = {}; - setTokenData(chainId: number, token: string, balance: BigNumber, allowance: BigNumber = toBN(0)) { + setTokenData(chainId: number, token: string, balance: BigNumber, allowance: BigNumber = toBN(0)): void { if (!this.tokenData[chainId]) { this.tokenData[chainId] = {}; } this.tokenData[chainId][token] = { balance, allowance }; } - setTokenShortFallData(chainId: number, token: string, deposits: number[], totalRequirement: BigNumber) { + setTokenShortFallData(chainId: number, token: string, deposits: number[], totalRequirement: BigNumber): void { if (!this.tokenShortfall[chainId]) { this.tokenShortfall[chainId] = {}; } this.tokenShortfall[chainId][token] = { deposits, totalRequirement }; } - getBalance(chainId: number, token: string) { + getBalance(chainId: number, token: string): BigNumber { if (!this.tokenData[chainId]) { return toBN(0); } @@ -31,7 +31,7 @@ export class MockTokenClient extends TokenClient { return this.tokenData[chainId][token].balance; } - getTokensNeededToCoverShortfall(chainId: number, token: string) { + getTokensNeededToCoverShortfall(chainId: number, token: string): BigNumber { if (!this.tokenShortfall[chainId]) { return toBN(0); } @@ -41,7 +41,7 @@ export class MockTokenClient extends TokenClient { return this.tokenShortfall[chainId][token].totalRequirement; } - decrementLocalBalance(chainId: number, token: string, amount: BigNumber) { + decrementLocalBalance(chainId: number, token: string, amount: BigNumber): void { if (!this.tokenData[chainId]) { this.tokenData[chainId] = {}; } diff --git a/test/types/index.ts b/test/types/index.ts new file mode 100644 index 000000000..a8b6e47a1 --- /dev/null +++ b/test/types/index.ts @@ -0,0 +1,38 @@ +import { HardhatEthersHelpers } from "@nomiclabs/hardhat-ethers/types"; +import type { ethers } from "ethers"; +import winston from "winston"; +import * as utils from "@across-protocol/contracts-v2/dist/test-utils"; +import { sinon } from "../utils"; + +export type EthersTestLibrary = typeof ethers & HardhatEthersHelpers; +export type SpyLoggerResult = { + spy: sinon.SinonSpy; + spyLogger: winston.Logger; +}; + +export type SpokePoolDeploymentResult = { + weth: utils.Contract; + erc20: utils.Contract; + spokePool: utils.Contract; + unwhitelistedErc20: utils.Contract; + destErc20: utils.Contract; + deploymentBlock: number; +}; + +export type ContractsV2SlowFillRelayData = { + depositor: string; + recipient: string; + destinationToken: string; + amount: utils.BigNumber; + realizedLpFeePct: utils.BigNumber; + relayerFeePct: utils.BigNumber; + depositId: string; + originChainId: string; + destinationChainId: string; + message: string; +}; + +export type ContractsV2SlowFill = { + relayData: ContractsV2SlowFillRelayData; + payoutAdjustmentPct: utils.BigNumber; +}; diff --git a/test/utils/utils.ts b/test/utils/utils.ts index d6ebe107a..dd0db7cd3 100644 --- a/test/utils/utils.ts +++ b/test/utils/utils.ts @@ -1,9 +1,10 @@ import * as utils from "@across-protocol/contracts-v2/dist/test-utils"; + import { TokenRolesEnum } from "@uma/common"; import { SpyTransport, bigNumberFormatter } from "@uma/financial-templates-lib"; import { constants as ethersConstants, providers } from "ethers"; -import { ConfigStoreClient, GLOBAL_CONFIG_STORE_KEYS, HubPoolClient, SpokePoolClient } from "../../src/clients"; -import { Deposit, Fill, FillWithBlock, RunningBalances } from "../../src/interfaces"; +import { ConfigStoreClient, GLOBAL_CONFIG_STORE_KEYS, HubPoolClient } from "../../src/clients"; +import { Deposit, Fill, FillWithBlock, RelayerRefundLeaf, RunningBalances } from "../../src/interfaces"; import { TransactionResponse, buildRelayerRefundTree, toBN, toBNWei, utf8ToHex } from "../../src/utils"; import { DEFAULT_BLOCK_RANGE_FOR_CHAIN, @@ -20,19 +21,24 @@ import { BigNumber, Contract, SignerWithAddress, deposit } from "./index"; export { MAX_SAFE_ALLOWANCE, MAX_UINT_VAL } from "@uma/common"; export { sinon, winston }; -import { AcrossConfigStore } from "@across-protocol/contracts-v2"; +import { AcrossConfigStore, MerkleTree } from "@across-protocol/contracts-v2"; import { constants } from "@across-protocol/sdk-v2"; -import chai from "chai"; +import chai, { expect } from "chai"; import chaiExclude from "chai-exclude"; import _ from "lodash"; import sinon from "sinon"; import winston from "winston"; +import { ContractsV2SlowFill, SpokePoolDeploymentResult, SpyLoggerResult } from "../types"; + chai.use(chaiExclude); const assert = chai.assert; export { assert, chai }; -export function deepEqualsWithBigNumber(x: any, y: any, omitKeys: string[] = []): boolean { +export function deepEqualsWithBigNumber(x: unknown, y: unknown, omitKeys: string[] = []): boolean { + if (x === undefined || y === undefined) { + return false; + } const sortedKeysX = Object.fromEntries( Object.keys(x) .sort() @@ -52,7 +58,8 @@ export async function assertPromiseError(promise: Promise, errMessage?: st try { await promise; throw new Error(SPECIAL_ERROR_MESSAGE); - } catch (err) { + } catch (e: unknown) { + const err: Error = e as Error; if (err.message.includes(SPECIAL_ERROR_MESSAGE)) { throw err; } @@ -67,7 +74,7 @@ export async function setupTokensForWallet( tokens: utils.Contract[], weth?: utils.Contract, seedMultiplier = 1 -) { +): Promise { await utils.seedWallet(wallet, tokens, weth, utils.amountToSeedWallets.mul(seedMultiplier)); await Promise.all( tokens.map((token) => @@ -79,7 +86,7 @@ export async function setupTokensForWallet( } } -export function createSpyLogger() { +export function createSpyLogger(): SpyLoggerResult { const spy = sinon.spy(); const spyLogger = winston.createLogger({ level: "debug", @@ -93,8 +100,12 @@ export function createSpyLogger() { return { spy, spyLogger }; } -export async function deploySpokePoolWithToken(fromChainId = 0, toChainId = 0, enableRoute = true) { - const { timer, weth, erc20, spokePool, unwhitelistedErc20, destErc20 } = await utils.deploySpokePool(utils.ethers); +export async function deploySpokePoolWithToken( + fromChainId = 0, + toChainId = 0, + enableRoute = true +): Promise { + const { weth, erc20, spokePool, unwhitelistedErc20, destErc20 } = await utils.deploySpokePool(utils.ethers); const receipt = await spokePool.deployTransaction.wait(); await spokePool.setChainId(fromChainId == 0 ? utils.originChainId : fromChainId); @@ -105,7 +116,7 @@ export async function deploySpokePoolWithToken(fromChainId = 0, toChainId = 0, e { originToken: weth.address, destinationChainId: toChainId == 0 ? utils.destinationChainId : toChainId }, ]); } - return { timer, weth, erc20, spokePool, unwhitelistedErc20, destErc20, deploymentBlock: receipt.blockNumber }; + return { weth, erc20, spokePool, unwhitelistedErc20, destErc20, deploymentBlock: receipt.blockNumber }; } export async function deployConfigStore( @@ -154,7 +165,13 @@ export async function deployAndConfigureHubPool( spokePools: { l2ChainId: number; spokePool: utils.Contract }[], finderAddress: string = zeroAddress, timerAddress: string = zeroAddress -) { +): Promise<{ + hubPool: utils.Contract; + mockAdapter: utils.Contract; + l1Token_1: utils.Contract; + l1Token_2: utils.Contract; + hubPoolDeploymentBlock: number; +}> { const lpTokenFactory = await (await utils.getContractFactory("LpTokenFactory", signer)).deploy(); const hubPool = await ( await utils.getContractFactory("HubPool", signer) @@ -175,12 +192,6 @@ export async function deployAndConfigureHubPool( return { hubPool, mockAdapter, l1Token_1, l1Token_2, hubPoolDeploymentBlock: receipt.blockNumber }; } -export async function deployNewToken(owner) { - const l2Token = await (await utils.getContractFactory("ExpandedERC20", owner)).deploy("L2 Token", "L2", 18); - await l2Token.addMember(TokenRolesEnum.MINTER, l2Token.address); - return l2Token; -} - export async function deployNewTokenMapping( l2TokenHolder: utils.SignerWithAddress, l1TokenHolder: utils.SignerWithAddress, @@ -189,7 +200,10 @@ export async function deployNewTokenMapping( configStore: utils.Contract, hubPool: utils.Contract, amountToSeedLpPool: BigNumber -) { +): Promise<{ + l2Token: utils.Contract; + l1Token: utils.Contract; +}> { // Deploy L2 token and enable it for deposits: const spokePoolChainId = await spokePool.chainId(); const l2Token = await (await utils.getContractFactory("ExpandedERC20", l2TokenHolder)).deploy("L2 Token", "L2", 18); @@ -239,7 +253,7 @@ export async function deployNewTokenMapping( export async function enableRoutesOnHubPool( hubPool: utils.Contract, rebalanceRouteTokens: { destinationChainId: number; l1Token: utils.Contract; destinationToken: utils.Contract }[] -) { +): Promise { for (const tkn of rebalanceRouteTokens) { await hubPool.setPoolRebalanceRoute(tkn.destinationChainId, tkn.l1Token.address, tkn.destinationToken.address); await hubPool.enableL1TokenForLiquidityProvision(tkn.l1Token.address); @@ -254,7 +268,7 @@ export async function simpleDeposit( destinationChainId: number = utils.destinationChainId, amountToDeposit: utils.BigNumber = utils.amountToDeposit, depositRelayerFeePct: utils.BigNumber = utils.depositRelayerFeePct -) { +): Promise { const depositObject = await utils.deposit( spokePool, token, @@ -272,138 +286,43 @@ export async function simpleDeposit( }; } -export async function deploySpokePoolForIterativeTest( - logger, - signer: utils.SignerWithAddress, - mockAdapter: utils.Contract, - configStoreClient: ConfigStoreClient, - desiredChainId = 0 -) { - const { spokePool, deploymentBlock } = await deploySpokePoolWithToken(desiredChainId, 0, false); - - await configStoreClient.hubPoolClient.hubPool.setCrossChainContracts( - utils.destinationChainId, - mockAdapter.address, - spokePool.address - ); - const spokePoolClient = new SpokePoolClient( - logger, - spokePool.connect(signer), - configStoreClient, - desiredChainId, - deploymentBlock - ); - - return { spokePool, spokePoolClient }; -} - -// Deploy, enable route and set pool rebalance route a new token over all spoke pools. -export async function deploySingleTokenAcrossSetOfChains( - signer: utils.SignerWithAddress, - spokePools: { contract: utils.Contract; chainId: number }[], - - hubPool: utils.Contract, - associatedL1Token: utils.Contract -) { - // Deploy one token per spoke pool. This of this as deploying USDC on all chains. - const tokens = []; - for (let i = 0; i < spokePools.length; i++) { - const erc20 = await (await utils.getContractFactory("ExpandedERC20", signer)).deploy("Yeet Coin", "Coin", 18); - await erc20.addMember(TokenRolesEnum.MINTER, signer.address); - tokens.push(erc20); - } - - // For each spokePool, whitelist the associated token above as an 'origin' token to all other destination chains. Also, - // enable the pool rebalance route for the l1Token and newly deployed token. - for (const [index, spokePool] of spokePools.entries()) { - for (const otherSpokePool of spokePools) { - if (spokePool === otherSpokePool) { - continue; - } - await utils.enableRoutes(spokePool.contract, [ - { originToken: tokens[index].address, destinationChainId: await otherSpokePool.chainId }, - ]); - } - - await hubPool.setPoolRebalanceRoute(spokePool.chainId, associatedL1Token.address, tokens[index].address); - } - return tokens; -} - -export function appendPropsToDeposit(deposit) { - return { ...deposit, realizedLpFeePct: utils.toBN(0), destinationToken: zeroAddress }; +/** + * Takes as input a body and returns a new object with the body and a message property. Used to appease the typescript + * compiler when we want to return a type that doesn't have a message property. + * @param body Typically a partial structure of a Deposit or Fill. + * @returns A new object with the body and a message property. + */ +export function appendMessageToResult(body: T): T & { message: string } { + return { ...body, message: "" }; } -export async function getLastBlockTime(provider: any) { +export async function getLastBlockTime(provider: providers.Provider): Promise { return (await provider.getBlock(await provider.getBlockNumber())).timestamp; } -export async function deployIterativeSpokePoolsAndToken( - logger: winston.Logger, - signer: utils.SignerWithAddress, - mockAdapter: utils.Contract, - configStoreClient: ConfigStoreClient, - numSpokePools: number, - numTokens: number -) { - const spokePools = []; - const l1TokenToL2Tokens = {}; - - // For each count of numSpokePools deploy a new spoke pool. Set the chainId to the index in the array. Note that we - // start at index of 1 here. spokePool with a chainId of 0 is not a good idea. - for (let i = 1; i < numSpokePools + 1; i++) { - spokePools.push(await deploySpokePoolForIterativeTest(logger, signer, mockAdapter, configStoreClient, i)); - } - - // For each count of numTokens deploy a new token. This will also set it as an enabled route over all chains. - for (let i = 1; i < numTokens + 1; i++) { - // Create an in incrementing address from 0x0000000000000000000000000000000000000001. - const associatedL1Token = await ( - await utils.getContractFactory("ExpandedERC20", signer) - ).deploy("Yeet Coin L1", "Coin", 18); - await associatedL1Token.addMember(TokenRolesEnum.MINTER, signer.address); - if (!l1TokenToL2Tokens[associatedL1Token.address]) { - l1TokenToL2Tokens[associatedL1Token.address] = []; - } - l1TokenToL2Tokens[associatedL1Token.address] = await deploySingleTokenAcrossSetOfChains( - signer, - spokePools.map((spokePool, index) => ({ contract: spokePool.spokePool, chainId: index + 1 })), - configStoreClient.hubPoolClient.hubPool, - associatedL1Token - ); - } - return { spokePools, l1TokenToL2Tokens }; -} - export async function addLiquidity( signer: utils.SignerWithAddress, hubPool: utils.Contract, l1Token: utils.Contract, amount: utils.BigNumber -) { +): Promise { await utils.seedWallet(signer, [l1Token], null, amount); await l1Token.connect(signer).approve(hubPool.address, amount); await hubPool.enableL1TokenForLiquidityProvision(l1Token.address); await hubPool.connect(signer).addLiquidity(l1Token.address, amount); } -export async function contractAt(contractName: string, signer: utils.Signer, address: string) { - try { - const artifactInterface = (await utils.getContractFactory(contractName, signer)).interface; - return new utils.Contract(address, artifactInterface, signer); - } catch (error) { - throw new Error(`Could not find the artifact for ${contractName}! \n${error}`); - } -} - // Submits a deposit transaction and returns the Deposit struct that that clients interact with. export async function buildDepositStruct( deposit: Omit, hubPoolClient: HubPoolClient, l1TokenForDepositedToken: Contract -) { +): Promise { const { quoteBlock, realizedLpFeePct } = await hubPoolClient.computeRealizedLpFeePct( - deposit, + { + ...deposit, + blockNumber: (await hubPoolClient.blockFinder.getBlockForTimestamp(deposit.quoteTimestamp)).number, + }, l1TokenForDepositedToken.address ); return { @@ -434,7 +353,9 @@ export async function buildDeposit( _amountToDeposit, _relayerFeePct ); - return await buildDepositStruct(_deposit!, hubPoolClient, l1TokenForDepositedToken); + // Sanity Check: Ensure that the deposit was successful. + expect(_deposit).to.not.be.null; + return await buildDepositStruct(appendMessageToResult(_deposit), hubPoolClient, l1TokenForDepositedToken); } // Submits a fillRelay transaction and returns the Fill struct that that clients will interact with. @@ -597,7 +518,7 @@ export async function buildFillForRepaymentChain( }; await spokePool.connect(relayer).fillRelay( ...utils.getFillRelayParams( - relayDataFromDeposit, + appendMessageToResult(relayDataFromDeposit), depositToFill.amount .mul(toBNWei(1).sub(depositToFill.realizedLpFeePct.add(depositToFill.relayerFeePct))) .mul(toBNWei(pctOfDepositToFill)) @@ -674,7 +595,10 @@ export async function buildRefundRequest( // Returns expected leaves ordered by origin chain ID and then deposit ID(ascending). Ordering is implemented // same way that dataworker orders them. -export function buildSlowRelayLeaves(deposits: Deposit[], payoutAdjustmentPcts: BigNumber[] = []) { +export function buildSlowRelayLeaves( + deposits: Deposit[], + payoutAdjustmentPcts: BigNumber[] = [] +): ContractsV2SlowFill[] { return deposits .map((_deposit, i) => { return { @@ -690,10 +614,10 @@ export function buildSlowRelayLeaves(deposits: Deposit[], payoutAdjustmentPcts: depositId: _deposit.depositId.toString(), message: _deposit.message, }, - payoutAdjustmentPct: payoutAdjustmentPcts[i]?.toString() ?? "0", + payoutAdjustmentPct: BigNumber.from(payoutAdjustmentPcts[i]?.toString() ?? "0"), }; }) // leaves should be ordered by origin chain ID and then deposit ID (ascending). - .sort((relayA, relayB) => { + .sort(({ relayData: relayA }, { relayData: relayB }) => { if (relayA.originChainId !== relayB.originChainId) { return Number(relayA.originChainId) - Number(relayB.originChainId); } else { @@ -711,15 +635,22 @@ export async function buildRelayerRefundTreeWithUnassignedLeafIds( refundAddresses: string[]; refundAmounts: BigNumber[]; }[] -) { - return await buildRelayerRefundTree( +): Promise> { + return buildRelayerRefundTree( leaves.map((leaf, id) => { return { ...leaf, leafId: id }; }) ); } -export async function constructPoolRebalanceTree(runningBalances: RunningBalances, realizedLpFees: RunningBalances) { +export async function constructPoolRebalanceTree( + runningBalances: RunningBalances, + realizedLpFees: RunningBalances +): Promise<{ + leaves: utils.PoolRebalanceLeaf[]; + tree: MerkleTree; + startingRunningBalances: utils.BigNumber; +}> { const leaves = utils.buildPoolRebalanceLeaves( Object.keys(runningBalances).map((x) => Number(x)), // Where funds are getting sent. Object.values(runningBalances).map((runningBalanceForL1Token) => Object.keys(runningBalanceForL1Token)), // l1Tokens. @@ -775,11 +706,23 @@ export async function buildSlowFill( // We use the offset input to bypass the bundleClient's cache key, which is the bundle block range. So, to make sure // that the client requeries fresh blockchain state, we need to slightly offset the block range to produce a different // cache key. -export function getDefaultBlockRange(toBlockOffset: number) { +export function getDefaultBlockRange(toBlockOffset: number): number[][] { return DEFAULT_BLOCK_RANGE_FOR_CHAIN.map((range) => [range[0], range[1] + toBlockOffset]); } -export function createRefunds(address: string, refundAmount: BigNumber, token: string) { +export function createRefunds( + address: string, + refundAmount: BigNumber, + token: string +): Record< + string, + { + refunds: Record; + fills: Fill[]; + totalRefundAmount: BigNumber; + realizedLpFees: BigNumber; + } +> { return { [token]: { refunds: { [address]: refundAmount }, @@ -790,6 +733,14 @@ export function createRefunds(address: string, refundAmount: BigNumber, token: s }; } -export function getLastBlockNumber() { +/** + * Grabs the latest block number from the hardhat provider. + * @returns The latest block number. + */ +export function getLastBlockNumber(): Promise { return (utils.ethers.provider as providers.Provider).getBlockNumber(); } + +export function convertMockedConfigClient(client: unknown): client is ConfigStoreClient { + return true; +} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 000000000..173e5ecf8 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "include": [".eslintrc.js", "index.ts", "./src", "./scripts", "./test", "./deploy", "./tasks"], + "exclude": ["artifacts", "cache", "dist", "node_modules"], + "compilerOptions": { + "target": "es6", + "module": "commonjs" + } +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 000000000..a5daea814 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "include": ["./test", "./src"] +} diff --git a/yarn.lock b/yarn.lock index 355abccc4..b798b53ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -37,10 +37,10 @@ "@openzeppelin/contracts" "4.1.0" "@uma/core" "^2.18.0" -"@across-protocol/sdk-v2@0.15.20": - version "0.15.20" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk-v2/-/sdk-v2-0.15.20.tgz#35e3faf01069bb6e82522efecef8c97ad8863462" - integrity sha512-LjnL9lwEoCA80XpOh6JwBPYys1aI7HP7XhDe3vkzBU7u86P7MY2oMDc4p2AErr7w+iR0mkUD727eYCSl10zfRg== +"@across-protocol/sdk-v2@0.15.21": + version "0.15.21" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk-v2/-/sdk-v2-0.15.21.tgz#da6de385c36c947a724ea2e02fed455ba21c7605" + integrity sha512-+3cudHaDaPryjXIO/fRZ9J+VUE9pzS57ySMbxso0uxUYgNHX/rncaxza6KA/MPxr9yVraPbxgQGdxT38ZAmdcA== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/contracts-v2" "^2.4.3" @@ -3034,6 +3034,18 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-3.0.1.tgz#1254750a4fec4aff2ebec088ccd0bb02e91fedb4" integrity sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw== +"@types/sinon@^10.0.16": + version "10.0.16" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.16.tgz#4bf10313bd9aa8eef1e50ec9f4decd3dd455b4d3" + integrity sha512-j2Du5SYpXZjJVJtXBokASpPRj+e2z+VUhCPHmM6WMfe3dpHu6iVKJMU6AiBcMp/XTAYnEj6Wc1trJUWwZ0QaAQ== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e" + integrity sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA== + "@typescript-eslint/eslint-plugin@^4.29.1": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276"