Skip to content

Commit

Permalink
Merge branch 'main' into xrsv/sor-unichain-mainnet-support
Browse files Browse the repository at this point in the history
  • Loading branch information
xrsv authored Dec 18, 2024
2 parents 696ef90 + 6084c42 commit d895a5a
Show file tree
Hide file tree
Showing 14 changed files with 405 additions and 12 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@uniswap/smart-order-router",
"version": "4.8.1",
"version": "4.9.2",
"description": "Uniswap Smart Order Router",
"main": "build/main/index.js",
"typings": "build/main/index.d.ts",
Expand Down
4 changes: 4 additions & 0 deletions src/providers/eth-estimate-gas-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { BEACON_CHAIN_DEPOSIT_ADDRESS, log } from '../util';
import {
calculateGasUsed,
initSwapRouteFromExisting,
logGasEstimationVsSimulationMetrics,
} from '../util/gas-factory-helpers';

import { IPortionProvider } from './portion-provider';
Expand Down Expand Up @@ -125,6 +126,9 @@ export class EthEstimateGasSimulator extends Simulator {
this.provider,
providerConfig
);

logGasEstimationVsSimulationMetrics(route, estimatedGasUsed, this.chainId);

return {
...initSwapRouteFromExisting(
route,
Expand Down
1 change: 1 addition & 0 deletions src/providers/simulation-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export enum SimulationStatus {
InsufficientBalance = 3,
NotApproved = 4,
SystemDown = 5,
SlippageTooLow = 6,
}

/**
Expand Down
48 changes: 45 additions & 3 deletions src/providers/tenderly-simulation-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import { APPROVE_TOKEN_FOR_TRANSFER } from '../util/callData';
import {
calculateGasUsed,
initSwapRouteFromExisting,
logGasEstimationVsSimulationMetrics,
} from '../util/gas-factory-helpers';

import { breakDownTenderlySimulationError } from '../util/tenderlySimulationErrorBreakDown';
import { EthEstimateGasSimulator } from './eth-estimate-gas-provider';
import { IPortionProvider } from './portion-provider';
import {
Expand Down Expand Up @@ -138,6 +140,18 @@ const TENDERLY_NODE_API = (chainId: ChainId, tenderlyNodeApiKey: string) => {
return `https://mainnet.gateway.tenderly.co/${tenderlyNodeApiKey}`;
case ChainId.BASE:
return `https://base.gateway.tenderly.co/${tenderlyNodeApiKey}`;
case ChainId.ARBITRUM_ONE:
return `https://arbitrum.gateway.tenderly.co/${tenderlyNodeApiKey}`;
case ChainId.OPTIMISM:
return `https://optimism.gateway.tenderly.co/${tenderlyNodeApiKey}`;
case ChainId.POLYGON:
return `https://polygon.gateway.tenderly.co/${tenderlyNodeApiKey}`;
case ChainId.AVALANCHE:
return `https://avalanche.gateway.tenderly.co/${tenderlyNodeApiKey}`;
case ChainId.BLAST:
return `https://blast.gateway.tenderly.co/${tenderlyNodeApiKey}`;
case ChainId.WORLDCHAIN:
return `https://worldchain-mainnet.gateway.tenderly.co/${tenderlyNodeApiKey}`;
default:
throw new Error(
`ChainId ${chainId} does not correspond to a tenderly node endpoint`
Expand All @@ -149,6 +163,9 @@ export const TENDERLY_NOT_SUPPORTED_CHAINS = [
ChainId.CELO,
ChainId.CELO_ALFAJORES,
ChainId.ZKSYNC,
// tenderly node RPC supports BNB and ZORA upon request, we will make them available
ChainId.BNB,
ChainId.ZORA,
];

// We multiply tenderly gas limit by this to overestimate gas limit
Expand Down Expand Up @@ -290,6 +307,8 @@ export class TenderlySimulator extends Simulator {
): Promise<SwapRoute> {
const currencyIn = swapRoute.trade.inputAmount.currency;
const tokenIn = currencyIn.wrapped;
const currencyOut = swapRoute.trade.outputAmount.currency;
const tokenOut = currencyOut.wrapped;
const chainId = this.chainId;

if (TENDERLY_NOT_SUPPORTED_CHAINS.includes(chainId)) {
Expand Down Expand Up @@ -464,6 +483,24 @@ export class TenderlySimulator extends Simulator {
2
)}.`
);

if (
resp &&
resp.result &&
resp.result.length >= 3 &&
(resp.result[2] as JsonRpcError).error &&
(resp.result[2] as JsonRpcError).error.data
) {
return {
...swapRoute,
simulationStatus: breakDownTenderlySimulationError(
tokenIn,
tokenOut,
(resp.result[2] as JsonRpcError).error.data
),
};
}

return { ...swapRoute, simulationStatus: SimulationStatus.Failed };
}

Expand Down Expand Up @@ -690,6 +727,9 @@ export class TenderlySimulator extends Simulator {
this.provider,
providerConfig
);

logGasEstimationVsSimulationMetrics(swapRoute, estimatedGasUsed, chainId);

return {
...initSwapRouteFromExisting(
swapRoute,
Expand Down Expand Up @@ -781,9 +821,11 @@ export class TenderlySimulator extends Simulator {
this.chainId,
this.tenderlyNodeApiKey
);
const blockNumber = swap.block_number
? BigNumber.from(swap.block_number).toHexString().replace('0x0', '0x')
: 'latest';
// TODO: ROUTE-362 - Revisit tenderly node simulation hardcode latest block number
// https://linear.app/uniswap/issue/ROUTE-362/revisit-tenderly-node-simulation-hardcode-latest-block-number
const blockNumber = // swap.block_number
// ? BigNumber.from(swap.block_number).toHexString().replace('0x0', '0x')
'latest';
const body: TenderlyNodeEstimateGasBundleBody = {
id: 1,
jsonrpc: '2.0',
Expand Down
7 changes: 7 additions & 0 deletions src/providers/token-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,13 @@ export const USDC_NATIVE_BASE = new Token(
'USDbC',
'USD Base Coin'
);
export const VIRTUAL_BASE = new Token(
ChainId.BASE,
'0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b',
18,
'VIRTUAL',
'Virtual Protocol'
);

// Base Goerli Tokens
export const USDC_BASE_GOERLI = new Token(
Expand Down
13 changes: 13 additions & 0 deletions src/providers/v2/subgraph-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ export class V2SubgraphProvider implements IV2SubgraphProvider {
(pool) =>
pool.token0.id == FEI ||
pool.token1.id == FEI ||
this.isVirtualPairBaseV2Pool(pool) ||
parseFloat(pool.trackedReserveETH) > this.trackedEthThreshold
);

Expand All @@ -269,6 +270,7 @@ export class V2SubgraphProvider implements IV2SubgraphProvider {
return (
pool.token0.id == FEI ||
pool.token1.id == FEI ||
this.isVirtualPairBaseV2Pool(pool) ||
parseFloat(pool.trackedReserveETH) > this.trackedEthThreshold ||
parseFloat(pool.reserveUSD) > this.untrackedUsdThreshold
);
Expand Down Expand Up @@ -312,4 +314,15 @@ export class V2SubgraphProvider implements IV2SubgraphProvider {

return poolsSanitized;
}

// This method checks if a given pool contains the VIRTUAL token.
public isVirtualPairBaseV2Pool(pool: RawV2SubgraphPool): boolean {
const virtualTokenAddress =
'0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b'.toLowerCase();
return (
this.chainId === ChainId.BASE &&
(pool.token0.id.toLowerCase() === virtualTokenAddress ||
pool.token1.id.toLowerCase() === virtualTokenAddress)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BigNumber } from '@ethersproject/bignumber';
import { BaseProvider } from '@ethersproject/providers';
import { ChainId, Price } from '@uniswap/sdk-core';
import { ChainId, Percent, Price } from '@uniswap/sdk-core';
import { Pool } from '@uniswap/v3-sdk';
import { CurrencyAmount, log, WRAPPED_NATIVE_CURRENCY } from '../../../util';
import { calculateL1GasFeesHelper } from '../../../util/gas-factory-helpers';
Expand Down Expand Up @@ -205,12 +205,19 @@ export abstract class TickBasedHeuristicGasModelFactory<
// Note that the syntheticGasCost being lessThan the original quoted value is not always strictly better
// e.g. the scenario where the amountToken/ETH pool is very illiquid as well and returns an extremely small number
// however, it is better to have the gasEstimation be almost 0 than almost infinity, as the user will still receive a quote
// Only use syntheticGasCostInTermsOfQuoteToken if it's within 30% of the original gasCostInTermsOfQuoteToken as a safeguard.
if (
syntheticGasCostInTermsOfQuoteToken !== null &&
(gasCostInTermsOfQuoteToken === null ||
syntheticGasCostInTermsOfQuoteToken.lessThan(
(syntheticGasCostInTermsOfQuoteToken.lessThan(
gasCostInTermsOfQuoteToken.asFraction
))
) &&
gasCostInTermsOfQuoteToken
.subtract(syntheticGasCostInTermsOfQuoteToken)
.lessThan(
gasCostInTermsOfQuoteToken.multiply(new Percent(30, 100))
.asFraction
)))
) {
log.info(
{
Expand Down
59 changes: 59 additions & 0 deletions src/util/gas-factory-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
GasModelProviderConfig,
getQuoteThroughNativePool,
MethodParameters,
metric,
MetricLoggerUnit,
MixedRouteWithValidQuote,
RouteWithValidQuote,
SwapOptions,
Expand Down Expand Up @@ -690,3 +692,60 @@ export const calculateL1GasFeesHelper = async (
return calculateArbitrumToL1FeeFromCalldata(data, gasData, chainId);
}
};

// Logs metrics about the diff between original estimatedGasUsed based on heuristics and the simulated gas used.
// This will help us track the quality of our gas estimation quality per chain.
export const logGasEstimationVsSimulationMetrics = (
route: SwapRoute,
simulationGasUsed: BigNumber,
chainId: ChainId
) => {
try {
// Log the diff between original estimatedGasUsed and the simulated gas used
const estimatedGasUsed = route.estimatedGasUsed.toNumber();
const simulatedGasUsed = simulationGasUsed.toNumber();
const diff = estimatedGasUsed - simulatedGasUsed;
const absDiff = Math.abs(estimatedGasUsed - simulatedGasUsed);
const misEstimatePercent = (diff / estimatedGasUsed) * 100;
const misEstimateAbsPercent = (absDiff / estimatedGasUsed) * 100;

log.info(
{
estimatedGasUsed: estimatedGasUsed,
simulatedGasUsed: simulatedGasUsed,
absDiff: absDiff,
diff: diff,
},
'Gas used diff between estimatedGasUsed and simulatedGasUsed'
);
log.info(
{
misEstimateAbsPercent: misEstimateAbsPercent,
},
'Gas used mis-estimate percent'
);

metric.putMetric(
`TenderlySimulationGasUsed_AbsDiff_Chain_${chainId}`,
absDiff,
MetricLoggerUnit.Count
);
metric.putMetric(
`TenderlySimulationGasUsed_MisEstimateAbsPercent_Chain_${chainId}`,
misEstimateAbsPercent,
MetricLoggerUnit.Count
);

const label = diff >= 0 ? 'OverEstimate' : 'UnderEstimate';
metric.putMetric(
`TenderlySimulationGasUsed_${label}Percent_Chain_${chainId}`,
misEstimatePercent,
MetricLoggerUnit.Count
);
} catch (err) {
log.error(
{ err: err },
'Failed to log diff between original estimatedGasUsed and the simulated gas used'
);
}
};
36 changes: 36 additions & 0 deletions src/util/tenderlySimulationErrorBreakDown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Token } from '@uniswap/sdk-core';
import { SimulationStatus, VIRTUAL_BASE } from '../providers';

export function breakDownTenderlySimulationError(
tokenIn: Token,
tokenOut: Token,
data?: string
): SimulationStatus {
if (data) {
switch (data) {
case '0x739dbe52': // V3TooMuchRequested
case '0x39d35496': // V3TooLittleReceived
case '0x849eaf98': // V2TooLittleReceived
case '0x8ab0bc16': // V2TooMuchRequested
case '0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000025556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54000000000000000000000000000000000000000000000000000000': // INSUFFICIENT_OUTPUT_AMOUNT
case '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034949410000000000000000000000000000000000000000000000000000000000': // IIA
return SimulationStatus.SlippageTooLow;
case '0x675cae38': // InsufficientToken
if (
tokenIn.address.toLowerCase() ===
VIRTUAL_BASE.address.toLowerCase() ||
tokenOut.address.toLowerCase() === VIRTUAL_BASE.address.toLowerCase()
) {
// if this is from virtual, we'd guess it's due to slippage too low, although it might be due to something else
return SimulationStatus.SlippageTooLow;
}

// Otherwise we don't wanna guess, just return generic failed.
return SimulationStatus.Failed;
default: // we don't know why onchain execution reverted, just return generic failed.
return SimulationStatus.Failed;
}
}

return SimulationStatus.Failed;
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ const GAS_ESTIMATE_DEVIATION_PERCENT: { [chainId in ChainId]: number } = {
[ChainId.MAINNET]: 50,
[ChainId.GOERLI]: 62,
[ChainId.SEPOLIA]: 75,
[ChainId.OPTIMISM]: 71,
[ChainId.OPTIMISM]: 75,
[ChainId.OPTIMISM_GOERLI]: 30,
[ChainId.OPTIMISM_SEPOLIA]: 30,
[ChainId.ARBITRUM_ONE]: 53,
Expand Down Expand Up @@ -747,7 +747,7 @@ describe('alpha router integration', () => {
// we will start using the new tenderly node endpoint in SOR integ-test suite at 100%
3000,
100,
[ChainId.MAINNET, ChainId.BASE]
[ChainId.MAINNET, ChainId.BASE, ChainId.ARBITRUM_ONE, ChainId.OPTIMISM, ChainId.POLYGON, ChainId.AVALANCHE, ChainId.BLAST, ChainId.WORLDCHAIN]
);

const simulator = new FallbackTenderlySimulator(
Expand Down Expand Up @@ -3656,7 +3656,7 @@ describe('quote for other networks', () => {
// we will start using the new tenderly node endpoint in SOR integ-test suite at 100%
3000,
100,
[ChainId.MAINNET, ChainId.BASE]
[ChainId.MAINNET, ChainId.BASE, ChainId.ARBITRUM_ONE, ChainId.OPTIMISM, ChainId.POLYGON, ChainId.AVALANCHE, ChainId.BLAST, ChainId.WORLDCHAIN]
);

const simulator = new FallbackTenderlySimulator(
Expand Down
1 change: 1 addition & 0 deletions test/unit/providers/simulation-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ jest.mock('../../../src/util/gas-factory-helpers', () => ({
quoteGasAdjusted,
};
},
logGasEstimationVsSimulationMetrics: jest.fn(),
}));

const provider = new JsonRpcProvider();
Expand Down
Loading

0 comments on commit d895a5a

Please sign in to comment.