Skip to content

Commit

Permalink
Merge branch 'master' into pxrl/gasScaling
Browse files Browse the repository at this point in the history
  • Loading branch information
pxrl authored May 10, 2024
2 parents 3b95ea3 + 4675913 commit 55c958b
Show file tree
Hide file tree
Showing 7 changed files with 598 additions and 166 deletions.
260 changes: 172 additions & 88 deletions src/clients/InventoryClient.ts

Large diffs are not rendered by default.

75 changes: 60 additions & 15 deletions src/interfaces/InventoryManagement.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,59 @@
import { BigNumber } from "ethers";
import { BigNumber, utils as ethersUtils } from "ethers";
import { TOKEN_SYMBOLS_MAP } from "../utils";

export type TokenBalanceConfig = {
targetOverageBuffer: BigNumber; // Max multiplier for targetPct, to give flexibility in repayment chain selection.
targetPct: BigNumber; // The desired amount of the given token on the L2 chainId.
thresholdPct: BigNumber; // Threshold, below which, we will execute a rebalance.
unwrapWethThreshold?: BigNumber; // Threshold for ETH to trigger WETH unwrapping to maintain ETH balance.
unwrapWethTarget?: BigNumber; // Amount of WETH to unwrap to refill ETH. Unused if unwrapWethThreshold is undefined.
};

export type ChainTokenConfig = {
[chainId: string]: TokenBalanceConfig;
};

// AliasConfig permits a single HubPool token to map onto multiple tokens on a remote chain.
export type ChainTokenInventory = {
[symbol: string]: ChainTokenConfig;
};

/**
* Example configuration:
* - DAI on chains 10 & 42161.
* - Bridged USDC (USDC.e, USDbC) on chains 10, 137, 324, 8453, 42161 & 59144.
* - Native USDC on Polygon.
*
* All token allocations are "global", so Polygon will be allocated a total of 8% of all USDC:
* - 4% of global USDC as Native USDC, and
* - 4% as Bridged USDC.
*
* "tokenConfig": {
* "DAI": {
* "10": { "targetPct": 8, "thresholdPct": 4 },
* "42161": { "targetPct": 8, "thresholdPct": 4 },
* },
* "USDC": {
* "USDC.e": {
* "10": { "targetPct": 8, "thresholdPct": 4 },
* "137": { "targetPct": 4, "thresholdPct": 2 },
* "324": { "targetPct": 8, "thresholdPct": 4 },
* "42161": { "targetPct": 8, "thresholdPct": 4 },
* "59144": { "targetPct": 5, "thresholdPct": 2 }
* },
* "USDbC": {
* "8453": { "targetPct": 5, "thresholdPct": 2 }
* },
* "USDC": {
* "137": { "targetPct": 4, "thresholdPct": 2 }
* }
* }
* }
*/
export interface InventoryConfig {
tokenConfig: {
[l1Token: string]: {
[chainId: string]: {
targetOverageBuffer: BigNumber; // The relayer will be allowed to hold this multiple times the targetPct
// of the full token balance on this chain.
targetPct: BigNumber; // The desired amount of the given token on the L2 chainId.
thresholdPct: BigNumber; // Threshold, below which, we will execute a rebalance.
unwrapWethThreshold?: BigNumber; // Threshold for ETH on this chain to trigger WETH unwrapping to maintain
// ETH balance
unwrapWethTarget?: BigNumber; // Amount of WETH to unwrap to refill ETH balance. Unused if unwrapWethThreshold
// is undefined.
};
};
};
// tokenConfig can map to a single token allocation, or a set of allocations that all map to the same HubPool token.
tokenConfig: { [l1Token: string]: ChainTokenConfig } | { [l1Token: string]: ChainTokenInventory };

// If ETH balance on chain is above threshold, wrap the excess over the target to WETH.
wrapEtherTargetPerChain: {
[chainId: number]: BigNumber;
Expand All @@ -25,3 +64,9 @@ export interface InventoryConfig {
};
wrapEtherThreshold: BigNumber;
}

export function isAliasConfig(config: ChainTokenConfig | ChainTokenInventory): config is ChainTokenInventory {
return (
Object.keys(config).every((k) => ethersUtils.isAddress(k)) || Object.keys(config).every((k) => TOKEN_SYMBOLS_MAP[k])
);
}
90 changes: 58 additions & 32 deletions src/relayer/RelayerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from "../utils";
import { CommonConfig, ProcessEnv } from "../common";
import * as Constants from "../common/Constants";
import { InventoryConfig } from "../interfaces";
import { InventoryConfig, TokenBalanceConfig, isAliasConfig } from "../interfaces/InventoryManagement";

type DepositConfirmationConfig = {
usdThreshold: BigNumber;
Expand Down Expand Up @@ -153,47 +153,73 @@ export class RelayerConfig extends CommonConfig {
}
});

