Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add updated UMIP invalidity cases #831

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/clients/BundleDataClient/BundleDataClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from "lodash";
import { utils } from "ethers";
import {
ProposedRootBundle,
SlowFillRequestWithBlock,
Expand Down Expand Up @@ -32,6 +33,7 @@ import {
mapAsync,
bnUint32Max,
isZeroValueDeposit,
chainIsEvm,
} from "../../utils";
import winston from "winston";
import {
Expand All @@ -55,6 +57,10 @@ type DataCache = Record<string, Promise<LoadDataReturnValue>>;

// V3 dictionary helper functions
function updateExpiredDepositsV3(dict: ExpiredDepositsToRefundV3, deposit: V3DepositWithBlock): void {
// A deposit refund for a deposit is invalid if the depositor has a bytes32 address input for an EVM chain. It is valid otherwise.
if (chainIsEvm(deposit.originChainId) && !utils.isAddress(deposit.depositor)) {
return;
}
const { originChainId, inputToken } = deposit;
if (!dict?.[originChainId]?.[inputToken]) {
assign(dict, [originChainId, inputToken], []);
Expand All @@ -77,6 +83,10 @@ function updateBundleFillsV3(
repaymentChainId: number,
repaymentToken: string
): void {
// It is impossible to refund a deposit if the repayment chain is EVM and the relayer is a non-evm address.
if (chainIsEvm(fill.repaymentChainId) && !utils.isAddress(fill.relayer)) {
return;
}
if (!dict?.[repaymentChainId]?.[repaymentToken]) {
assign(dict, [repaymentChainId, repaymentToken], {
fills: [],
Expand Down Expand Up @@ -807,6 +817,8 @@ export class BundleDataClient {

// If deposit block is within origin chain bundle block range, then save as bundle deposit.
// If deposit is in bundle and it has expired, additionally save it as an expired deposit.
// Note: if the `depositor` field in the expired deposit is an invalid address, e.g. a bytes32 address on an EVM
// chain, then the deposit refund is invalid and ignored.
// If deposit is not in the bundle block range, then save it as an older deposit that
// may have expired.
if (deposit.blockNumber >= originChainBlockRange[0] && deposit.blockNumber <= originChainBlockRange[1]) {
Expand Down Expand Up @@ -914,9 +926,20 @@ export class BundleDataClient {
bundleInvalidFillsV3.push(fill);
return;
}
// If the fill's repayment address is not a valid EVM address and the repayment chain is an EVM chain, the fill is invalid.
if (chainIsEvm(fill.repaymentChainId) && !utils.isAddress(fill.relayer)) {
const fillTransaction = await originClient.spokePool.provider.getTransaction(fill.transactionHash);
const originRelayer = fillTransaction.from;
// Repayment chain is still an EVM chain, but the msg.sender is a bytes32 address, so the fill is invalid.
if (!utils.isAddress(originRelayer)) {
bundleInvalidFillsV3.push(fill);
return;
}
// Otherwise, assume the relayer to be repaid is the msg.sender.
fill.relayer = originRelayer;
}
// If deposit is using the deterministic relay hash feature, then the following binary search-based
// algorithm will not work. However, it is impossible to emit an infinite fill deadline using
// the unsafeDepositV3 function so there is no need to catch the special case.
// algorithm will not work.
const historicalDeposit = await queryHistoricalDepositForFill(originClient, fill);
if (!historicalDeposit.found) {
bundleInvalidFillsV3.push(fill);
Expand Down
9 changes: 9 additions & 0 deletions src/utils/DepositUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CachingMechanismInterface, Deposit, DepositWithBlock, Fill, SlowFillReq
import { getNetworkName } from "./NetworkUtils";
import { getDepositInCache, getDepositKey, setDepositInCache } from "./CachingUtils";
import { validateFillForDeposit } from "./FlowUtils";
import { isUnsafeDepositId } from "./SpokeUtils";
import { getCurrentTime } from "./TimeUtils";
import { isDefined } from "./TypeGuards";
import { isDepositFormedCorrectly } from "./ValidatorUtils";
Expand All @@ -17,6 +18,7 @@ export enum InvalidFill {
DepositIdInvalid = 0, // Deposit ID seems invalid for origin SpokePool
DepositIdNotFound, // Deposit ID not found (bad RPC data?)
FillMismatch, // Fill does not match deposit parameters for deposit ID.
DepositIdOutOfRange, // Fill is for a deterministic deposit.
}

export type DepositSearchResult =
Expand All @@ -40,6 +42,13 @@ export async function queryHistoricalDepositForFill(
fill: Fill | SlowFillRequest,
cache?: CachingMechanismInterface
): Promise<DepositSearchResult> {
if (isUnsafeDepositId(fill.depositId)) {
bmzig marked this conversation as resolved.
Show resolved Hide resolved
return {
found: false,
code: InvalidFill.DepositIdOutOfRange,
reason: `Cannot find historical deposit for fill with unsafe deposit ID ${fill.depositId}.`,
};
}
if (fill.originChainId !== spokePoolClient.chainId) {
throw new Error(`OriginChainId mismatch (${fill.originChainId} != ${spokePoolClient.chainId})`);
}
Expand Down
43 changes: 43 additions & 0 deletions src/utils/NetworkUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,49 @@ export function chainIsL1(chainId: number): boolean {
return [CHAIN_IDs.MAINNET, CHAIN_IDs.SEPOLIA].includes(chainId);
}

/**
* Determines whether a chain ID runs on an EVM-like execution layer.
* @param chainId Chain ID to evaluate.
* @returns True if chain corresponding to chainId has an EVM-like execution layer.
*/
export function chainIsEvm(chainId: number): boolean {
bmzig marked this conversation as resolved.
Show resolved Hide resolved
const chainFamily = PUBLIC_NETWORKS[chainId].family;
const checkEvmExceptionChains = (_chainId: number) => {
const {
MAINNET,
SEPOLIA,
ARBITRUM,
ARBITRUM_SEPOLIA,
LINEA,
POLYGON,
POLYGON_AMOY,
SCROLL,
SCROLL_SEPOLIA,
ZK_SYNC,
ZK_SYNC_SEPOLIA,
} = CHAIN_IDs;
return [
MAINNET,
SEPOLIA,
ARBITRUM,
ARBITRUM_SEPOLIA,
LINEA,
POLYGON,
POLYGON_AMOY,
SCROLL,
SCROLL_SEPOLIA,
ZK_SYNC,
ZK_SYNC_SEPOLIA,
].includes(_chainId);
};
return (
chainFamily === ChainFamily.OP_STACK ||
chainFamily === ChainFamily.ORBIT ||
chainFamily === ChainFamily.ZK_STACK ||
checkEvmExceptionChains(chainId)
);
}

/**
* Determines whether a chain ID has the capacity for having its USDC bridged via CCTP.
* @param chainId Chain ID to evaluate.
Expand Down
2 changes: 1 addition & 1 deletion src/utils/SpokeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ export async function getBlockRangeForDepositId(
export async function getDepositIdAtBlock(contract: Contract, blockTag: number): Promise<BigNumber> {
const _depositIdAtBlock = await contract.numberOfDeposits({ blockTag });
const depositIdAtBlock = toBN(_depositIdAtBlock);
// Sanity check to ensure that the deposit ID is an integer and is greater than or equal to zero.
// Sanity check to ensure that the deposit ID is greater than or equal to zero.
if (depositIdAtBlock.lt(bnZero)) {
throw new Error("Invalid deposit count");
}
Expand Down
Loading