Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into pxrl/types-prep
Browse files Browse the repository at this point in the history
  • Loading branch information
pxrl committed Jan 25, 2024
2 parents 8a6a19c + a34c18c commit c48dcca
Show file tree
Hide file tree
Showing 16 changed files with 122 additions and 655 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@across-protocol/sdk-v2",
"author": "UMA Team",
"version": "0.20.1",
"version": "0.20.2",
"license": "AGPL-3.0",
"homepage": "https://docs.across.to/v/developer-docs/developers/across-sdk",
"files": [
Expand Down Expand Up @@ -99,7 +99,7 @@
"@across-protocol/across-token": "^1.0.0",
"@across-protocol/constants-v2": "^1.0.8",
"@across-protocol/contracts-v2": "^2.4.7",
"@eth-optimism/sdk": "^2.1.0",
"@eth-optimism/sdk": "^3.1.8",
"@pinata/sdk": "^2.1.0",
"@types/mocha": "^10.0.1",
"@uma/sdk": "^0.34.1",
Expand Down
4 changes: 4 additions & 0 deletions src/clients/HubPoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ export class HubPoolClient extends BaseAbstractClient {
return this.disputedRootBundles;
}

getExecutedRootBundles(): ExecutedRootBundle[] {
return this.executedRootBundles;
}

getSpokePoolForBlock(chain: number, block: number = Number.MAX_SAFE_INTEGER): string {
if (!this.crossChainContracts[chain]) {
throw new Error(`No cross chain contracts set for ${chain}`);
Expand Down
55 changes: 1 addition & 54 deletions src/clients/SpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ import {
FundsDepositedEvent,
FundsDepositedEventStringified,
RealizedLpFee,
RefundRequestWithBlock,
RefundRequestWithBlockStringified,
RelayerRefundExecutionWithBlock,
RelayerRefundExecutionWithBlockStringified,
RootBundleRelayWithBlock,
Expand Down Expand Up @@ -84,7 +82,6 @@ export class SpokePoolClient extends BaseAbstractClient {
public firstDepositIdForSpokePool = Number.MAX_SAFE_INTEGER;
public lastDepositIdForSpokePool = Number.MAX_SAFE_INTEGER;
public fills: { [OriginChainId: number]: FillWithBlock[] } = {};
public refundRequests: RefundRequestWithBlock[] = [];

/**
* Creates a new SpokePoolClient.
Expand Down Expand Up @@ -115,7 +112,6 @@ export class SpokePoolClient extends BaseAbstractClient {
FundsDeposited: this.spokePool.filters.FundsDeposited(),
RequestedSpeedUpDeposit: this.spokePool.filters.RequestedSpeedUpDeposit(),
FilledRelay: this.spokePool.filters.FilledRelay(),
RefundRequested: this.spokePool.filters.RefundRequested(),
EnabledDepositRoute: this.spokePool.filters.EnabledDepositRoute(),
TokensBridged: this.spokePool.filters.TokensBridged(),
RelayedRootBundle: this.spokePool.filters.RelayedRootBundle(),
Expand Down Expand Up @@ -217,18 +213,6 @@ export class SpokePoolClient extends BaseAbstractClient {
return this.getFills().filter((fill) => fill.blockNumber >= startingBlock && fill.blockNumber <= endingBlock);
}

/**
* Retrieves a list of refund requests from the SpokePool contract that are within an optional block range.
* @param fromBlock The starting block number. If not provided, requests will not be filtered by any bounds.
* @param toBlock The ending block number. If not provided, requests will not be filtered by any bounds.
* @returns A list of refund requests.
*/
public getRefundRequests(fromBlock?: number, toBlock?: number): RefundRequestWithBlock[] {
return fromBlock === undefined || toBlock === undefined || isNaN(fromBlock) || isNaN(toBlock)
? this.refundRequests
: this.refundRequests.filter((request) => request.blockNumber >= fromBlock && request.blockNumber <= toBlock);
}

/**
* Retrieves a list of root bundle relays from the SpokePool contract.
* @returns A list of root bundle relays.
Expand Down Expand Up @@ -598,7 +582,7 @@ export class SpokePoolClient extends BaseAbstractClient {
// Collate the relevant set of block numbers and filter for uniqueness, then query each corresponding block.
const blockNumbers = Array.from(
new Set(
["FundsDeposited", "FilledRelay", "RefundRequested"]
["FundsDeposited", "FilledRelay"]
.filter((eventName) => eventsToQuery.includes(eventName))
.map((eventName) => {
const idx = eventsToQuery.indexOf(eventName);
Expand Down Expand Up @@ -767,33 +751,6 @@ export class SpokePoolClient extends BaseAbstractClient {
}
}

// @note: In Across 2.5, callers will simultaneously request [FundsDeposited, FilledRelay, RefundsRequested].
// The list of events is always pre-sorted, so rather than splitting them out individually, it might make sense to
// evaluate them as a single group, to avoid having to re-merge and sequence again afterwards.
if (eventsToQuery.includes("RefundRequested")) {
const refundRequests = queryResults[eventsToQuery.indexOf("RefundRequested")];

if (refundRequests.length > 0) {
this.log("debug", `Found ${refundRequests.length} new relayer refund requests on chain ${this.chainId}`, {
earliestEvent: refundRequests[0].blockNumber,
});
}
for (const event of refundRequests) {
const rawRefundRequest = spreadEventWithBlockNumber(event) as RefundRequestWithBlock;
const refundRequest: RefundRequestWithBlock = {
...rawRefundRequest,
repaymentChainId: this.chainId, // repaymentChainId is not part of the on-chain event, so add it here.
blockTimestamp: 0,
};
// Override the default blockTimestamp of 0 only if the UBA is active and we have pre-queried block times
// for each event.
if (isDefined(blocks)) {
refundRequest.blockTimestamp = blocks[event.blockNumber].timestamp;
}
this.refundRequests.push(refundRequest);
}
}

if (eventsToQuery.includes("EnabledDepositRoute")) {
const enableDepositsEvents = queryResults[eventsToQuery.indexOf("EnabledDepositRoute")];

Expand Down Expand Up @@ -1116,13 +1073,6 @@ export class SpokePoolClient extends BaseAbstractClient {
}),
{}
);
this.refundRequests = (spokePoolClientState.refundRequests || []).map((refundRequest) => ({
...refundRequest,
amount: BigNumber.from(refundRequest.amount),
realizedLpFeePct: BigNumber.from(refundRequest.realizedLpFeePct),
previousIdenticalRequests: BigNumber.from(refundRequest.previousIdenticalRequests),
fillBlock: BigNumber.from(refundRequest.fillBlock),
}));
this.isUpdated = true;
}

Expand Down Expand Up @@ -1162,9 +1112,6 @@ export class SpokePoolClient extends BaseAbstractClient {
firstBlockToSearch: this.firstBlockToSearch,
isUpdated: this.isUpdated,
fills: JSON.parse(stringifyJSONWithNumericString(this.fills)) as Record<string, FillWithBlockStringified[]>,
refundRequests: JSON.parse(
stringifyJSONWithNumericString(this.refundRequests)
) as RefundRequestWithBlockStringified[],
};
}
}
189 changes: 5 additions & 184 deletions src/clients/UBAClient/UBAClientUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import { mapAsync } from "../../utils/ArrayUtils";
import { SpokePoolClients } from "../../utils/TypeUtils";
import { isDefined } from "../../utils/TypeGuards";
import { validateFillForDeposit } from "../../utils/FlowUtils";
import { ModifiedUBAFlow, RequestValidReturnType, SpokePoolFillFilter, UBAClientState } from "./UBAClientTypes";
import { ModifiedUBAFlow, SpokePoolFillFilter, UBAClientState } from "./UBAClientTypes";
import {
DepositWithBlock,
Fill,
FillWithBlock,
RefundRequestWithBlock,
TokenRunningBalance,
UBAParsedConfigType,
UbaFlow,
Expand Down Expand Up @@ -471,11 +470,8 @@ export function getFlowChain(flow: UbaFlow): number {
if (isUbaInflow(flow)) {
flowChain = flow.originChainId;
} else {
if (outflowIsFill(flow)) {
flowChain = flow.destinationChainId;
} else {
flowChain = flow.repaymentChainId;
}
assert(outflowIsFill(flow));
flowChain = flow.destinationChainId;
}
return flowChain;
}
Expand All @@ -484,7 +480,7 @@ export function getFlowChain(flow: UbaFlow): number {
* Retrieves the flows for a given chainId. This is designed only to work with UBA start and end blocks as
* the realizedLpFeePct for deposits is not compared against fills. Retrieving any "Pre UBA" deposits with
* defined realizedLpFeePct will cause this function to throw.
* * @param tokenSymbol Symbol of token to retrieve flows for.
* @param tokenSymbol Symbol of token to retrieve flows for.
* @param chainId The chainId to retrieve flows for
* @param spokePoolClients A mapping of chainIds to spoke pool clients
* @param hubPoolClient A hub pool client instance to query the hub pool
Expand Down Expand Up @@ -548,27 +544,8 @@ export async function getUBAFlows(

// TODO: For each fill, add a matchedFill, or expected realizedLpFeePct value to the matched deposit.

const refundRequests: UbaFlow[] = (
await getValidRefundCandidates(
chainId,
hubPoolClient,
spokePoolClients,
{
fromBlock,
toBlock,
},
["realizedLpFeePct"]
)
).filter((refundRequest: RefundRequestWithBlock) => {
const _tokenSymbol = hubPoolClient.getL1TokenInfoForL2Token(
refundRequest.refundToken,
refundRequest.repaymentChainId
)?.symbol;
return _tokenSymbol === tokenSymbol;
});

// This is probably more expensive than we'd like... @todo: optimise.
const flows = sortFlowsAscending(deposits.concat(fills).concat(refundRequests));
const flows = sortFlowsAscending(deposits.concat(fills));

return flows;
}
Expand Down Expand Up @@ -617,108 +594,6 @@ export function sortFlowsAscending(flows: UbaFlow[]): UbaFlow[] {
return sortFlowsAscendingInPlace([...flows]);
}

/**
* Validate a refund request.
* @param chainId The chainId of the spoke pool
* @param chainIdIndices The chainIds of the spoke pools that align with the spoke pool clients
* @param spokePoolClients A mapping of chainIds to spoke pool clients
* @param hubPoolClient The hub pool client
* @param refundRequest The refund request to validate
* @returns Whether or not the refund request is valid
*/
export async function refundRequestIsValid(
spokePoolClients: SpokePoolClients,
hubPoolClient: HubPoolClient,
refundRequest: RefundRequestWithBlock,
ignoredDepositValidationParams: string[] = []
): Promise<RequestValidReturnType> {
const {
relayer,
amount,
refundToken,
depositId,
originChainId,
destinationChainId,
repaymentChainId,
realizedLpFeePct,
fillBlock,
previousIdenticalRequests,
} = refundRequest;

if (destinationChainId === repaymentChainId) {
return { valid: false, reason: "Invalid destinationChainId" };
}
const destSpoke = spokePoolClients[destinationChainId];

if (fillBlock.lt(destSpoke.deploymentBlock) || fillBlock.gt(destSpoke.latestBlockSearched)) {
const { deploymentBlock, latestBlockSearched } = destSpoke;
return {
valid: false,
reason: `FillBlock (${fillBlock} out of SpokePool range [${deploymentBlock}, ${latestBlockSearched}]`,
};
}

// @dev: In almost all cases we should only count refunds where this value is 0. However, sometimes its possible
// that an initial refund request is thrown out due to some odd timing bug so this might be overly restrictive.
if (previousIdenticalRequests.gt(0)) {
return { valid: false, reason: "Previous identical request exists" };
}

// Validate relayer and depositId. Also check that fill requested refund on same chain that
// refund was sent.
const fill = destSpoke.getFillsForRelayer(relayer).find((fill) => {
// prettier-ignore
return (
fill.depositId === depositId
&& fill.originChainId === originChainId
&& fill.destinationChainId === destinationChainId
// Must have requested refund on chain that refund was sent on.
&& fill.repaymentChainId === repaymentChainId
// Must be a full fill to qualify for a refund.
&& fill.amount.eq(amount)
&& fill.fillAmount.eq(amount)
&& fill.realizedLpFeePct.eq(realizedLpFeePct)
&& fill.blockNumber === fillBlock.toNumber()
);
});
if (!isDefined(fill)) {
if (fillBlock.lt(destSpoke.eventSearchConfig.fromBlock)) {
// TODO: We need to do a look back for a fill if we can't find it. We can't assume it doesn't exist, similar to
// why we try to do a longer lookback below for a deposit. The problem with looking up the fill is that there is no
// deterministic way to eliminate the possibility that a fill exists.
// However, its OK to assume this refund is invalid for now since we assume that refunds are sent very close
// to the time of the fill.
// Can try to use `getModifiedFlow` in combination with some new findFill method in the SpokePoolClient.
throw new Error(
`Unimplemented: refund request fillBlock ${fillBlock} is older than spoke pool client from block, set a wider lookback`
);
} else {
return { valid: false, reason: "Unable to find matching fill" };
}
}

// Now, match the deposit against a fill but don't check the realizedLpFeePct parameter because it will be
// undefined in the spoke pool client until we validate it later.
const deposit = await getMatchedDeposit(spokePoolClients, fill, ignoredDepositValidationParams);
if (!isDefined(deposit)) {
return { valid: false, reason: "Unable to find matching deposit" };
}

// Verify that the refundToken maps to a known HubPool token and is the correct
// token for the chain where the refund was sent from.
// Note: the refundToken must be valid at the time the deposit was sent.
try {
const expectedRefundToken = hubPoolClient.getL2TokenForDeposit(deposit, repaymentChainId);
if (expectedRefundToken !== refundToken) {
return { valid: false, reason: `Refund token does not map to expected refund token ${refundToken}` };
}
} catch {
return { valid: false, reason: `Refund token unknown at HubPool block ${deposit.quoteBlockNumber}` };
}

return { valid: true, matchingFill: fill, matchingDeposit: deposit };
}

/**
* @notice Get the matching flow in a stream of already validated flows. Useful for seeing if an outflow's
* matched inflow `targetFlow` is in the `allValidatedFlows` list.
Expand Down Expand Up @@ -794,60 +669,6 @@ export async function getValidFillCandidates(
return fills;
}

/**
* Search for refund requests recorded by a specific SpokePool.
* @param chainId Chain ID of the relevant SpokePoolClient instance.
* @param chainIdIndices Complete set of ordered chain IDs.
* @param hubPoolClient HubPoolClient instance.
* @param spokePoolClients Set of SpokePoolClient instances, mapped by chain ID.
* @param filter Optional filtering criteria.
* @returns Array of RefundRequestWithBlock events matching the chain ID and optional filtering criteria.
*/
export async function getValidRefundCandidates(
chainId: number,
hubPoolClient: HubPoolClient,
spokePoolClients: SpokePoolClients,
filter: Pick<SpokePoolFillFilter, "fromBlock" | "toBlock"> = {},
ignoredDepositValidationParams: string[] = []
): Promise<(RefundRequestWithBlock & { matchedDeposit: DepositWithBlock })[]> {
const spokePoolClient = spokePoolClients[chainId];
assert(isDefined(spokePoolClient));

const { fromBlock, toBlock } = filter;

return (
await mapAsync(spokePoolClient.getRefundRequests(), async (refundRequest) => {
if (isDefined(fromBlock) && fromBlock > refundRequest.blockNumber) {
return undefined;
}
if (isDefined(toBlock) && toBlock < refundRequest.blockNumber) {
return undefined;
}

const result = await refundRequestIsValid(
spokePoolClients,
hubPoolClient,
refundRequest,
ignoredDepositValidationParams
);
if (result.valid) {
const matchedDeposit = result.matchingDeposit;
if (matchedDeposit === undefined) {
throw new Error("refundRequestIsValid returned true but matchingDeposit is undefined");
}
return {
...refundRequest,
matchedDeposit,
};
} else {
return undefined;
}
})
).filter((refundRequest) => refundRequest !== undefined) as (RefundRequestWithBlock & {
matchedDeposit: DepositWithBlock;
})[];
}

/**
* Serializes a `UBAClientState` object.
* @param ubaClientState `UBAClientState` object to serialize.
Expand Down
Loading

0 comments on commit c48dcca

Please sign in to comment.