const parseTokenConfig = (
l1Token: string,
chainId: string,
rawTokenConfig: TokenBalanceConfig
): TokenBalanceConfig => {
const { targetPct, thresholdPct, unwrapWethThreshold, unwrapWethTarget, targetOverageBuffer } = rawTokenConfig;
const tokenConfig: TokenBalanceConfig = { targetPct, thresholdPct, targetOverageBuffer };

assert(
targetPct !== undefined && thresholdPct !== undefined,
`Bad config. Must specify targetPct, thresholdPct for ${l1Token} on ${chainId}`
);
assert(
toBN(thresholdPct).lte(toBN(targetPct)),
`Bad config. thresholdPct<=targetPct for ${l1Token} on ${chainId}`
);
tokenConfig.targetPct = toBNWei(targetPct).div(100);
tokenConfig.thresholdPct = toBNWei(thresholdPct).div(100);

// Default to 150% the targetPct. targetOverageBuffer does not have to be defined so that no existing configs
// are broken. This is a reasonable default because it allows the relayer to be a bit more flexible in
// holding more tokens than the targetPct, but perhaps a better default is 100%
tokenConfig.targetOverageBuffer = toBNWei(targetOverageBuffer ?? "1.5");

// For WETH, also consider any unwrap target/threshold.
if (l1Token === TOKEN_SYMBOLS_MAP.WETH.addresses[this.hubPoolChainId]) {
if (unwrapWethThreshold !== undefined) {
tokenConfig.unwrapWethThreshold = toBNWei(unwrapWethThreshold);
}
tokenConfig.unwrapWethTarget = toBNWei(unwrapWethTarget ?? 2);
}

return tokenConfig;
};

