-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
improve(API): Apply gas mark up to base fee rather than gas cost #1339
Changes from all commits
1a21394
eaf7fd8
ed22e76
00a4557
cb6b690
cdde913
396d62b
8cf8072
99b5da2
eb77db9
ea0a94d
ed05490
769296d
6a524ac
0113ac5
3044ca9
099d72b
18c3641
c9a3481
75254f6
6f2bcf3
051be80
7f4f2a8
bbfbe1b
5fb79b9
6948d14
752704c
868d76b
7d91801
47fa488
5f6a1c5
2ffb6a6
f10a8e9
fc08217
9fe1b31
24c2bbd
207397d
ca704ea
e48b4f9
894c141
0f3d4f2
cd4dd7e
fa9a87e
2ac4d07
35ae6cf
0237cda
1840184
9af605f
33d4eb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,6 @@ import { | |
} from "@across-protocol/contracts/dist/typechain"; | ||
import acrossDeployments from "@across-protocol/contracts/dist/deployments/deployments.json"; | ||
import * as sdk from "@across-protocol/sdk"; | ||
import { asL2Provider } from "@eth-optimism/sdk"; | ||
import { | ||
BALANCER_NETWORK_CONFIG, | ||
BalancerSDK, | ||
|
@@ -86,14 +85,19 @@ const { | |
REACT_APP_HUBPOOL_CHAINID, | ||
REACT_APP_PUBLIC_INFURA_ID, | ||
REACT_APP_COINGECKO_PRO_API_KEY, | ||
GAS_MARKUP, | ||
GAS_MARKUP, // To be deprecated and replaced by BASE_FEE_MARKUP and PRIORITY_FEE_MARKUP | ||
BASE_FEE_MARKUP, | ||
PRIORITY_FEE_MARKUP, | ||
VERCEL_ENV, | ||
LOG_LEVEL, | ||
} = process.env; | ||
|
||
export const gasMarkup: { | ||
export const baseFeeMarkup: { | ||
[chainId: string]: number; | ||
} = GAS_MARKUP ? JSON.parse(GAS_MARKUP) : {}; | ||
} = JSON.parse(BASE_FEE_MARKUP || GAS_MARKUP || "{}"); | ||
export const priorityFeeMarkup: { | ||
[chainId: string]: number; | ||
} = JSON.parse(PRIORITY_FEE_MARKUP || "{}"); | ||
// Default to no markup. | ||
export const DEFAULT_GAS_MARKUP = 0; | ||
|
||
|
@@ -589,14 +593,47 @@ export const getHubPoolClient = () => { | |
); | ||
}; | ||
|
||
export const getGasMarkup = (chainId: string | number) => { | ||
if (typeof gasMarkup[chainId] === "number") { | ||
return gasMarkup[chainId]; | ||
export const getGasMarkup = ( | ||
chainId: string | number | ||
): { baseFeeMarkup: BigNumber; priorityFeeMarkup: BigNumber } => { | ||
let _baseFeeMarkup: BigNumber | undefined; | ||
let _priorityFeeMarkup: BigNumber | undefined; | ||
if (typeof baseFeeMarkup[chainId] === "number") { | ||
_baseFeeMarkup = utils.parseEther((1 + baseFeeMarkup[chainId]).toString()); | ||
} | ||
if (typeof priorityFeeMarkup[chainId] === "number") { | ||
_priorityFeeMarkup = utils.parseEther( | ||
(1 + priorityFeeMarkup[chainId]).toString() | ||
); | ||
} | ||
|
||
// Otherwise, use default gas markup (or optimism's for OP stack). | ||
if (_baseFeeMarkup === undefined) { | ||
_baseFeeMarkup = utils.parseEther( | ||
( | ||
1 + | ||
(sdk.utils.chainIsOPStack(Number(chainId)) | ||
? baseFeeMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP | ||
: DEFAULT_GAS_MARKUP) | ||
).toString() | ||
); | ||
} | ||
if (_priorityFeeMarkup === undefined) { | ||
_priorityFeeMarkup = utils.parseEther( | ||
( | ||
1 + | ||
(sdk.utils.chainIsOPStack(Number(chainId)) | ||
? priorityFeeMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP | ||
: DEFAULT_GAS_MARKUP) | ||
).toString() | ||
); | ||
} | ||
|
||
return sdk.utils.chainIsOPStack(Number(chainId)) | ||
? gasMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP | ||
: DEFAULT_GAS_MARKUP; | ||
// Otherwise, use default gas markup (or optimism's for OP stack). | ||
return { | ||
baseFeeMarkup: _baseFeeMarkup, | ||
priorityFeeMarkup: _priorityFeeMarkup, | ||
}; | ||
}; | ||
|
||
/** | ||
|
@@ -627,7 +664,7 @@ export const getRelayerFeeCalculator = ( | |
); | ||
}; | ||
|
||
const getRelayerFeeCalculatorQueries = ( | ||
export const getRelayerFeeCalculatorQueries = ( | ||
destinationChainId: number, | ||
overrides: Partial<{ | ||
spokePoolAddress: string; | ||
|
@@ -641,8 +678,7 @@ const getRelayerFeeCalculatorQueries = ( | |
overrides.spokePoolAddress || getSpokePoolAddress(destinationChainId), | ||
overrides.relayerAddress, | ||
REACT_APP_COINGECKO_PRO_API_KEY, | ||
getLogger(), | ||
getGasMarkup(destinationChainId) | ||
getLogger() | ||
); | ||
}; | ||
|
||
|
@@ -654,12 +690,13 @@ const getRelayerFeeCalculatorQueries = ( | |
* @param originChainId The origin chain that this token will be transferred from | ||
* @param destinationChainId The destination chain that this token will be transferred to | ||
* @param recipientAddress The address that will receive the transferred funds | ||
* @param tokenPrice An optional overred price to prevent the SDK from creating its own call | ||
* @param message An optional message to include in the transfer | ||
* @param relayerAddress An optional relayer address to use for the transfer | ||
* @param gasUnits An optional gas unit to use for the transfer | ||
* @param gasPrice An optional gas price to use for the transfer | ||
* @returns The a promise to the relayer fee for the given `amount` of transferring `l1Token` to `destinationChainId` | ||
* @param tokenPrice Price of input token in gas token units, used by SDK to compute gas fee percentages. | ||
* @param relayerAddress Relayer address that SDK will use to simulate the fill transaction for gas cost estimation if | ||
* the gasUnits is not defined. | ||
* @param gasPrice Gas price that SDK will use to compute gas fee percentages. | ||
* @param [gasUnits] An optional gas cost to use for the transfer. If not provided, the SDK will recompute this. | ||
* @returns Relayer fee components for a fill of the given `amount` of transferring `l1Token` to `destinationChainId` | ||
*/ | ||
export const getRelayerFeeDetails = async ( | ||
deposit: { | ||
|
@@ -671,10 +708,11 @@ export const getRelayerFeeDetails = async ( | |
recipientAddress: string; | ||
message?: string; | ||
}, | ||
tokenPrice?: number, | ||
relayerAddress?: string, | ||
tokenPrice: number, | ||
relayerAddress: string, | ||
gasPrice: sdk.utils.BigNumberish, | ||
gasUnits?: sdk.utils.BigNumberish, | ||
gasPrice?: sdk.utils.BigNumberish | ||
tokenGasCost?: sdk.utils.BigNumberish | ||
): Promise<sdk.relayFeeCalculator.RelayerFeeDetails> => { | ||
const { | ||
inputToken, | ||
|
@@ -703,7 +741,8 @@ export const getRelayerFeeDetails = async ( | |
relayerAddress, | ||
tokenPrice, | ||
gasPrice, | ||
gasUnits | ||
gasUnits, | ||
tokenGasCost | ||
); | ||
}; | ||
|
||
|
@@ -1931,18 +1970,34 @@ export function getCachedFillGasUsage( | |
deposit.destinationChainId, | ||
overrides | ||
); | ||
const { nativeGasCost } = await relayerFeeCalculatorQueries.getGasCosts( | ||
// We don't care about the gas token price or the token gas price, only the raw gas units. In the API | ||
// we'll compute the gas price separately. | ||
const gasCosts = await relayerFeeCalculatorQueries.getGasCosts( | ||
buildDepositForSimulation(deposit), | ||
overrides?.relayerAddress, | ||
{ | ||
omitMarkup: true, | ||
// Scale the op stack L1 gas cost component by the base fee multiplier. | ||
// Consider adding a new environment variable OP_STACK_L1_GAS_COST_MARKUP if we want finer-grained control. | ||
opStackL1GasCostMultiplier: getGasMarkup(deposit.destinationChainId) | ||
.baseFeeMarkup, | ||
} | ||
); | ||
return nativeGasCost; | ||
return { | ||
nativeGasCost: gasCosts.nativeGasCost, | ||
tokenGasCost: gasCosts.tokenGasCost, | ||
}; | ||
}; | ||
|
||
return getCachedValue(cacheKey, ttl, fetchFn, (bnFromCache) => | ||
BigNumber.from(bnFromCache) | ||
return getCachedValue( | ||
cacheKey, | ||
ttl, | ||
fetchFn, | ||
(gasCosts: { nativeGasCost: BigNumber; tokenGasCost: BigNumber }) => { | ||
return { | ||
nativeGasCost: BigNumber.from(gasCosts.nativeGasCost), | ||
tokenGasCost: BigNumber.from(gasCosts.tokenGasCost), | ||
}; | ||
} | ||
); | ||
} | ||
|
||
|
@@ -1955,7 +2010,7 @@ export function latestGasPriceCache(chainId: number) { | |
return makeCacheGetterAndSetter( | ||
buildInternalCacheKey("latestGasPriceCache", chainId), | ||
ttlPerChain[chainId] || ttlPerChain.default, | ||
() => getMaxFeePerGas(chainId), | ||
async () => (await getMaxFeePerGas(chainId)).maxFeePerGas, | ||
(bnFromCache) => BigNumber.from(bnFromCache) | ||
); | ||
} | ||
|
@@ -1965,16 +2020,18 @@ export function latestGasPriceCache(chainId: number) { | |
* @param chainId The chain ID to resolve the gas price for | ||
* @returns The gas price in the native currency of the chain | ||
*/ | ||
export async function getMaxFeePerGas(chainId: number): Promise<BigNumber> { | ||
if (sdk.utils.chainIsOPStack(chainId)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Important LOC we're removing. We should use gas price oracle for all estimates since it handles OP stack now |
||
const l2Provider = asL2Provider(getProvider(chainId)); | ||
return l2Provider.getGasPrice(); | ||
} | ||
const { maxFeePerGas } = await sdk.gasPriceOracle.getGasPriceEstimate( | ||
getProvider(chainId), | ||
chainId | ||
); | ||
return maxFeePerGas; | ||
export function getMaxFeePerGas( | ||
chainId: number | ||
): Promise<sdk.gasPriceOracle.GasPriceEstimate> { | ||
const { | ||
baseFeeMarkup: baseFeeMultiplier, | ||
priorityFeeMarkup: priorityFeeMultiplier, | ||
} = getGasMarkup(chainId); | ||
return sdk.gasPriceOracle.getGasPriceEstimate(getProvider(chainId), { | ||
chainId, | ||
baseFeeMultiplier, | ||
priorityFeeMultiplier, | ||
}); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,154 @@ | ||
import { VercelResponse } from "@vercel/node"; | ||
import { | ||
buildDepositForSimulation, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file has a lot of changes but its an API endpoint we don't depend on in production as its mostly here to compare against the current existing API to compare the expected gas total changes. The main changes are:
|
||
getGasMarkup, | ||
getLogger, | ||
getMaxFeePerGas, | ||
getRelayerFeeCalculatorQueries, | ||
handleErrorCondition, | ||
latestGasPriceCache, | ||
sendResponse, | ||
} from "./_utils"; | ||
import { TypedVercelRequest } from "./_types"; | ||
import { ethers, providers, VoidSigner } from "ethers"; | ||
import * as sdk from "@across-protocol/sdk"; | ||
import { L2Provider } from "@eth-optimism/sdk/dist/interfaces/l2-provider"; | ||
|
||
import mainnetChains from "../src/data/chains_1.json"; | ||
import { | ||
DEFAULT_SIMULATED_RECIPIENT_ADDRESS, | ||
TOKEN_SYMBOLS_MAP, | ||
} from "./_constants"; | ||
import { assert, Infer, object, optional, string } from "superstruct"; | ||
|
||
const chains = mainnetChains; | ||
|
||
const QueryParamsSchema = object({ | ||
symbol: optional(string()), | ||
}); | ||
type QueryParams = Infer<typeof QueryParamsSchema>; | ||
|
||
const handler = async ( | ||
_: TypedVercelRequest<Record<string, never>>, | ||
{ query }: TypedVercelRequest<QueryParams>, | ||
response: VercelResponse | ||
) => { | ||
const logger = getLogger(); | ||
assert(query, QueryParamsSchema); | ||
const tokenSymbol = query.symbol ?? "WETH"; | ||
|
||
try { | ||
const chainIdsWithToken: { [chainId: string]: string } = Object.fromEntries( | ||
chains | ||
.map(({ chainId }) => { | ||
const tokenAddress = | ||
TOKEN_SYMBOLS_MAP?.[tokenSymbol as keyof typeof TOKEN_SYMBOLS_MAP] | ||
?.addresses[chainId]; | ||
return [chainId, tokenAddress]; | ||
}) | ||
.filter(([, tokenAddress]) => tokenAddress !== undefined) | ||
); | ||
// getMaxFeePerGas will return the gas price after including the baseFeeMultiplier. | ||
const gasPrices = await Promise.all( | ||
chains.map(({ chainId }) => { | ||
return latestGasPriceCache(chainId).get(); | ||
Object.keys(chainIdsWithToken).map((chainId) => { | ||
return getMaxFeePerGas(Number(chainId)); | ||
}) | ||
); | ||
const responseJson = Object.fromEntries( | ||
chains.map(({ chainId }, i) => [chainId, gasPrices[i].toString()]) | ||
const gasCosts = await Promise.all( | ||
Object.entries(chainIdsWithToken).map( | ||
async ([chainId, tokenAddress], i) => { | ||
// This is a dummy deposit used to pass into buildDepositForSimulation() to build a fill transaction | ||
// that we can simulate without reversion. The only parameter that matters is that the destinationChainId | ||
// is set to the spoke pool's chain ID we'll be simulating the fill call on. | ||
const depositArgs = { | ||
amount: ethers.BigNumber.from(100), | ||
inputToken: sdk.constants.ZERO_ADDRESS, | ||
outputToken: tokenAddress, | ||
recipientAddress: DEFAULT_SIMULATED_RECIPIENT_ADDRESS, | ||
originChainId: 0, // Shouldn't matter for simulation | ||
destinationChainId: Number(chainId), | ||
}; | ||
const deposit = buildDepositForSimulation(depositArgs); | ||
const relayerFeeCalculatorQueries = getRelayerFeeCalculatorQueries( | ||
Number(chainId) | ||
); | ||
const opStackL1GasCostMultiplier = getGasMarkup( | ||
Number(chainId) | ||
).baseFeeMarkup; | ||
const { nativeGasCost, tokenGasCost } = | ||
await relayerFeeCalculatorQueries.getGasCosts( | ||
deposit, | ||
relayerFeeCalculatorQueries.simulatedRelayerAddress, | ||
{ | ||
// Pass in the already-computed gasPrice into this query so that the tokenGasCost includes | ||
// the scaled gas price, | ||
// e.g. tokenGasCost = nativeGasCost * (baseFee * baseFeeMultiplier + priorityFee). | ||
gasPrice: gasPrices[i].maxFeePerGas, | ||
opStackL1GasCostMultiplier, | ||
} | ||
); | ||
// OPStack chains factor in the L1 gas cost of including the L2 transaction in an L1 rollup batch | ||
// into the total gas cost of the L2 transaction. | ||
let opStackL1GasCost: ethers.BigNumber | undefined = undefined; | ||
if (sdk.utils.chainIsOPStack(Number(chainId))) { | ||
const provider = relayerFeeCalculatorQueries.provider; | ||
const _unsignedTx = await sdk.utils.populateV3Relay( | ||
relayerFeeCalculatorQueries.spokePool, | ||
deposit, | ||
relayerFeeCalculatorQueries.simulatedRelayerAddress | ||
); | ||
const voidSigner = new VoidSigner( | ||
relayerFeeCalculatorQueries.simulatedRelayerAddress, | ||
relayerFeeCalculatorQueries.provider | ||
); | ||
const unsignedTx = await voidSigner.populateTransaction({ | ||
..._unsignedTx, | ||
gasLimit: nativeGasCost, // prevents additional gas estimation call | ||
}); | ||
opStackL1GasCost = await ( | ||
provider as L2Provider<providers.Provider> | ||
).estimateL1GasCost(unsignedTx); | ||
opStackL1GasCost = opStackL1GasCostMultiplier | ||
.mul(opStackL1GasCost) | ||
.div(sdk.utils.fixedPointAdjustment); | ||
} | ||
return { | ||
nativeGasCost, | ||
tokenGasCost, | ||
opStackL1GasCost, | ||
}; | ||
} | ||
) | ||
); | ||
const responseJson = { | ||
tokenSymbol, | ||
...Object.fromEntries( | ||
Object.keys(chainIdsWithToken).map((chainId, i) => [ | ||
chainId, | ||
{ | ||
gasPrice: gasPrices[i].maxFeePerGas.toString(), | ||
gasPriceComponents: { | ||
maxFeePerGas: gasPrices[i].maxFeePerGas | ||
.sub(gasPrices[i].maxPriorityFeePerGas) | ||
.toString(), | ||
priorityFeePerGas: gasPrices[i].maxPriorityFeePerGas.toString(), | ||
baseFeeMultiplier: ethers.utils.formatEther( | ||
getGasMarkup(chainId).baseFeeMarkup | ||
), | ||
priorityFeeMultiplier: ethers.utils.formatEther( | ||
getGasMarkup(chainId).priorityFeeMarkup | ||
), | ||
opStackL1GasCostMultiplier: sdk.utils.chainIsOPStack( | ||
Number(chainId) | ||
) | ||
? ethers.utils.formatEther(getGasMarkup(chainId).baseFeeMarkup) | ||
: undefined, | ||
}, | ||
nativeGasCost: gasCosts[i].nativeGasCost.toString(), | ||
tokenGasCost: gasCosts[i].tokenGasCost.toString(), | ||
opStackL1GasCost: gasCosts[i]?.opStackL1GasCost?.toString(), | ||
}, | ||
]) | ||
), | ||
}; | ||
|
||
logger.debug({ | ||
at: "GasPrices", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made gasPrice, tokenPrice and relayerAddress all required since they're never set undefined in this repo and its better to be more constricting on this type where possible