Skip to content
Merged
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
10 changes: 5 additions & 5 deletions src/common/Config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import winston from "winston";
import { DEFAULT_MULTICALL_CHUNK_SIZE, DEFAULT_ARWEAVE_GATEWAY } from "../common";
import { ArweaveGatewayInterface, ArweaveGatewayInterfaceSS } from "../interfaces";
import { addressAdapters, AddressAggregator, assert, CHAIN_IDs, isDefined } from "../utils";
import { addressAdapters, AddressAggregator, assert, CHAIN_IDs, isDefined, parseJson } from "../utils";
import * as Constants from "./Constants";

export interface ProcessEnv {
Expand Down Expand Up @@ -79,8 +79,8 @@ export class CommonConfig {
// `maxRelayerLookBack` is how far we fetch events from, modifying the search config's 'fromBlock'
this.maxRelayerLookBack = Number(MAX_RELAYER_DEPOSIT_LOOK_BACK ?? Constants.MAX_RELAYER_DEPOSIT_LOOK_BACK);
this.pollingDelay = Number(POLLING_DELAY ?? 60);
this.spokePoolChainsOverride = JSON.parse(SPOKE_POOL_CHAINS_OVERRIDE ?? "[]");
this.l1TokensOverride = JSON.parse(L1_TOKENS_OVERRIDE ?? "[]");
this.spokePoolChainsOverride = parseJson.numberArray(SPOKE_POOL_CHAINS_OVERRIDE);
this.l1TokensOverride = parseJson.stringArray(L1_TOKENS_OVERRIDE);

// Inherit the default eth_getLogs block range config, then sub in any env-based overrides.
this.maxBlockLookBack = mergeConfig(Constants.CHAIN_MAX_BLOCK_LOOKBACK, MAX_BLOCK_LOOK_BACK);
Expand All @@ -93,9 +93,9 @@ export class CommonConfig {
this.arweaveGateway = _arweaveGateway;

this.peggedTokenPrices = Object.fromEntries(
Object.entries(JSON.parse(PEGGED_TOKEN_PRICES ?? "{}")).map(([pegTokenSymbol, tokenSymbolsToPeg]) => [
Object.entries(parseJson.stringArrayMap(PEGGED_TOKEN_PRICES)).map(([pegTokenSymbol, tokenSymbolsToPeg]) => [
pegTokenSymbol,
new Set(tokenSymbolsToPeg as string[]),
new Set(tokenSymbolsToPeg),
])
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/dataworker/DataworkerConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonConfig, ProcessEnv } from "../common";
import { assert, getArweaveJWKSigner } from "../utils";
import { assert, getArweaveJWKSigner, parseJson } from "../utils";

export class DataworkerConfig extends CommonConfig {
readonly minChallengeLeadTime: number;
Expand Down Expand Up @@ -83,7 +83,7 @@ export class DataworkerConfig extends CommonConfig {
this.proposerEnabled = PROPOSER_ENABLED === "true";
this.l2ExecutorEnabled = L2_EXECUTOR_ENABLED === "true";
this.l1ExecutorEnabled = L1_EXECUTOR_ENABLED === "true";
this.executorIgnoreChains = JSON.parse(EXECUTOR_IGNORE_CHAINS ?? "[]");
this.executorIgnoreChains = parseJson.numberArray(EXECUTOR_IGNORE_CHAINS);
if (this.l2ExecutorEnabled) {
assert(this.spokeRootsLookbackCount > 0, "must set spokeRootsLookbackCount > 0 if L2 executor enabled");
} else if (this.disputerEnabled || this.proposerEnabled) {
Expand Down
4 changes: 2 additions & 2 deletions src/deposit-address/DepositAddressHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DepositAddressHandlerConfig } from "./DepositAddressHandlerConfig";
import {
getRedisCache,
isDefined,
parseJson,
Signer,
scheduleTask,
Provider,
Expand Down Expand Up @@ -134,8 +135,7 @@ export class DepositAddressHandler {
let arr: string[] = [];
try {
if (raw) {
const parsed = JSON.parse(raw);
arr = Array.isArray(parsed) ? parsed : [];
arr = parseJson.stringArray(raw);
}
} catch (err) {
this.logger.error({
Expand Down
3 changes: 2 additions & 1 deletion src/deposit-address/DepositAddressHandlerConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CommonConfig, ProcessEnv } from "../common";
import { parseJson } from "../utils";

export class DepositAddressHandlerConfig extends CommonConfig {
apiEndpoint: string;
Expand Down Expand Up @@ -33,7 +34,7 @@ export class DepositAddressHandlerConfig extends CommonConfig {
throw new Error("SWAP_API_KEY is required (set SWAP_API_KEY in env)");
}

const relayerOriginChains = new Set<number>(JSON.parse(RELAYER_ORIGIN_CHAINS ?? "[]"));
const relayerOriginChains = new Set(parseJson.numberArray(RELAYER_ORIGIN_CHAINS));
this.relayerOriginChains = Array.from(relayerOriginChains);

this.depositLookback = Number(MAX_RELAYER_DEPOSIT_LOOKBACK ?? 3600);
Expand Down
8 changes: 4 additions & 4 deletions src/finalizer/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from "assert";
import { assert as ssAssert, enums } from "superstruct";
import { CommonConfig, FINALIZER_TOKENBRIDGE_LOOKBACK, ProcessEnv } from "../common";
import { Address, EvmAddress, SvmAddress, ethers } from "../utils";
import { Address, EvmAddress, SvmAddress, ethers, parseJson } from "../utils";

/**
* The finalization type is used to determine the direction of the finalization.
Expand All @@ -19,13 +19,13 @@ export class FinalizerConfig extends CommonConfig {
const {
FINALIZER_MAX_TOKENBRIDGE_LOOKBACK,
FINALIZER_CHAINS = "[]",
FINALIZER_WITHDRAWAL_TO_ADDRESSES = "[]",
FINALIZER_WITHDRAWAL_TO_ADDRESSES = "{}",
FINALIZATION_STRATEGY = "l1<->l2",
FINALIZER_CHUNK_SIZE = 3,
} = env;
super(env);

const userAddresses: { [address: string]: string[] } = JSON.parse(FINALIZER_WITHDRAWAL_TO_ADDRESSES);
const userAddresses = parseJson.stringArrayMap(FINALIZER_WITHDRAWAL_TO_ADDRESSES);
this.userAddresses = new Map();
Object.entries(userAddresses).forEach(([address, tokensToFinalize]) => {
if (ethers.utils.isHexString(address)) {
Expand All @@ -35,7 +35,7 @@ export class FinalizerConfig extends CommonConfig {
}
});

this.chainsToFinalize = JSON.parse(FINALIZER_CHAINS);
this.chainsToFinalize = parseJson.numberArray(FINALIZER_CHAINS);

// `maxFinalizerLookback` is how far we fetch events from, modifying the search config's 'fromBlock'
this.maxFinalizerLookback = Number(FINALIZER_MAX_TOKENBRIDGE_LOOKBACK ?? FINALIZER_TOKENBRIDGE_LOOKBACK);
Expand Down
15 changes: 9 additions & 6 deletions src/gasless/GaslessRelayerConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import assert from "assert";
import { CommonConfig, ProcessEnv } from "../common";
import { isDefined, parseJson } from "../utils";

/**
* Allowed pegged token pairs for gasless deposits/fills. Same shape as PEGGED_TOKEN_PRICES:
Expand Down Expand Up @@ -42,24 +44,25 @@ export class GaslessRelayerConfig extends CommonConfig {
this.apiPollingInterval = Number(API_POLLING_INTERVAL ?? 1); // Default to 1s
this.apiEndpoint = String(API_GASLESS_ENDPOINT);

const relayerOriginChains = new Set<number>(JSON.parse(RELAYER_ORIGIN_CHAINS ?? "[]"));
const relayerOriginChains = new Set(parseJson.numberArray(RELAYER_ORIGIN_CHAINS));
this.relayerOriginChains = Array.from(relayerOriginChains);
const relayerDestinationChains = new Set<number>(JSON.parse(RELAYER_DESTINATION_CHAINS ?? "[]"));
const relayerDestinationChains = new Set(parseJson.numberArray(RELAYER_DESTINATION_CHAINS));
this.relayerDestinationChains = Array.from(relayerDestinationChains);

this.relayerTokenSymbols = JSON.parse(RELAYER_TOKEN_SYMBOLS); // Relayer token symbols must be defined.
assert(isDefined(RELAYER_TOKEN_SYMBOLS), "RELAYER_TOKEN_SYMBOLS must be defined");
this.relayerTokenSymbols = parseJson.stringArray(RELAYER_TOKEN_SYMBOLS);
this.depositLookback = Number(MAX_RELAYER_DEPOSIT_LOOKBACK ?? 3600);

this.apiTimeoutOverride = Number(API_TIMEOUT_OVERRIDE ?? 3000); // In ms
this.initializationRetryAttempts = Number(INITIALIZATION_RETRY_ATTEMPTS ?? 3);
this.refundFlowTestEnabled = String(RELAYER_GASLESS_REFUND_FLOW_TEST_ENABLED ?? "").toLowerCase() === "true";

this.spokePoolPeripheryOverrides = JSON.parse(SPOKE_POOL_PERIPHERY_OVERRIDES ?? "{}");
this.spokePoolPeripheryOverrides = parseJson.stringMap(SPOKE_POOL_PERIPHERY_OVERRIDES);

this.allowedPeggedPairs = Object.fromEntries(
Object.entries(JSON.parse(GASLESS_ALLOWED_PEGGED_PAIRS ?? "{}")).map(([inputSymbol, outputSymbols]) => [
Object.entries(parseJson.stringArrayMap(GASLESS_ALLOWED_PEGGED_PAIRS)).map(([inputSymbol, outputSymbols]) => [
inputSymbol,
new Set(outputSymbols as string[]),
new Set(outputSymbols),
])
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/hyperliquid/HyperliquidExecutorConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { toBNWei, BigNumber } from "../utils";
import { toBNWei, BigNumber, parseJson } from "../utils";
import { CommonConfig, ProcessEnv } from "../common";

export class HyperliquidExecutorConfig extends CommonConfig {
Expand All @@ -20,13 +20,13 @@ export class HyperliquidExecutorConfig extends CommonConfig {
HYPERLIQUID_SETTLEMENT_INTERVAL = 5, // blocks
MAX_SLIPPAGE_BY_ROUTE_BPS,
} = env;
this.supportedTokens = JSON.parse(HYPERLIQUID_SUPPORTED_TOKENS ?? "[]");
this.supportedTokens = parseJson.stringArray(HYPERLIQUID_SUPPORTED_TOKENS);
this.lookback = Number(HL_DEPOSIT_LOOKBACK ?? 3600);
this.reviewInterval = Number(HYPERLIQUID_REPLACE_ORDER_BLOCK_TIMEOUT ?? 20);
this.maxSlippageBps = toBNWei(Number(MAX_SLIPPAGE_BPS ?? 5), 4); // With 8 decimal precision, a basis point is 10000. Default to 5bps.
this.settlementInterval = Number(HYPERLIQUID_SETTLEMENT_INTERVAL);

const maxSlippageByRoute = JSON.parse(MAX_SLIPPAGE_BY_ROUTE_BPS ?? "{}");
const maxSlippageByRoute = parseJson.numericMap(MAX_SLIPPAGE_BY_ROUTE_BPS);
Object.entries(maxSlippageByRoute).forEach(([pairName, value]) => {
this.maxSlippageByRoute[pairName] = toBNWei(Number(value), 4);
});
Expand Down
22 changes: 12 additions & 10 deletions src/monitor/MonitorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
TOKEN_SYMBOLS_MAP,
Address,
toAddressType,
parseJson,
} from "../utils";

// Set modes to true that you want to enable in the AcrossMonitor bot.
Expand Down Expand Up @@ -104,19 +105,18 @@ export class MonitorConfig extends CommonConfig {
this.whitelistedRelayers = parseAddressesOptional(WHITELISTED_RELAYERS);
this.monitoredRelayers = parseAddressesOptional(MONITORED_RELAYERS);
this.knownV1Addresses = parseAddressesOptional(KNOWN_V1_ADDRESSES);
this.monitoredSpokePoolChains = JSON.parse(MONITORED_SPOKE_POOL_CHAINS ?? "[]");
this.monitoredTokenSymbols = JSON.parse(MONITORED_TOKEN_SYMBOLS ?? "[]");
this.monitoredSpokePoolChains = parseJson.numberArray(MONITORED_SPOKE_POOL_CHAINS);
this.monitoredTokenSymbols = parseJson.stringArray(MONITORED_TOKEN_SYMBOLS);
this.bundlesCount = Number(BUNDLES_COUNT ?? 4);
this.additionalL1NonLpTokens = JSON.parse(MONITOR_REPORT_NON_LP_TOKENS ?? "[]").map((token) => {
if (TOKEN_SYMBOLS_MAP[token]?.addresses?.[CHAIN_IDs.MAINNET]) {
return TOKEN_SYMBOLS_MAP[token]?.addresses?.[CHAIN_IDs.MAINNET];
}
});
this.additionalL1NonLpTokens = parseJson
.stringArray(MONITOR_REPORT_NON_LP_TOKENS)
.filter((token) => TOKEN_SYMBOLS_MAP[token]?.addresses[CHAIN_IDs.MAINNET])
.map((token) => TOKEN_SYMBOLS_MAP[token].addresses[CHAIN_IDs.MAINNET]);

this.binanceWithdrawWarnThreshold = Number(BINANCE_WITHDRAW_WARN_THRESHOLD ?? 1);
this.binanceWithdrawAlertThreshold = Number(BINANCE_WITHDRAW_ALERT_THRESHOLD ?? 1);

this.hyperliquidTokens = JSON.parse(HYPERLIQUID_SUPPORTED_TOKENS ?? '["USDC", "USDT0", "USDH"]');
this.hyperliquidTokens = parseJson.stringArray(HYPERLIQUID_SUPPORTED_TOKENS ?? '["USDC", "USDT0", "USDH"]');
this.hyperliquidOrderMaximumLifetime = Number(HYPERLIQUID_ORDER_MAXIMUM_LIFETIME ?? -1);

// Default pool utilization threshold at 90%.
Expand Down Expand Up @@ -183,8 +183,10 @@ export class MonitorConfig extends CommonConfig {
}

const parseAddressesOptional = (addressJson?: string): Address[] => {
const rawAddresses: string[] = addressJson ? JSON.parse(addressJson) : [];
return rawAddresses.map((address) => {
if (!addressJson) {
return [];
}
return parseJson.stringArray(addressJson).map((address) => {
const chainId = address.startsWith("0x") ? CHAIN_IDs.MAINNET : CHAIN_IDs.SOLANA;
return toAddressType(address, chainId);
});
Expand Down
17 changes: 8 additions & 9 deletions src/relayer/RelayerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
Address,
toAddressType,
EvmAddress,
parseJson,
} from "../utils";
import { CommonConfig, ProcessEnv } from "../common";
import * as Constants from "../common/Constants";
Expand Down Expand Up @@ -107,32 +108,30 @@ export class RelayerConfig extends CommonConfig {
this.eventListener = this.externalListener && RELAYER_EVENT_LISTENER === "true";

// Empty means all chains.
this.relayerOriginChains = JSON.parse(RELAYER_ORIGIN_CHAINS ?? "[]");
this.relayerDestinationChains = JSON.parse(RELAYER_DESTINATION_CHAINS ?? "[]");
this.relayerOriginChains = parseJson.numberArray(RELAYER_ORIGIN_CHAINS);
this.relayerDestinationChains = parseJson.numberArray(RELAYER_DESTINATION_CHAINS);

// Empty means all tokens.
this.relayerTokens = JSON.parse(RELAYER_TOKENS ?? "[]").map((token) =>
toAddressType(ethers.utils.getAddress(token), CHAIN_IDs.MAINNET)
);
this.relayerTokens = parseJson.stringArray(RELAYER_TOKENS).map((token) => EvmAddress.from(token));
// An empty array for a defined destination chain means that all tokens are supported. To support no tokens
// for a destination chain, map the chain to an empty array. For example, to fill only token A on chain C
// and fill nothing on chain D, set relayerDestinationTokens: { C: [A], D: [] }
this.relayerDestinationTokens = Object.fromEntries(
Object.entries(JSON.parse(RELAYER_DESTINATION_TOKENS ?? "{}")).map(([_chainId, tokens]) => {
Object.entries(parseJson.stringArrayMap(RELAYER_DESTINATION_TOKENS)).map(([_chainId, tokens]) => {
const chainId = Number(_chainId);
return [chainId, ((tokens as string[]) ?? []).map((token) => toAddressType(token, Number(chainId)))];
return [chainId, tokens.map((token) => toAddressType(token, chainId))];
})
);

// SLOW_DEPOSITORS can exist on any network, so their origin network must be inferred based on the structure of the address.
this.slowDepositors = JSON.parse(SLOW_DEPOSITORS ?? "[]").map((depositor) => {
this.slowDepositors = parseJson.stringArray(SLOW_DEPOSITORS).map((depositor) => {
const chainId = ethers.utils.isHexString(depositor) ? CHAIN_IDs.MAINNET : CHAIN_IDs.SOLANA;
return toAddressType(depositor, chainId);
});

this.minRelayerFeePct = toBNWei(MIN_RELAYER_FEE_PCT || Constants.RELAYER_MIN_FEE_PCT);

this.tryMulticallChains = JSON.parse(RELAYER_TRY_MULTICALL_CHAINS ?? "[]");
this.tryMulticallChains = parseJson.numberArray(RELAYER_TRY_MULTICALL_CHAINS);
this.loggingInterval = Number(RELAYER_LOGGING_INTERVAL);
this.maintenanceInterval = Number(RELAYER_MAINTENANCE_INTERVAL);

Expand Down
4 changes: 2 additions & 2 deletions src/utils/SignerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { readFile } from "fs/promises";
import { constants as ethersConsts, VoidSigner } from "ethers";
import minimist from "minimist";
import { typeguards } from "@across-protocol/sdk";
import { Signer, Wallet, retrieveGckmsKeys, getGckmsConfig, isDefined, assert, mapAsync } from "./";
import { Signer, Wallet, retrieveGckmsKeys, getGckmsConfig, isDefined, assert, mapAsync, parseJson } from "./";
import { ArweaveWalletJWKInterface, ArweaveWalletJWKInterfaceSS } from "../interfaces";

/**
Expand Down Expand Up @@ -183,7 +183,7 @@ export async function getDispatcherKeys(): Promise<Signer[]> {
const args = minimist(process.argv.slice(2), opts);
if (!isDefined(args.dispatcherKeys)) {
// If GCKMS keys are undefined. Assume keys are in environment.
const keys = JSON.parse(process.env["DISPATCHER_KEYS"] ?? "[]");
const keys = parseJson.stringArray(process.env.DISPATCHER_KEYS);
return keys.map((key) => new Wallet(key));
}
const dispatcherKeyNames = args.dispatcherKeys.split(",");
Expand Down
40 changes: 40 additions & 0 deletions src/utils/TypeGuards.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,47 @@
import { array, create, number, record, string, union } from "superstruct";
import { utils } from "@across-protocol/sdk";

export const { isDefined, isPromiseFulfilled, isPromiseRejected } = utils;

/**
* Typed JSON.parse helpers. Validates the parsed result against a superstruct schema
* and returns the correctly-typed value. Throws on validation failure.
*
* Usage:
* parseJson.stringArray(env.FOO)
* parseJson.numberArray(env.BAR)
* parseJson.stringArrayMap(env.BAZ)
* parseJson.stringMap(env.QUX)
* parseJson.numberMap(env.QUUX)
* parseJson.numericMap(env.CORGE)
*/
export const parseJson = {
/** Parse a JSON string expected to contain a string[]. */
stringArray(json = "[]"): string[] {
return create(JSON.parse(json), array(string()));
},
/** Parse a JSON string expected to contain a number[]. */
numberArray(json = "[]"): number[] {
return create(JSON.parse(json), array(number()));
},
/** Parse a JSON string expected to contain a Record<string, string>. */
stringMap(json = "{}"): Record<string, string> {
return create(JSON.parse(json), record(string(), string()));
},
/** Parse a JSON string expected to contain a Record<string, number>. */
numberMap(json = "{}"): Record<string, number> {
return create(JSON.parse(json), record(string(), number()));
},
/** Parse a JSON string expected to contain a Record<string, string | number>. */
numericMap(json = "{}"): Record<string, string | number> {
return create(JSON.parse(json), record(string(), union([string(), number()])));
},
/** Parse a JSON string expected to contain a Record<string, string[]>. */
stringArrayMap(json = "{}"): Record<string, string[]> {
return create(JSON.parse(json), record(string(), array(string())));
},
};

// This function allows you to test for the key type in an object literal.
// For instance, this would compile in typescript strict:
// const myObj = { a: 1, b: 2, c: 3 } as const;
Expand Down
Loading
Loading