-
Notifications
You must be signed in to change notification settings - Fork 45
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(relayer): Consider gas cost when calculating profitability #261
Conversation
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.
Left some comments. Overall great work I'm really excited to add this feature! I'm just being a bit cautious on it as it's high risk
src/clients/ProfitClient.ts
Outdated
cgPrices = await this.coingeckoPrices(Object.keys(l1Tokens)); | ||
const [maticTokenPrice, otherTokenPrices] = await Promise.all([ | ||
// Add WMATIC for gas cost calculations. | ||
this.coingeckoPrices([WMATIC], "polygon-pos"), |
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.
Should this be conditional upon RELAYER_DESTINATION_CHAINS including Polygon?
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.
Nope this is only to fetch WMATIC on Polygon for gas price. Plus it doesn't exist on other chains anyway
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.
But the price of WMATIC is only needed if the bot is actually filling relays on Polygon, right? The reason I ask is that the RL bot is obviously doing this, but I'm not sure that's a valid assumption for non-RL relayers.
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.
Hmm that is true. It does make the code a bit more complex to have WMATIC being optional when the relayer's enabledChainIds doesn't include Polygon though. For now I think an extra request to get gas price for Polygon might not be a deal breaker. We can address this in a followup PR if needed.
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.
Yeah I can see it's a bit clunky to do in the middle. Happy to consider it as an optimisation later on, once things settle down.
|
||
private constructRelayerFeeQuery(chainId: number, provider: Provider): relayFeeCalculator.QueryInterface { | ||
// Fallback to Coingecko's free API for now. | ||
// TODO: Add support for Coingecko Pro. |
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.
eventually we can just use the sdk-v2's CoingeckoClient
and get the fallback logic for free
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.
SG. We can bump the sdk-v2 version when that's ready
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.
It's ready right now but I want to isolate only the Vercel API to using CG pro to reduce pro requests for time being, basic seems to work for the most part for relayer
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.
OOC, why are you changing the test syntax and modifying arrow function syntax and async/sync? Is something not working locally?
|
||
private constructRelayerFeeQuery(chainId: number, provider: Provider): relayFeeCalculator.QueryInterface { | ||
// Fallback to Coingecko's free API for now. | ||
// TODO: Add support for Coingecko Pro. |
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.
It's ready right now but I want to isolate only the Vercel API to using CG pro to reduce pro requests for time being, basic seems to work for the most part for relayer
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.
LGTM, i'm gonna request from @mrice32 too since this is a sensitive PR!
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.
Driveby comments
src/clients/ProfitClient.ts
Outdated
cgPrices = await this.coingeckoPrices(Object.keys(l1Tokens)); | ||
const [maticTokenPrice, otherTokenPrices] = await Promise.all([ | ||
// Add WMATIC for gas cost calculations. | ||
this.coingeckoPrices([WMATIC], "polygon-pos"), |
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.
But the price of WMATIC is only needed if the bot is actually filling relays on Polygon, right? The reason I ask is that the RL bot is obviously doing this, but I'm not sure that's a valid assumption for non-RL relayers.
src/clients/ProfitClient.ts
Outdated
switch (chainId) { | ||
case 1: | ||
return new relayFeeCalculator.EthereumQueries( | ||
provider, | ||
undefined, | ||
undefined, | ||
undefined, | ||
undefined, | ||
coingeckoProApiKey, | ||
this.logger, | ||
gasMarkup | ||
); |
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.
This is just a style comment, but is there any possibility for a construction something like the following?
const queryHandlers: { [chainId: number]: queryFunction } = {
1: relayFeeCalculator.EthereumQueries,
10: relayFeeCalculator.OptimismQueries,
137: relayFeeCalculator.PolygonQueries,
288: relayFeeCalculator.BobaQueries,
42161: relayFeeCalculator.ArbitrumQueries,
}
const queryFn: queryFunction = queryHandlers[chainId];
if (queryFn === undefined) {
throw new Error(`Unexpected chain ${chainId}`);
}
return new queryFn(
provider,
undefined,
undefined,
undefined,
undefined,
coingeckoProApiKey,
this.logger,
gasMarkup
);
Advantage would be that it's a 1-line change to add a new chain, and the overhead of changing the structure of the function arguments is quite a bit lower. It's also arguably easier to audit and ensure that all chains are supplying the same arguments in the same order.
I've used it a lot in C and Python, but I'm not sure how common it is in TS/JS though. Does it make sense here?
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.
Amazing suggestion! It's much cleaner and more elegant. I updated the code.
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.
All of this logic looks great! Really excited about rolling this out. A few very minor comments and nits.
src/clients/ProfitClient.ts
Outdated
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH | ||
10: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH |
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.
nit: why not make WETH a constant, like WMATIC?
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.
Updated
src/clients/ProfitClient.ts
Outdated
const totalGasCostWei = this.getTotalGasCost(deposit.destinationChainId); | ||
if (!totalGasCostWei) { | ||
const chainId = deposit.destinationChainId; | ||
const errorMsg = `Missing total gas cost for ${chainId}. This likely indicate some gas cost request failed`; |
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.
const errorMsg = `Missing total gas cost for ${chainId}. This likely indicate some gas cost request failed`; | |
const errorMsg = `Missing total gas cost for ${chainId}. This likely indicates some gas cost request failed`; |
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.
Updated
src/clients/ProfitClient.ts
Outdated
const totalGasCostWei = this.getTotalGasCost(deposit.destinationChainId); | ||
if (!totalGasCostWei) { |
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.
Could this ever be falsy? It looks like the method defaults to returning a BigNumber of 0 if there is no price stored, and I don't think a BigNumber object can be falsy (please correct me if I'm wrong!).
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.
Good call! Updated to be more explicit and check against undefined + toBN(0)
src/clients/ProfitClient.ts
Outdated
this.logger.error({ | ||
at: "ProfitClient", | ||
message: errorMsg, | ||
allGasCostsFetched: this.totalGasCosts, | ||
chainId, | ||
}); | ||
throw new Error(errorMsg); |
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.
Should we log an error and throw an error? Not necessarily wrong, but I think this will end up generating two error alerts. One from the crash of the process and one from the error that you explicitly send here. Also, a crash will generally be retried, so if the issue is sporadic, the retries will mean that no human gets alerted, but this error log will immediately alert someone.
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.
I changed the logging to warn and still leave throw new Error so the relayer would fail.
src/clients/ProfitClient.ts
Outdated
if (toBN(deposit.relayerFeePct).eq(toBN(0))) { | ||
this.logger.debug({ at: "ProfitClient", message: "Deposit set 0 relayerFeePct. Rejecting relay" }); | ||
return false; | ||
} | ||
|
||
// This should happen before the previous checks as we don't want to turn them off when profitability is disabled. |
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.
nit: I'm a little confused by this comment. Should this check happen before or after the checks above? If it should happen before them, then shouldn't we move it?
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.
So sorry I had a brain fart. It should be "after"
src/clients/ProfitClient.ts
Outdated
cgPrices = await this.coingeckoPrices(Object.keys(l1Tokens)); | ||
const [maticTokenPrice, otherTokenPrices] = await Promise.all([ | ||
// Add WMATIC for gas cost calculations. | ||
this.coingeckoPrices([WMATIC], "polygon-pos"), |
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.
Can we use the ETH mainnet matic token here instead, so we don't need to do this special casing for matic? We could then do all of our queries with ethereum as the platform, I think, right?
I think that might also simplify some of the logic below.
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.
Good find! I updated to fetch MATIC price on ethereum
src/clients/ProfitClient.ts
Outdated
this.logger.error({ at: "ProfitClient", message: "Could not fetch all token prices 💳", mrkdwn }); | ||
throw new Error(mrkdwn); |
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.
Same comment as above -- are we sure we want to alert and crash?
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.
Changed the logging to warn
src/clients/ProfitClient.ts
Outdated
const getGasCosts = []; | ||
for (const chainId of this.enabledChainIds) { | ||
getGasCosts.push(this.relayerFeeQueries[chainId].getGasCosts()); |
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.
nit: Is there some reason we've spread the looping and Promise.all out? Could we just do it in a simple Promise.all wrapping a map?
const gasCosts = await Promise.all(this.enabledChainIds.map(
(chainId) => this.relayerFeeQueries[chainId].getGasCosts()
));
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.
Good suggestion! Updated
Originally reviewed in #212