-
Notifications
You must be signed in to change notification settings - Fork 57
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
feat(profitability): Remove hardcoded min revenue map and add logic to consider gas cost #212
Changes from 2 commits
7198daa
9395e48
a9b0ab4
e3233ad
ed9d47f
fe1f6fd
a9e9c30
e52a467
cd44aae
2e7b298
ca839bd
d232b02
e850af8
0d94335
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 |
---|---|---|
|
@@ -6,7 +6,7 @@ import { Coingecko } from "@uma/sdk"; | |
// Define the minimum revenue, in USD, that a relay must yield in order to be considered "profitable". This is a short | ||
// term solution to enable us to avoid DOS relays that yield negative profits. In the future this should be updated | ||
// to actual factor in the cost of sending transactions on the associated target chains. | ||
const chainIdToMinRevenue = { | ||
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. Context: https://risklabs.slack.com/archives/D03GKFYCDJ7/p1657999254013629. tldr; is this hardcoded limit is filtering out a lot of small deposits with profits of < $1. When running with this previous logic, my relayer was skipping 9/10 deposits. We'll follow up with data analysis to understand profitability of small deposits. |
||
const MIN_REVENUE_BY_CHAIN_ID = { | ||
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. nit: Constants should be ALL_CAPS |
||
// Mainnet and L1 testnets. | ||
1: toBNWei(10), | ||
4: toBNWei(10), | ||
|
@@ -22,6 +22,17 @@ const chainIdToMinRevenue = { | |
80001: toBNWei(1), | ||
}; | ||
|
||
// We use wrapped ERC-20 versions instead of the native tokens such as ETH, MATIC for ease of computing prices. | ||
const GAS_TOKEN_BY_CHAIN_ID = { | ||
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH | ||
10: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH | ||
137: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", // WMATIC | ||
288: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH | ||
42161: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH | ||
}; | ||
// TODO: Make this dynamic once we support chains with gas tokens that have different decimals. | ||
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. I hope there is no world where a chain has a non-18 decimal gas token but sadly this will probably be the case |
||
const GAS_TOKEN_DECIMALS = 18; | ||
|
||
export class ProfitClient { | ||
private readonly coingecko; | ||
protected tokenPrices: { [l1Token: string]: BigNumber } = {}; | ||
|
@@ -55,7 +66,7 @@ export class ProfitClient { | |
this.unprofitableFills = {}; | ||
} | ||
|
||
isFillProfitable(deposit: Deposit, fillAmount: BigNumber) { | ||
isFillProfitable(deposit: Deposit, fillAmount: BigNumber, gasUsed: BigNumber) { | ||
if (toBN(this.relayerDiscount).eq(toBNWei(1))) { | ||
this.logger.debug({ at: "ProfitClient", message: "Relayer discount set to 100%. Accepting relay" }); | ||
return true; | ||
|
@@ -69,10 +80,18 @@ export class ProfitClient { | |
const tokenPriceInUsd = this.getPriceOfToken(l1Token); | ||
const fillRevenueInRelayedToken = toBN(deposit.relayerFeePct).mul(fillAmount).div(toBN(10).pow(decimals)); | ||
const fillRevenueInUsd = fillRevenueInRelayedToken.mul(tokenPriceInUsd).div(toBNWei(1)); | ||
|
||
// Consider gas cost. | ||
const gasCostInUsd = gasUsed | ||
.mul(this.getPriceOfToken(GAS_TOKEN_BY_CHAIN_ID[deposit.destinationChainId])) | ||
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. Don't we need to compute the current gas price to take into account fluctuations in relayer costs? I think most chains have gas costs that can vary by up to 10x week-to-week, so I don't think hardcoding USD revenue will be sufficient to match the dynamic estimations being done by the frontend. Note: maybe I'm missing the point of this PR! Feel free to clarify in the description. I'm just going off of the title. 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. gasUsed already includes the gas fee (gasUsed = gas cost * gas fee) as passed in from Relayer. Regarding the hardcoded min USD revenue. We'll likely remove them as the first step as we currently have a lot of small deposits with < $1 profit (after gas costs). Depending on external relayer demand, we might or might add back the ability to configure a min profit amount. 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. Hmmm, maybe we're just using different language to describe the same thing. I'm talking about the USD costs, which would amount to gasUsed * gasPrice * gasTokenPrice. I don't think this code takes I think if we want this code to correctly take into account the true cost of relaying a transaction, we need to estimate the gas cost that will be paid. Do you agree with that assessment? 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. That's what I was talking about. That multiplication you're looking for is done in https://github.com/across-protocol/relayer-v2/pull/212/files#diff-562d70c5c8a2a3e8074947c1f599ce3c57b2abd4cdff462422b5bc9fa4257e9fR76 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. Ah, i understand matt's confusion. Its that usually "gas used" in web3/ethers context refers the EVM gas assessed for a smart contract function. Also, 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. Yep, exactly. Sorry about that @kevinuma. You're completely right. Thanks for the clarification! |
||
.div(toBN(10).pow(GAS_TOKEN_DECIMALS)); | ||
|
||
// How much minimumAcceptableRevenue is scaled. If relayer discount is 0 then need minimumAcceptableRevenue at min. | ||
const revenueScalar = toBNWei(1).sub(this.relayerDiscount); | ||
const minimumAcceptableRevenue = chainIdToMinRevenue[deposit.destinationChainId].mul(revenueScalar).div(toBNWei(1)); | ||
const fillProfitable = fillRevenueInUsd.gte(minimumAcceptableRevenue); | ||
const minimumAcceptableRevenue = MIN_REVENUE_BY_CHAIN_ID[deposit.destinationChainId] | ||
.mul(revenueScalar) | ||
.div(toBNWei(1)); | ||
const fillProfitable = fillRevenueInUsd.sub(gasCostInUsd).gte(minimumAcceptableRevenue); | ||
this.logger.debug({ | ||
at: "ProfitClient", | ||
message: "Considered fill profitability", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,6 +63,19 @@ export async function willSucceed( | |
} | ||
} | ||
|
||
export async function estimateGas( | ||
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. nit: I think it might be clearer if we called this estimateGasCost, since estimateGas is the name of the method that is usually used to report the amount of gas rather than the amount of tokens used for gas. |
||
transaction: AugmentedTransaction | ||
): Promise<{ transaction: AugmentedTransaction; succeed: boolean; reason: string; gasUsed: BigNumber }> { | ||
try { | ||
const args = transaction.value ? [...transaction.args, { value: transaction.value }] : transaction.args; | ||
const gasUsed = await transaction.contract.estimateGas[transaction.method](...args); | ||
return { transaction, succeed: true, reason: null, gasUsed }; | ||
} catch (error) { | ||
console.error(error); | ||
return { transaction, succeed: false, reason: error.reason, gasUsed: toBN(0) }; | ||
} | ||
} | ||
|
||
export function getTarget(targetAddress: string) { | ||
try { | ||
return { targetAddress, ...getContractInfoFromAddress(targetAddress) }; | ||
|
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.
Unused vars