Skip to content

Commit

Permalink
basic accomodation for undefined
Browse files Browse the repository at this point in the history
Signed-off-by: bennett <bennett@umaproject.org>
  • Loading branch information
bmzig committed Sep 3, 2024
1 parent c23eb42 commit 384853b
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 68 deletions.
31 changes: 20 additions & 11 deletions src/clients/BundleDataClient/utils/DataworkerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
fixedPointAdjustment,
count2DDictionaryValues,
count3DDictionaryValues,
isDefined,
} from "../../../utils";
import {
addLastRunningBalance,
Expand Down Expand Up @@ -160,8 +161,10 @@ export function _buildPoolRebalanceRoot(
mainnetBundleEndBlock
);

updateRunningBalance(runningBalances, repaymentChainId, l1TokenCounterpart, totalRefundAmount);
updateRunningBalance(realizedLpFees, repaymentChainId, l1TokenCounterpart, totalRealizedLpFee);
if (isDefined(l1TokenCounterpart)) {
updateRunningBalance(runningBalances, repaymentChainId, l1TokenCounterpart, totalRefundAmount);
updateRunningBalance(realizedLpFees, repaymentChainId, l1TokenCounterpart, totalRealizedLpFee);
}
}
);
});
Expand All @@ -182,10 +185,12 @@ export function _buildPoolRebalanceRoot(
destinationChainId,
mainnetBundleEndBlock
);
const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(fixedPointAdjustment);
updateRunningBalance(runningBalances, destinationChainId, l1TokenCounterpart, deposit.inputAmount.sub(lpFee));
// Slow fill LP fees are accounted for when the slow fill executes and a V3FilledRelay is emitted. i.e. when
// the slow fill execution is included in bundleFillsV3.
if (isDefined(l1TokenCounterpart)) {
const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(fixedPointAdjustment);
updateRunningBalance(runningBalances, destinationChainId, l1TokenCounterpart, deposit.inputAmount.sub(lpFee));
// Slow fill LP fees are accounted for when the slow fill executes and a V3FilledRelay is emitted. i.e. when
// the slow fill execution is included in bundleFillsV3.
}
});
});
});
Expand All @@ -206,10 +211,12 @@ export function _buildPoolRebalanceRoot(
destinationChainId,
mainnetBundleEndBlock
);
const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(fixedPointAdjustment);
updateRunningBalance(runningBalances, destinationChainId, l1TokenCounterpart, lpFee.sub(deposit.inputAmount));
// Slow fills don't add to lpFees, only when the slow fill is executed and a V3FilledRelay is emitted, so
// we don't need to subtract it here. Moreover, the HubPoole expects bundleLpFees to be > 0.
if (isDefined(l1TokenCounterpart)) {
const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(fixedPointAdjustment);
updateRunningBalance(runningBalances, destinationChainId, l1TokenCounterpart, lpFee.sub(deposit.inputAmount));
// Slow fills don't add to lpFees, only when the slow fill is executed and a V3FilledRelay is emitted, so
// we don't need to subtract it here. Moreover, the HubPoole expects bundleLpFees to be > 0.
}
});
});
});
Expand Down Expand Up @@ -242,7 +249,9 @@ export function _buildPoolRebalanceRoot(
originChainId,
mainnetBundleEndBlock
);
updateRunningBalance(runningBalances, originChainId, l1TokenCounterpart, deposit.inputAmount);
if (isDefined(l1TokenCounterpart)) {
updateRunningBalance(runningBalances, originChainId, l1TokenCounterpart, deposit.inputAmount);
}
});
});
});
Expand Down
8 changes: 5 additions & 3 deletions src/clients/BundleDataClient/utils/FillUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Fill } from "../../../interfaces";
import { getBlockRangeForChain, isSlowFill } from "../../../utils";
import { getBlockRangeForChain, isSlowFill, assert, isDefined } from "../../../utils";
import { HubPoolClient } from "../../HubPoolClient";

export function getRefundInformationFromFill(
Expand Down Expand Up @@ -32,13 +32,15 @@ export function getRefundInformationFromFill(
fill.inputToken,
fill.originChainId,
endBlockForMainnet
);
)!;

