Skip to content

Commit

Permalink
Merge branch 'master' into npai/v3-dataworker
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholaspai authored Feb 2, 2024
2 parents c5025f2 + b320a51 commit e71ddc9
Show file tree
Hide file tree
Showing 11 changed files with 62 additions and 298 deletions.
2 changes: 0 additions & 2 deletions src/clients/BundleDataClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,6 @@ export class BundleDataClient {
assignValidFillToFillsToRefund(fillsToRefund, fill, chainToSendRefundTo, repaymentToken);
allRelayerRefunds.push({ repaymentToken, repaymentChain: chainToSendRefundTo });

// Note: the UBA model doesn't use the following realized LP fees data but we keep it for backwards
// compatibility.
updateTotalRealizedLpFeePct(fillsToRefund, fill, chainToSendRefundTo, repaymentToken);

// Save deposit as one that is eligible for a slow fill, since there is a fill
Expand Down
1 change: 0 additions & 1 deletion src/clients/ConfigStoreClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +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;

export class ConfigStoreClient extends clients.AcrossConfigStoreClient {
Expand Down
4 changes: 0 additions & 4 deletions src/common/Constants.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { ethers } from "../utils";
import { utils } from "@across-protocol/sdk-v2";

// Maximum supported version of the configuration loaded into the Across ConfigStore.
// It protects bots from running outdated code against newer version of the on-chain config store.
// @dev Incorrectly setting this value may lead to incorrect behaviour and potential loss of funds.
export const CONFIG_STORE_VERSION = 2;

// The first version where UBA is in effect.
export const { UBA_MIN_CONFIG_STORE_VERSION } = utils;

export const RELAYER_MIN_FEE_PCT = 0.0003;

// Target ~4 hours
Expand Down
79 changes: 32 additions & 47 deletions src/dataworker/DataworkerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,13 @@ export function _buildSlowRelayRoot(unfilledDeposits: UnfilledDeposit[]): {
};
}

// @dev `runningBalances` is only used in pre-UBA model to determine whether a spoke's running balances
// are over the target/threshold. In the UBA model, this is handled at the UBA client level.
export function _buildRelayerRefundRoot(
endBlockForMainnet: number,
fillsToRefund: FillsToRefund,
poolRebalanceLeaves: PoolRebalanceLeaf[],
runningBalances: RunningBalances,
clients: DataworkerClients,
maxRefundCount: number,
isUBA = false
maxRefundCount: number
): {
leaves: RelayerRefundLeaf[];
tree: MerkleTree<RelayerRefundLeaf>;
Expand All @@ -249,38 +246,32 @@ export function _buildRelayerRefundRoot(
// return value, so sort refund addresses by refund amount (descending) and then address (ascending).
const sortedRefundAddresses = sortRefundAddresses(refunds);

const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock(
l2TokenAddress,
repaymentChainId,
endBlockForMainnet
);

const spokePoolTargetBalance = clients.configStoreClient.getSpokeTargetBalancesForBlock(
l1TokenCounterpart,
repaymentChainId,
endBlockForMainnet
);

// The `amountToReturn` for a { repaymentChainId, L2TokenAddress} should be set to max(-netSendAmount, 0).
const amountToReturn = getAmountToReturnForRelayerRefundLeaf(
spokePoolTargetBalance,
runningBalances[repaymentChainId][l1TokenCounterpart]
);

// Create leaf for { repaymentChainId, L2TokenAddress }, split leaves into sub-leaves if there are too many
// refunds.

// If UBA model, set amount to return to 0 for now until we match this leaf against a pool rebalance leaf. In
// the UBA model, the amount to return is simpler to compute: simply set it equal to the negative
// net send amount value if the net send amount is negative.
let amountToReturn = bnZero;
if (!isUBA) {
const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock(
l2TokenAddress,
repaymentChainId,
endBlockForMainnet
);

const spokePoolTargetBalance = clients.configStoreClient.getSpokeTargetBalancesForBlock(
l1TokenCounterpart,
Number(repaymentChainId),
endBlockForMainnet
);

// The `amountToReturn` for a { repaymentChainId, L2TokenAddress} should be set to max(-netSendAmount, 0).
amountToReturn = getAmountToReturnForRelayerRefundLeaf(
spokePoolTargetBalance,
runningBalances[repaymentChainId][l1TokenCounterpart]
);
}
for (let i = 0; i < sortedRefundAddresses.length; i += maxRefundCount) {
relayerRefundLeaves.push({
groupIndex: i, // Will delete this group index after using it to sort leaves for the same chain ID and
// L2 token address
amountToReturn: i === 0 ? amountToReturn : bnZero,
chainId: Number(repaymentChainId),
chainId: repaymentChainId,
refundAmounts: sortedRefundAddresses.slice(i, i + maxRefundCount).map((address) => refunds[address]),
leafId: 0, // Will be updated before inserting into tree when we sort all leaves.
l2TokenAddress,
Expand All @@ -301,32 +292,26 @@ export function _buildRelayerRefundRoot(
}

const l2TokenCounterpart = clients.hubPoolClient.getL2TokenForL1TokenAtBlock(leaf.l1Tokens[index], leaf.chainId);
// If we've already seen this leaf, then skip. If UBA, reset the net send amount and then skip.
// If we've already seen this leaf, then skip.
const existingLeaf = relayerRefundLeaves.find(
(relayerRefundLeaf) =>
relayerRefundLeaf.chainId === leaf.chainId && relayerRefundLeaf.l2TokenAddress === l2TokenCounterpart
);
if (existingLeaf !== undefined) {
if (isUBA) {
existingLeaf.amountToReturn = netSendAmount.mul(-1);
}
return;
}

// If UBA model we don't need to do the following to figure out the amount to return:
let amountToReturn = netSendAmount.mul(-1);
if (!isUBA) {
const spokePoolTargetBalance = clients.configStoreClient.getSpokeTargetBalancesForBlock(
leaf.l1Tokens[index],
leaf.chainId,
endBlockForMainnet
);

amountToReturn = getAmountToReturnForRelayerRefundLeaf(
spokePoolTargetBalance,
runningBalances[leaf.chainId][leaf.l1Tokens[index]]
);
}
const spokePoolTargetBalance = clients.configStoreClient.getSpokeTargetBalancesForBlock(
leaf.l1Tokens[index],
leaf.chainId,
endBlockForMainnet
);

const amountToReturn = getAmountToReturnForRelayerRefundLeaf(
spokePoolTargetBalance,
runningBalances[leaf.chainId][leaf.l1Tokens[index]]
);

relayerRefundLeaves.push({
groupIndex: 0, // Will delete this group index after using it to sort leaves for the same chain ID and
// L2 token address
Expand Down
69 changes: 20 additions & 49 deletions src/dataworker/PoolRebalanceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,18 +320,16 @@ export function constructPoolRebalanceLeaves(
runningBalances: interfaces.RunningBalances,
realizedLpFees: interfaces.RunningBalances,
configStoreClient: ConfigStoreClient,
maxL1TokenCount?: number,
incentivePoolBalances?: interfaces.RunningBalances,
netSendAmounts?: interfaces.RunningBalances,
ubaMode = false
maxL1TokenCount?: number
): interfaces.PoolRebalanceLeaf[] {
// Create one leaf per L2 chain ID. First we'll create a leaf with all L1 tokens for each chain ID, and then
// we'll split up any leaves with too many L1 tokens.
const leaves: interfaces.PoolRebalanceLeaf[] = [];
Object.keys(runningBalances)
.map((chainId) => Number(chainId))
// Leaves should be sorted by ascending chain ID
.sort((chainIdA, chainIdB) => Number(chainIdA) - Number(chainIdB))
.map((chainId: string) => {
.sort((chainIdA, chainIdB) => chainIdA - chainIdB)
.map((chainId) => {
// Sort addresses.
const sortedL1Tokens = Object.keys(runningBalances[chainId]).sort((addressA, addressB) => {
return compareAddresses(addressA, addressB);
Expand All @@ -347,57 +345,30 @@ export function constructPoolRebalanceLeaves(
const l1TokensToIncludeInThisLeaf = sortedL1Tokens.slice(i, i + maxL1TokensPerLeaf);

const spokeTargetBalances = l1TokensToIncludeInThisLeaf.map((l1Token) =>
configStoreClient.getSpokeTargetBalancesForBlock(l1Token, Number(chainId), latestMainnetBlock)
configStoreClient.getSpokeTargetBalancesForBlock(l1Token, chainId, latestMainnetBlock)
);

// Build leaves using running balances and realized lp fees data for l1Token + chain, or default to
// zero if undefined.
const leafBundleLpFees = l1TokensToIncludeInThisLeaf.map((l1Token) => {
if (realizedLpFees[chainId]?.[l1Token]) {
return realizedLpFees[chainId][l1Token];
} else {
return bnZero;
}
});
const leafNetSendAmounts = l1TokensToIncludeInThisLeaf.map((l1Token, index) => {
if (ubaMode && netSendAmounts?.[chainId] && netSendAmounts[chainId][l1Token]) {
return netSendAmounts[chainId][l1Token];
} else if (runningBalances[chainId] && runningBalances[chainId][l1Token]) {
return getNetSendAmountForL1Token(spokeTargetBalances[index], runningBalances[chainId][l1Token]);
} else {
return bnZero;
}
});
const leafRunningBalances = l1TokensToIncludeInThisLeaf.map((l1Token, index) => {
if (runningBalances[chainId]?.[l1Token]) {
// If UBA bundle, then we don't need to compare running balance to transfer thresholds or
// spoke target balances, as the UBA client already performs similar logic to set the running balances
// for each flow. In the UBA, simply take the running balances computed by the UBA client.
if (ubaMode) {
return runningBalances[chainId][l1Token];
} else {
return getRunningBalanceForL1Token(spokeTargetBalances[index], runningBalances[chainId][l1Token]);
}
} else {
return bnZero;
}
});
const incentiveBalances =
ubaMode &&
incentivePoolBalances &&
l1TokensToIncludeInThisLeaf.map((l1Token) => {
if (incentivePoolBalances[chainId]?.[l1Token]) {
return incentivePoolBalances[chainId][l1Token];
} else {
return bnZero;
}
});
const leafBundleLpFees = l1TokensToIncludeInThisLeaf.map(
(l1Token) => realizedLpFees[chainId]?.[l1Token] ?? bnZero
);
const leafNetSendAmounts = l1TokensToIncludeInThisLeaf.map((l1Token, index) =>
runningBalances[chainId] && runningBalances[chainId][l1Token]
? getNetSendAmountForL1Token(spokeTargetBalances[index], runningBalances[chainId][l1Token])
: bnZero
);
const leafRunningBalances = l1TokensToIncludeInThisLeaf.map((l1Token, index) =>
runningBalances[chainId]?.[l1Token]
? getRunningBalanceForL1Token(spokeTargetBalances[index], runningBalances[chainId][l1Token])
: bnZero
);

leaves.push({
chainId: Number(chainId),
chainId: chainId,
bundleLpFees: leafBundleLpFees,
netSendAmounts: leafNetSendAmounts,
runningBalances: leafRunningBalances.concat(incentivePoolBalances ? incentiveBalances : []),
runningBalances: leafRunningBalances,
groupIndex: groupIndexForChainId++,
leafId: leaves.length,
l1Tokens: l1TokensToIncludeInThisLeaf,
Expand Down
4 changes: 1 addition & 3 deletions src/dataworker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ When an L2's SpokePool doesn't have a canonical bridge connected to Ethereum, Ac

One, it could use a third party bridge, but this is strongly avoided because it exposes LP capital to additional trust assumptions. Across historically has avoided supporting networks without canonical bridges for this reason, and moreover its been unnecessary as the most popular L2 networks have proven to be those that have spent the time to build out an effective canonical bridge.

Secondly, it could try to incentivize depositors to send capital to the SpokePool that Across can use to refund relayers and execute slow fills. Depositors obviously are incentivized by profit so they must have a reason to deposit on a SpokePool. This is why the UBA fee model introduces a way for Across to know (or anticipate) when it will need more funds on a SpokePool and properly incentivize deposits there.
Secondly, it could try to incentivize depositors to send capital to the SpokePool that Across can use to refund relayers and execute slow fills. Depositors obviously are incentivized by profit so they must have a reason to deposit on a SpokePool.

## So, how does Across move capital around?

Expand Down Expand Up @@ -166,8 +166,6 @@ Therefore, `amount`, `originChainId`, `destinationChainId`, `relayerFeePct`, `de

Finally, the fill's `realizedLpFeePct` must be correct. Currently this is deterministically linked with the deposit's `quoteTimestamp`: there is a correct `realizedLpFeePct` for each `quoteTimestamp`, which is computed by querying the HubPool's "utilization" at the Ethereum block height corresponding to the `quoteTimestamp`. This is described in the [UMIP here](https://github.com/UMAprotocol/UMIPs/blob/e3198578b1d339914afa5243a80e3ac8055fba34/UMIPs/umip-157.md#validating-realizedlpfeepct).

_N.B. This formula for setting the `realizedLpFeePct` will change in the UBA model and will not be primarily linked to the deposit's `quoteTimestamp`._

## Incorporating slow fills

At this point we have `D`, the set of all deposits in the block range, and `F`, the set of all fills that were validated in the block range. Each `F` has a `relayer` that needs to be refunded, so we need to mark down a refund (equal to the filled amount plus the relayer fee percentage) for each `F`.
Expand Down
4 changes: 2 additions & 2 deletions src/finalizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ export async function finalize(
spokePoolClients: SpokePoolClientsByChain,
configuredChainIds: number[],
submitFinalizationTransactions: boolean,
optimisticRollupFinalizationWindow: number = 5 * oneDaySeconds,
polygonFinalizationWindow: number = oneDaySeconds
optimisticRollupFinalizationWindow = 5 * oneDaySeconds,
polygonFinalizationWindow = 5 * oneDaySeconds
): Promise<void> {
const finalizationWindows: { [chainId: number]: number } = {
// Mainnets
Expand Down
14 changes: 1 addition & 13 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { clients, interfaces } from "@across-protocol/sdk-v2";
import { interfaces } from "@across-protocol/sdk-v2";

export * from "./InventoryManagement";
export * from "./SpokePool";
Expand Down Expand Up @@ -59,18 +59,6 @@ export type FillsToRefund = interfaces.FillsToRefund;
export type RunningBalances = interfaces.RunningBalances;
export type TokensBridged = interfaces.TokensBridged;

// UBA interfaces
export type UbaInflow = interfaces.UbaInflow;
export type UbaOutflow = interfaces.UbaOutflow;
export type UbaFlow = interfaces.UbaFlow;
export type UBASpokeBalanceType = interfaces.UBASpokeBalanceType;
export type UBAFeeResult = interfaces.UBAFeeResult;
export type UBABalancingFee = clients.BalancingFeeReturnType;
export type UBASystemFee = clients.SystemFeeResult;
export const isUbaInflow = interfaces.isUbaInflow;
export const isUbaOutflow = interfaces.isUbaOutflow;
export const outflowIsFill = interfaces.outflowIsFill;

export type CachingMechanismInterface = interfaces.CachingMechanismInterface;

// V2 / V3 interfaces
Expand Down
10 changes: 4 additions & 6 deletions src/relayer/Relayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,10 @@ export class Relayer {
// Filter out deposits whose block time does not meet the minimum number of confirmations for the
// corresponding origin chain. Finally, sort the deposits by the total earnable fee for the relayer.
const confirmedUnfilledDeposits = unfilledDeposits
.filter((x) => {
return (
x.deposit.blockNumber <=
spokePoolClients[x.deposit.originChainId].latestBlockSearched - mdcPerChain[x.deposit.originChainId]
);
})
.filter(
({ deposit: { originChainId, blockNumber } }) =>
blockNumber <= spokePoolClients[originChainId].latestBlockSearched - mdcPerChain[originChainId]
)
.sort((a, b) =>
a.unfilledAmount.mul(a.deposit.relayerFeePct).lt(b.unfilledAmount.mul(b.deposit.relayerFeePct)) ? 1 : -1
);
Expand Down
Loading

0 comments on commit e71ddc9

Please sign in to comment.