Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into pxrl/experimentalGas
Browse files Browse the repository at this point in the history
  • Loading branch information
pxrl committed Nov 13, 2024
2 parents a278a95 + 952bb0c commit 55c449d
Show file tree
Hide file tree
Showing 21 changed files with 632 additions and 273 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@across-protocol/sdk",
"author": "UMA Team",
"version": "3.2.9",
"version": "3.2.13",
"license": "AGPL-3.0",
"homepage": "https://docs.across.to/reference/sdk",
"files": [
Expand Down Expand Up @@ -99,8 +99,8 @@
},
"dependencies": {
"@across-protocol/across-token": "^1.0.0",
"@across-protocol/constants": "^3.1.16",
"@across-protocol/contracts": "^3.0.11",
"@across-protocol/constants": "^3.1.19",
"@across-protocol/contracts": "^3.0.16",
"@eth-optimism/sdk": "^3.3.1",
"@ethersproject/bignumber": "^5.7.0",
"@pinata/sdk": "^2.1.0",
Expand Down
5 changes: 4 additions & 1 deletion src/clients/BundleDataClient/BundleDataClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,10 @@ export class BundleDataClient {
];
// Sanity checks:
assert(endTime >= startTime, "End time should be greater than start time.");
assert(startTime > 0, "Start time should be greater than 0.");
assert(
startBlockForChain === 0 || startTime > 0,
"Start timestamp must be greater than 0 if the start block is greater than 0."
);
return [chainId, [startTime, endTime]];
})
).filter(isDefined)
Expand Down
2 changes: 1 addition & 1 deletion src/clients/BundleDataClient/utils/DataworkerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function _buildPoolRebalanceRoot(
// Realized LP fees are keyed the same as running balances and represent the amount of LP fees that should be paid
// to LP's for each running balance.

// For each FilledRelay group, identified by { repaymentChainId, L1TokenAddress }, initialize a "running balance"
// For each FilledV3Relay group, identified by { repaymentChainId, L1TokenAddress }, initialize a "running balance"
// to the total refund amount for that group.
const runningBalances: RunningBalances = {};
const realizedLpFees: RunningBalances = {};
Expand Down
2 changes: 1 addition & 1 deletion src/clients/SpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ export class SpokePoolClient extends BaseAbstractClient {
* @note // We want to find the block range that satisfies these conditions:
* // - the low block has deposit count <= targetDepositId
* // - the high block has a deposit count > targetDepositId.
* // This way the caller can search for a FundsDeposited event between [low, high] that will always
* // This way the caller can search for a V3FundsDeposited event between [low, high] that will always
* // contain the event emitted when deposit ID was incremented to targetDepositId + 1. This is the same transaction
* // where the deposit with deposit ID = targetDepositId was created.
*/
Expand Down
2 changes: 0 additions & 2 deletions src/clients/mocks/MockSpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,6 @@ export class MockSpokePoolClient extends SpokePoolClient {
// Event signatures. Not strictly required, but they make generated events more recognisable.
public readonly eventSignatures: Record<string, string> = {
EnabledDepositRoute: "address,uint256,bool",
FilledRelay: "uint256,uint256,uint256,int64,uint32,uint32,address,address,address,bytes",
FundsDeposited: "uint256,uint256,uint256,int64,uint32,uint32,address,address,address,bytes",
};

depositV3(deposit: DepositWithBlock): Log {
Expand Down
2 changes: 2 additions & 0 deletions src/gasPriceOracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async function getEthersGasPriceEstimate(
}

const gasPriceFeeds = {
[CHAIN_IDs.ALEPH_ZERO]: arbitrum.eip1559,
[CHAIN_IDs.ARBITRUM]: arbitrum.eip1559,
[CHAIN_IDs.BASE]: ethereum.eip1559,
[CHAIN_IDs.LINEA]: linea.eip1559, // @todo: Support linea_estimateGas in adapter.
Expand Down Expand Up @@ -88,6 +89,7 @@ export async function getViemGasPriceEstimate(
const viemProvider = getPublicClient(chainId, transport);

const gasPriceFeeds = {
[CHAIN_IDs.ALEPH_ZERO]: arbitrumViem.eip1559,
[CHAIN_IDs.ARBITRUM]: arbitrumViem.eip1559,
[CHAIN_IDs.POLYGON]: polygonViem.gasStation,
} as const;
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/SpokePool.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { SortableEvent } from "./Common";
import { FilledRelayEvent, FilledV3RelayEvent, FundsDepositedEvent, V3FundsDepositedEvent } from "../typechain";
import { FilledV3RelayEvent, V3FundsDepositedEvent } from "../typechain";
import { SpokePoolClient } from "../clients";
import { BigNumber } from "../utils";
import { RelayerRefundLeaf } from "./HubPool";

export type { FilledRelayEvent, FilledV3RelayEvent, FundsDepositedEvent, V3FundsDepositedEvent };
export type { FilledV3RelayEvent, V3FundsDepositedEvent };

export interface RelayData {
originChainId: number;
Expand Down
1 change: 1 addition & 0 deletions src/providers/drpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RPCTransport } from "./types";

// Chain-specific overrides for when the endpoint name does not match the canonical chain name.
const endpoints: { [chainId: string]: string } = {
[CHAIN_IDs.ALEPH_ZERO]: "alephzero",
[CHAIN_IDs.ARBITRUM]: "arbitrum",
[CHAIN_IDs.MAINNET]: "ethereum",
};
Expand Down
53 changes: 46 additions & 7 deletions src/providers/retryProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getOriginFromURL } from "../utils/NetworkUtils";
import { CacheProvider } from "./cachedProvider";
import { compareRpcResults, createSendErrorWithMessage, formatProviderError } from "./utils";
import { PROVIDER_CACHE_TTL } from "./constants";
import { JsonRpcError, RpcError } from "./types";
import { Logger } from "winston";

export class RetryProvider extends ethers.providers.StaticJsonRpcProvider {
Expand Down Expand Up @@ -88,16 +89,22 @@ export class RetryProvider extends ethers.providers.StaticJsonRpcProvider {
): Promise<[ethers.providers.StaticJsonRpcProvider, unknown]> => {
return this._trySend(provider, method, params)
.then((result): [ethers.providers.StaticJsonRpcProvider, unknown] => [provider, result])
.catch((err) => {
.catch((err: unknown) => {
// Append the provider and error to the error array.
errors.push([provider, err?.stack || err?.toString()]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
errors.push([provider, (err as any)?.stack || err?.toString()]);

// If there are no new fallback providers to use, terminate the recursion by throwing an error.
// Otherwise, we can try to call another provider.
if (fallbackProviders.length === 0) {
throw err;
}

// If one RPC provider reverted, others likely will too. Skip them.
if (quorumThreshold === 1 && this.callReverted(method, err)) {
throw err;
}

// This line does two things:
// 1. Removes a fallback provider from the array so it cannot be used as a fallback for another required
// provider.
Expand Down Expand Up @@ -260,12 +267,44 @@ export class RetryProvider extends ethers.providers.StaticJsonRpcProvider {
return response;
}

_trySend(provider: ethers.providers.StaticJsonRpcProvider, method: string, params: Array<unknown>): Promise<unknown> {
let promise = this._sendAndValidate(provider, method, params);
for (let i = 0; i < this.retries; i++) {
promise = promise.catch(() => delay(this.delay).then(() => this._sendAndValidate(provider, method, params)));
// For an error emitted in response to an eth_call or eth_estimateGas request, determine whether the response body
// indicates that the call reverted during execution. The exact RPC responses returned can vary, but `error.body` has
// reliably included both the code (typically 3 on revert) and the error message indicating "execution reverted".
// This is consistent with section 5.1 of the JSON-RPC spec (https://www.jsonrpc.org/specification).
protected callReverted(method: string, error: unknown): boolean {
if (!(method === "eth_call" || method === "eth_estimateGas") || !RpcError.is(error)) {
return false;
}

let response: unknown;
try {
response = JSON.parse(error.body);
} catch {
return false;
}

return JsonRpcError.is(response) && response.error.message.toLowerCase().includes("revert");
}

async _trySend(
provider: ethers.providers.StaticJsonRpcProvider,
method: string,
params: Array<unknown>
): Promise<unknown> {
let { retries } = this;

// eslint-disable-next-line no-constant-condition
while (true) {
const [settled] = await Promise.allSettled([this._sendAndValidate(provider, method, params)]);
if (settled.status === "fulfilled") {
return settled.value;
}

if (retries-- <= 0 || this.callReverted(method, settled.reason)) {
throw settled.reason;
}
await delay(this.delay);
}
return promise;
}

_getQuorum(method: string, params: Array<unknown>): number {
Expand Down
21 changes: 21 additions & 0 deletions src/providers/types.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,23 @@
import { any, literal, nullable, number, string, type, union } from "superstruct";

export type RPCProvider = "ALCHEMY" | "DRPC" | "INFURA" | "INFURA_DIN";
export type RPCTransport = "https" | "wss";

// JSON-RPC 2.0 Error object
// See JSON-RPC 2.0 Specification section 5 Reponse object
// https://www.jsonrpc.org/specification
export const JsonRpcError = type({
jsonrpc: literal("2.0"),
id: union([number(), string()]),
error: type({
code: number(),
message: string(),
data: nullable(any()),
}),
});

// Generic/unknown RPC error (may embed a JsonRpcError).
export const RpcError = type({
reason: string(),
body: string(),
});
48 changes: 48 additions & 0 deletions src/relayFeeCalculator/chain-queries/alephZero.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import assert from "assert";
import { getDeployedAddress } from "../../utils/DeploymentUtils";
import { DEFAULT_LOGGER, Logger } from "../relayFeeCalculator";
import { providers } from "ethers";
import { CHAIN_IDs, DEFAULT_SIMULATED_RELAYER_ADDRESS, TOKEN_SYMBOLS_MAP } from "../../constants";
import { Coingecko } from "../../coingecko/Coingecko";
import { isDefined } from "../../utils";
import { QueryBase } from "./baseQuery";

export class AlephZeroQueries extends QueryBase {
constructor(
provider: providers.Provider,
symbolMapping = TOKEN_SYMBOLS_MAP,
spokePoolAddress = getDeployedAddress("SpokePool", CHAIN_IDs.ALEPH_ZERO),
simulatedRelayerAddress = DEFAULT_SIMULATED_RELAYER_ADDRESS,
coingeckoProApiKey?: string,
logger: Logger = DEFAULT_LOGGER,
gasMarkup = 0
) {
assert(isDefined(spokePoolAddress));
super(
provider,
symbolMapping,
spokePoolAddress,
simulatedRelayerAddress,
gasMarkup,
logger,
coingeckoProApiKey,
undefined,
"usd"
);
}

override async getTokenPrice(tokenSymbol: string): Promise<number> {
if (!this.symbolMapping[tokenSymbol]) throw new Error(`${tokenSymbol} does not exist in mapping`);
const coingeckoInstance = Coingecko.get(this.logger, this.coingeckoProApiKey);
const [, tokenPrice] = await coingeckoInstance.getCurrentPriceByContract(
this.symbolMapping[tokenSymbol].addresses[CHAIN_IDs.MAINNET],
"usd"
);

const [, alephZeroPrice] = await coingeckoInstance.getCurrentPriceByContract(
this.symbolMapping["AZERO"].addresses[CHAIN_IDs.MAINNET],
"usd"
);
return Number((tokenPrice / alephZeroPrice).toFixed(this.symbolMapping["AZERO"].decimals));
}
}
17 changes: 15 additions & 2 deletions src/relayFeeCalculator/chain-queries/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { getDeployedAddress } from "@across-protocol/contracts";
import { asL2Provider } from "@eth-optimism/sdk";
import { providers } from "ethers";
import { DEFAULT_SIMULATED_RELAYER_ADDRESS } from "../../constants";
import { chainIsMatic, chainIsOPStack, isDefined } from "../../utils";
import { chainIsAlephZero, chainIsMatic, chainIsOPStack, isDefined } from "../../utils";
import { QueryBase } from "./baseQuery";
import { PolygonQueries } from "./polygon";
import { DEFAULT_LOGGER, Logger } from "../relayFeeCalculator";
import { AlephZeroQueries } from "./alephZero";

/**
* Some chains have a fixed gas price that is applied to the gas estimates. We should override
Expand All @@ -31,7 +32,6 @@ export class QueryBase__factory {
): QueryBase {
assert(isDefined(spokePoolAddress));

// Currently the only chain that has a custom query class is Polygon
if (chainIsMatic(chainId)) {
return new PolygonQueries(
provider,
Expand All @@ -43,6 +43,19 @@ export class QueryBase__factory {
gasMarkup
);
}

if (chainIsAlephZero(chainId)) {
return new AlephZeroQueries(
provider,
symbolMapping,
spokePoolAddress,
simulatedRelayerAddress,
coingeckoProApiKey,
logger,
gasMarkup
);
}

// For OPStack chains, we need to wrap the provider in an L2Provider
provider = chainIsOPStack(chainId) ? asL2Provider(provider) : provider;

Expand Down
3 changes: 0 additions & 3 deletions src/typechain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ export type { HubPool, HubPoolInterface } from "@across-protocol/contracts/dist/
export type {
SpokePool,
SpokePoolInterface,
FundsDepositedEvent,
FilledRelayEvent,
RequestedSpeedUpDepositEvent,
V3FundsDepositedEvent,
FilledV3RelayEvent,
} from "@across-protocol/contracts/dist/typechain/contracts/SpokePool";
Expand Down
6 changes: 1 addition & 5 deletions src/utils/BlockUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ export async function averageBlockTime(
const { chainId } = await provider.getNetwork();

// OP stack chains inherit Optimism block times, but can be overridden.
// prettier-ignore
const cache = blockTimes[chainId]
?? chainIsOPStack(chainId)
? blockTimes[CHAIN_IDs.OPTIMISM]
: undefined;
const cache = blockTimes[chainId] ?? (chainIsOPStack(chainId) ? blockTimes[CHAIN_IDs.OPTIMISM] : undefined);

const now = getCurrentTime();
if (isDefined(cache) && now < cache.timestamp + cacheTTL) {
Expand Down
1 change: 1 addition & 0 deletions src/utils/Multicall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type Call3 = {
const DETERMINISTIC_MULTICALL_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";

const NON_DETERMINISTIC_MULTICALL_ADDRESSES = {
[CHAIN_IDs.ALEPH_ZERO]: "0x3CA11702f7c0F28e0b4e03C31F7492969862C569",
[CHAIN_IDs.ZK_SYNC]: "0xF9cda624FBC7e059355ce98a31693d299FACd963",
};

Expand Down
20 changes: 19 additions & 1 deletion src/utils/NetworkUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,16 @@ export function chainIsMatic(chainId: number): boolean {
* @returns True if chainId is an OP stack, otherwise false.
*/
export function chainIsOPStack(chainId: number): boolean {
return PUBLIC_NETWORKS[chainId]?.family === ChainFamily.OP_STACK ?? false;
return PUBLIC_NETWORKS[chainId]?.family === ChainFamily.OP_STACK;
}

/**
* Determines whether a chain ID is an Arbitrum Orbit implementation.
* @param chainId Chain ID to evaluate.
* @returns True if chainId is an Orbit chain, otherwise false.
*/
export function chainIsOrbit(chainId: number): boolean {
return PUBLIC_NETWORKS[chainId]?.family === ChainFamily.ORBIT;
}

/**
Expand All @@ -69,6 +78,15 @@ export function chainIsArbitrum(chainId: number): boolean {
return [CHAIN_IDs.ARBITRUM, CHAIN_IDs.ARBITRUM_SEPOLIA].includes(chainId);
}

/**
* Determines whether a chain ID is an Aleph0 implementation
* @param chainId Chain ID to evaluate
* @returns True if chainId is an Aleph0 chain, otherwise false.
*/
export function chainIsAlephZero(chainId: number): boolean {
return [CHAIN_IDs.ALEPH_ZERO].includes(chainId);
}

/**
* Determines whether a chain ID is a Linea implementation.
* @param chainId Chain ID to evaluate.
Expand Down
2 changes: 1 addition & 1 deletion src/utils/SpokeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function populateV3Relay(
* @note // We want to find the block range that satisfies these conditions:
* // - the low block has deposit count <= targetDepositId
* // - the high block has a deposit count > targetDepositId.
* // This way the caller can search for a FundsDeposited event between [low, high] that will always
* // This way the caller can search for a V3FundsDeposited event between [low, high] that will always
* // contain the event emitted when deposit ID was incremented to targetDepositId + 1. This is the same transaction
* // where the deposit with deposit ID = targetDepositId was created.
*/
Expand Down
2 changes: 1 addition & 1 deletion test/SpokePoolClient.fills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ describe("SpokePoolClient: Fills", function () {
expect(fillBlock).to.equal(targetFillBlock);
});

it("FilledRelay block search: bounds checking", async function () {
it("FilledV3Relay block search: bounds checking", async function () {
const nBlocks = 100;
const startBlock = await spokePool.provider.getBlockNumber();
for (let i = 0; i < nBlocks; ++i) {
Expand Down
4 changes: 2 additions & 2 deletions test/SpokePoolClient.v3Events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {
type EventSearchConfig = sdkUtils.EventSearchConfig;

describe("SpokePoolClient: Event Filtering", function () {
const fundsDepositedEvents = ["FundsDeposited", "V3FundsDeposited"];
const fundsDepositedEvents = ["V3FundsDeposited"];
const slowFillRequestedEvents = ["RequestedV3SlowFill"];
const filledRelayEvents = ["FilledRelay", "FilledV3Relay"];
const filledRelayEvents = ["FilledV3Relay"];

let owner: SignerWithAddress;
let chainIds: number[];
Expand Down
Loading

0 comments on commit 55c449d

Please sign in to comment.