assert(isDefined(l1TokenCounterpart), "There must be an l1 token counterpart for a filled deposit");
const repaymentToken = hubPoolClient.getL2TokenForL1TokenAtBlock(
l1TokenCounterpart,
chainToSendRefundTo,
endBlockForMainnet
);
)!;
assert(isDefined(repaymentToken), "There must be defined repayment token for a filled deposit");
return {
chainToSendRefundTo,
repaymentToken,
Expand Down
6 changes: 4 additions & 2 deletions src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MerkleTree } from "@across-protocol/contracts/dist/utils/MerkleTree";
import { RunningBalances, PoolRebalanceLeaf, Clients, SpokePoolTargetBalance } from "../../../interfaces";
import { SpokePoolClient } from "../../SpokePoolClient";
import { BigNumber } from "ethers";
import { bnZero, compareAddresses } from "../../../utils";
import { bnZero, compareAddresses, isDefined } from "../../../utils";
import { HubPoolClient } from "../../HubPoolClient";
import { V3DepositWithBlock } from "./shims";
import { AcrossConfigStoreClient } from "../../AcrossConfigStoreClient";
Expand Down Expand Up @@ -171,7 +171,9 @@ export function updateRunningBalanceForDeposit(
deposit.originChainId,
deposit.quoteBlockNumber
);
updateRunningBalance(runningBalances, deposit.originChainId, l1TokenCounterpart, updateAmount);
if (isDefined(l1TokenCounterpart)) {
updateRunningBalance(runningBalances, deposit.originChainId, l1TokenCounterpart, updateAmount);
}
}