const rawTokenConfigs = inventoryConfig?.tokenConfig ?? {};
const tokenConfigs = (inventoryConfig.tokenConfig = {});
Object.keys(rawTokenConfigs).forEach((l1Token) => {
// If the l1Token is a symbol, resolve the correct address.
const effectiveL1Token = ethersUtils.isAddress(l1Token)
? l1Token
: TOKEN_SYMBOLS_MAP[l1Token]?.addresses[this.hubPoolChainId];
: TOKEN_SYMBOLS_MAP[l1Token].addresses[this.hubPoolChainId];
assert(effectiveL1Token !== undefined, `No token identified for ${l1Token}`);

Object.keys(rawTokenConfigs[l1Token]).forEach((chainId) => {
const { targetPct, thresholdPct, unwrapWethThreshold, unwrapWethTarget, targetOverageBuffer } =
rawTokenConfigs[l1Token][chainId];
tokenConfigs[effectiveL1Token] ??= {};
const hubTokenConfig = rawTokenConfigs[l1Token];

tokenConfigs[effectiveL1Token] ??= {};
tokenConfigs[effectiveL1Token][chainId] ??= { targetPct, thresholdPct, targetOverageBuffer };
const tokenConfig = tokenConfigs[effectiveL1Token][chainId];
if (isAliasConfig(hubTokenConfig)) {
Object.keys(hubTokenConfig).forEach((symbol) => {
Object.keys(hubTokenConfig[symbol]).forEach((chainId) => {
const rawTokenConfig = hubTokenConfig[symbol][chainId];
const effectiveSpokeToken = TOKEN_SYMBOLS_MAP[symbol].addresses[chainId];

assert(
targetPct !== undefined && thresholdPct !== undefined,
`Bad config. Must specify targetPct, thresholdPct for ${l1Token} on ${chainId}`
);
assert(
toBN(thresholdPct).lte(toBN(targetPct)),
`Bad config. thresholdPct<=targetPct for ${l1Token} on ${chainId}`
);
tokenConfig.targetPct = toBNWei(targetPct).div(100);
tokenConfig.thresholdPct = toBNWei(thresholdPct).div(100);

// Default to 150% the targetPct. targetOverageBuffer does not have to be defined so that no existing configs
// are broken. This is a reasonable default because it allows the relayer to be a bit more flexible in
// holding more tokens than the targetPct, but perhaps a better default is 100%
tokenConfig.targetOverageBuffer = toBNWei(targetOverageBuffer ?? "1.5");

// For WETH, also consider any unwrap target/threshold.
if (effectiveL1Token === TOKEN_SYMBOLS_MAP.WETH.addresses[this.hubPoolChainId]) {
if (unwrapWethThreshold !== undefined) {
tokenConfig.unwrapWethThreshold = toBNWei(unwrapWethThreshold);
}
tokenConfig.unwrapWethTarget = toBNWei(unwrapWethTarget ?? 2);
}
});
tokenConfigs[effectiveL1Token][effectiveSpokeToken] ??= {};
tokenConfigs[effectiveL1Token][effectiveSpokeToken][chainId] = parseTokenConfig(
l1Token,
chainId,
rawTokenConfig
);
});
});
} else {
Object.keys(hubTokenConfig).forEach((chainId) => {
const rawTokenConfig = hubTokenConfig[chainId];
tokenConfigs[effectiveL1Token][chainId] = parseTokenConfig(l1Token, chainId, rawTokenConfig);
});
}
});
}

Expand Down
13 changes: 10 additions & 3 deletions src/scripts/validateRunningBalances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ let logger: winston.Logger;

const slowRootCache = {};

const expectedExcesses: { [chainId: number]: { [token: string]: number } } = {
[10]: { ["USDC"]: 15.336508 }, // On May 4th, USDC was sent to the SpokePool here: https://optimistic.etherscan.io/tx/0x5f53293fe6a27ff9897d4dde445fd6aab46f841ca641befea48beef62014a549
};

export async function runScript(_logger: winston.Logger, baseSigner: Signer): Promise<void> {
logger = _logger;

Expand Down Expand Up @@ -367,14 +371,17 @@ export async function runScript(_logger: winston.Logger, baseSigner: Signer): Pr
logger.debug({
at: "validateRunningBalances#index",
message: "Historical excesses",
expectedExcesses,
excesses,
});
const unexpectedExcess = Object.entries(excesses).some(([, tokenExcesses]) => {
return Object.entries(tokenExcesses).some(([, excesses]) => {
const unexpectedExcess = Object.entries(excesses).some(([chainId, tokenExcesses]) => {
return Object.entries(tokenExcesses).some(([l1Token, excesses]) => {
// We only care about the latest excess, because sometimes excesses can appear in historical bundles
// due to ordering of executing leaves. As long as the excess resets back to 0 eventually it is fine.
const excess = Number(excesses[0]);
return excess > 0.05 || excess < -0.05;
// Subtract any expected excesses
const excessForChain = excess - (expectedExcesses[chainId]?.[l1Token] ?? 0);
return excessForChain > 0.05 || excessForChain < -0.05;
});
});
if (unexpectedExcess) {
Expand Down
Loading

0 comments on commit 55c958b

Please sign in to comment.