export function constructPoolRebalanceLeaves(
Expand Down
58 changes: 42 additions & 16 deletions src/clients/HubPoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ export class HubPoolClient extends BaseAbstractClient {
* @param deposit Deposit event
* @param returns string L1 token counterpart for Deposit
*/
getL1TokenForDeposit(deposit: Pick<DepositWithBlock, "originChainId" | "inputToken" | "quoteBlockNumber">): string {
getL1TokenForDeposit(
deposit: Pick<DepositWithBlock, "originChainId" | "inputToken" | "quoteBlockNumber">
): string | undefined {
// L1-->L2 token mappings are set via PoolRebalanceRoutes which occur on mainnet,
// so we use the latest token mapping. This way if a very old deposit is filled, the relayer can use the
// latest L2 token mapping to find the L1 token counterpart.
Expand All @@ -232,16 +234,18 @@ export class HubPoolClient extends BaseAbstractClient {
* Returns the L2 token that should be used as a counterpart to a deposit event. For example, the caller
* might want to know what the refund token will be on l2ChainId for the deposit event.
* @param l2ChainId Chain where caller wants to get L2 token counterpart for
* @param event Deposit event
* @param event Deposit eventn
* @returns string L2 token counterpart on l2ChainId
*/
getL2TokenForDeposit(
deposit: Pick<DepositWithBlock, "originChainId" | "destinationChainId" | "inputToken" | "quoteBlockNumber">,
l2ChainId = deposit.destinationChainId
): string {
): string | undefined {
const l1Token = this.getL1TokenForDeposit(deposit);
// Use the latest hub block number to find the L2 token counterpart.
return this.getL2TokenForL1TokenAtBlock(l1Token, l2ChainId, deposit.quoteBlockNumber);
return isDefined(l1Token)
? this.getL2TokenForL1TokenAtBlock(l1Token, l2ChainId, deposit.quoteBlockNumber)
: undefined;
}

l2TokenEnabledForL1Token(l1Token: string, destinationChainId: number): boolean {
Expand Down Expand Up @@ -363,11 +367,23 @@ export class HubPoolClient extends BaseAbstractClient {
// Map SpokePool token addresses to HubPool token addresses.
// Note: Should only be accessed via `getHubPoolToken()` or `getHubPoolTokens()`.
const hubPoolTokens: { [k: string]: string } = {};
const getHubPoolToken = (deposit: LpFeeRequest, quoteBlockNumber: number): string => {
const getHubPoolToken = (deposit: LpFeeRequest, quoteBlockNumber: number): string | undefined => {
const tokenKey = `${deposit.originChainId}-${deposit.inputToken}`;
return (hubPoolTokens[tokenKey] ??= this.getL1TokenForDeposit({ ...deposit, quoteBlockNumber }));
if (isDefined(hubPoolTokens[tokenKey])) {
return hubPoolTokens[tokenKey];
}
const l1Token = this.getL1TokenForDeposit({ ...deposit, quoteBlockNumber });
if (!isDefined(l1Token)) {
this.logger.warn({
at: "HubPoolClient",
message: `Unable to determine an appropriate L1 Token for deposit ${deposit}`,
});
}
hubPoolTokens[tokenKey] = l1Token;

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Builds

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Builds

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Builds

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Builds

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Builds

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Builds

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Lint

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Lint

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Lint

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Test

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Test

Type 'string | undefined' is not assignable to type 'string'.

Check failure on line 382 in src/clients/HubPoolClient.ts

View workflow job for this annotation

GitHub Actions / Test

Type 'string | undefined' is not assignable to type 'string'.
return l1Token;
};
const getHubPoolTokens = (): string[] => dedupArray(Object.values(hubPoolTokens));
const getHubPoolTokens = (): string[] =>
dedupArray(Object.values(hubPoolTokens)).filter((token) => isDefined(token));

// Helper to resolve the unqiue hubPoolToken & quoteTimestamp mappings.
const resolveUniqueQuoteTimestamps = (deposit: LpFeeRequest): void => {
Expand All @@ -376,6 +392,9 @@ export class HubPoolClient extends BaseAbstractClient {
// Resolve the HubPool token address for this origin chainId/token pair, if it isn't already known.
const quoteBlockNumber = quoteBlocks[quoteTimestamp];
const hubPoolToken = getHubPoolToken(deposit, quoteBlockNumber);
if (!isDefined(hubPoolToken)) {
return;
}

// Append the quoteTimestamp for this HubPool token, if it isn't already enqueued.
utilizationTimestamps[hubPoolToken] ??= [];
Expand Down Expand Up @@ -416,11 +435,11 @@ export class HubPoolClient extends BaseAbstractClient {
const { originChainId, paymentChainId, inputAmount, quoteTimestamp } = deposit;
const quoteBlock = quoteBlocks[quoteTimestamp];

if (paymentChainId === undefined) {
const hubPoolToken = getHubPoolToken(deposit, quoteBlock);
if (!isDefined(paymentChainId) || !isDefined(hubPoolToken)) {
return { quoteBlock, realizedLpFeePct: bnZero };
}

const hubPoolToken = getHubPoolToken(deposit, quoteBlock);
const rateModel = this.configStoreClient.getRateModelForBlockNumber(
hubPoolToken,
originChainId,
Expand Down Expand Up @@ -480,13 +499,16 @@ export class HubPoolClient extends BaseAbstractClient {

getL1TokenInfoForL2Token(l2Token: string, chainId: number): L1Token | undefined {
const l1TokenCounterpart = this.getL1TokenForL2TokenAtBlock(l2Token, chainId, this.latestBlockSearched);
return this.getTokenInfoForL1Token(l1TokenCounterpart);
return isDefined(l1TokenCounterpart) ? this.getTokenInfoForL1Token(l1TokenCounterpart) : undefined;
}

getTokenInfoForDeposit(deposit: Deposit): L1Token | undefined {
return this.getTokenInfoForL1Token(
this.getL1TokenForL2TokenAtBlock(deposit.inputToken, deposit.originChainId, this.latestBlockSearched)
const l1Token = this.getL1TokenForL2TokenAtBlock(
deposit.inputToken,
deposit.originChainId,
this.latestBlockSearched
);
return isDefined(l1Token) ? this.getTokenInfoForL1Token(l1Token) : undefined;
}

getTokenInfo(chainId: number | string, tokenAddress: string): L1Token | undefined {
Expand All @@ -509,10 +531,14 @@ export class HubPoolClient extends BaseAbstractClient {
return false;
}

// Resolve both HubPool tokens back to a current SpokePool token and verify that they match.
const _tokenA = this.getL2TokenForL1TokenAtBlock(l1TokenA, chainIdA, hubPoolBlock);
const _tokenB = this.getL2TokenForL1TokenAtBlock(l1TokenB, chainIdB, hubPoolBlock);
return tokenA === _tokenA && tokenB === _tokenB;
if (isDefined(l1TokenA) && isDefined(l1TokenB)) {
// Resolve both HubPool tokens back to a current SpokePool token and verify that they match.
const _tokenA = this.getL2TokenForL1TokenAtBlock(l1TokenA, chainIdA, hubPoolBlock);
const _tokenB = this.getL2TokenForL1TokenAtBlock(l1TokenB, chainIdB, hubPoolBlock);
return tokenA === _tokenA && tokenB === _tokenB;
} else {
return false;
}
} catch {
return false; // One or both input tokens were not recognised.
}
Expand Down
3 changes: 2 additions & 1 deletion src/clients/SpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,8 @@ export class SpokePoolClient extends BaseAbstractClient {
return ZERO_ADDRESS;
}

return this.hubPoolClient.getL2TokenForDeposit(deposit);
// If there is no l2 token for the deposit also return the zero address.
return this.hubPoolClient.getL2TokenForDeposit(deposit) ?? ZERO_ADDRESS;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/clients/mocks/MockHubPoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export class MockHubPoolClient extends HubPoolClient {
this.spokePoolTokens[l1Token][chainId] = l2Token;
}

getL1TokenForL2TokenAtBlock(l2Token: string, chainId: number, blockNumber: number): string {
getL1TokenForL2TokenAtBlock(l2Token: string, chainId: number, blockNumber: number): string | undefined {
const l1Token = Object.keys(this.spokePoolTokens).find(
(l1Token) => this.spokePoolTokens[l1Token]?.[chainId] === l2Token
);
Expand Down
53 changes: 19 additions & 34 deletions test/HubPoolClient.DepositToDestinationToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,12 @@ describe("HubPoolClient: Deposit to Destination Token", function () {
});

it("Gets L2 token counterpart", async function () {
expect(() => hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, 0)).to.throw(
/Could not find SpokePool mapping/
);
expect(hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, 0)).to.be.undefined;
const e1 = hubPoolClient.setPoolRebalanceRoute(destinationChainId, randomL1Token, randomDestinationToken);
await hubPoolClient.update();

// If input hub pool block is before all events, should throw.
expect(() => hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, 0)).to.throw(
/Could not find SpokePool mapping/
);
expect(hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, 0)).to.be.undefined;
expect(hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, e1.blockNumber)).to.equal(
randomDestinationToken
);
Expand All @@ -73,16 +69,12 @@ describe("HubPoolClient: Deposit to Destination Token", function () {
);
});
it("Gets L1 token counterpart", async function () {
expect(() => hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, 0)).to.throw(
/Could not find HubPool mapping/
);
expect(hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, 0)).to.be.undefined;
const e1 = hubPoolClient.setPoolRebalanceRoute(destinationChainId, randomL1Token, randomDestinationToken);
await hubPoolClient.update();

// If input hub pool block is before all events, should throw.
expect(() => hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, 0)).to.throw(
/Could not find HubPool mapping/
);
// If input hub pool block is before all events, should resolve no l1 token.
expect(hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, 0)).to.be.undefined;
expect(
hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, e1.blockNumber)
).to.equal(randomL1Token);
Expand All @@ -98,13 +90,11 @@ describe("HubPoolClient: Deposit to Destination Token", function () {
hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, e1.blockNumber)
).to.equal(randomL1Token);

// If L2 token mapping doesn't exist, throw.
expect(() => hubPoolClient.getL1TokenForL2TokenAtBlock(randomL1Token, destinationChainId, e2.blockNumber)).to.throw(
/Could not find HubPool mapping/
);
expect(() =>
hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, originChainId, e2.blockNumber)
).to.throw(/Could not find HubPool mapping/);
// If L2 token mapping doesn't exist, return undefined.
expect(hubPoolClient.getL1TokenForL2TokenAtBlock(randomL1Token, destinationChainId, e2.blockNumber)).to.be
.undefined;
expect(hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, originChainId, e2.blockNumber)).to.be
.undefined;
});
it("Gets L1 token for deposit", async function () {
const depositData = {
Expand All @@ -119,18 +109,15 @@ describe("HubPoolClient: Deposit to Destination Token", function () {
);

// quote block too early
expect(() => hubPoolClient.getL1TokenForDeposit({ ...depositData, quoteBlockNumber: 0 })).to.throw(
/Could not find HubPool mapping/
);

expect(hubPoolClient.getL1TokenForDeposit({ ...depositData, quoteBlockNumber: 0 })).to.be.undefined;
// no deposit with matching origin token
expect(() =>
expect(
hubPoolClient.getL1TokenForDeposit({
...depositData,
inputToken: randomL1Token,
quoteBlockNumber: e0.blockNumber,
})
).to.throw(/Could not find HubPool mapping/);
).to.be.undefined;

const e1 = hubPoolClient.setPoolRebalanceRoute(originChainId, randomOriginToken, randomOriginToken);
await hubPoolClient.update();
Expand All @@ -152,24 +139,22 @@ describe("HubPoolClient: Deposit to Destination Token", function () {
).to.equal(randomDestinationToken);

// origin chain token is set but none for destination chain yet, as of e0.
expect(() =>
hubPoolClient.getL2TokenForDeposit({ ...depositData, destinationChainId, quoteBlockNumber: e0.blockNumber })
).to.throw(/Could not find SpokePool mapping/);
expect(hubPoolClient.getL2TokenForDeposit({ ...depositData, destinationChainId, quoteBlockNumber: e0.blockNumber }))
.to.be.undefined;

// quote block too early
expect(() =>
hubPoolClient.getL2TokenForDeposit({ ...depositData, destinationChainId, quoteBlockNumber: 0 })
).to.throw(/Could not find HubPool mapping/);
expect(hubPoolClient.getL2TokenForDeposit({ ...depositData, destinationChainId, quoteBlockNumber: 0 })).to.be
.undefined;

// No deposit with matching token.
expect(() =>
expect(
hubPoolClient.getL2TokenForDeposit({
...depositData,
destinationChainId,
inputToken: randomL1Token,
quoteBlockNumber: e0.blockNumber,
})
).to.throw(/Could not find HubPool mapping/);
).to.be.undefined;

const e2 = hubPoolClient.setPoolRebalanceRoute(destinationChainId, randomL1Token, randomL1Token);
await hubPoolClient.update();
Expand Down

0 comments on commit 384853b

Please sign in to comment.