Skip to content
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

Merged
merged 23 commits into from
Aug 20, 2022
Merged

Conversation

kevinuma
Copy link
Contributor

@kevinuma kevinuma commented Aug 17, 2022

Originally reviewed in #212

@kevinuma kevinuma changed the title Gas feat(relayer): Consider gas cost when calculating profitability Aug 18, 2022
Copy link
Member

@nicholaspai nicholaspai left a 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 Show resolved Hide resolved
src/clients/ProfitClient.ts Outdated Show resolved Hide resolved
test/ProfitClient.ConsiderProfitability.ts Outdated Show resolved Hide resolved
test/ProfitClient.ConsiderProfitability.ts Show resolved Hide resolved
@kevinuma kevinuma marked this pull request as ready for review August 19, 2022 14:55
cgPrices = await this.coingeckoPrices(Object.keys(l1Tokens));
const [maticTokenPrice, otherTokenPrices] = await Promise.all([
// Add WMATIC for gas cost calculations.
this.coingeckoPrices([WMATIC], "polygon-pos"),
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

package.json Outdated Show resolved Hide resolved

private constructRelayerFeeQuery(chainId: number, provider: Provider): relayFeeCalculator.QueryInterface {
// Fallback to Coingecko's free API for now.
// TODO: Add support for Coingecko Pro.
Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Member

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

Copy link
Member

@nicholaspai nicholaspai left a 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?

src/clients/ProfitClient.ts Show resolved Hide resolved
src/clients/ProfitClient.ts Outdated Show resolved Hide resolved

private constructRelayerFeeQuery(chainId: number, provider: Provider): relayFeeCalculator.QueryInterface {
// Fallback to Coingecko's free API for now.
// TODO: Add support for Coingecko Pro.
Copy link
Member

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

src/relayer/RelayerClientHelper.ts Outdated Show resolved Hide resolved
src/relayer/RelayerClientHelper.ts Show resolved Hide resolved
test/ProfitClient.ConsiderProfitability.ts Outdated Show resolved Hide resolved
Copy link
Member

@nicholaspai nicholaspai left a 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!

@kevinuma kevinuma requested a review from pxrl August 19, 2022 18:16
Copy link
Contributor

@pxrl pxrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Driveby comments

cgPrices = await this.coingeckoPrices(Object.keys(l1Tokens));
const [maticTokenPrice, otherTokenPrices] = await Promise.all([
// Add WMATIC for gas cost calculations.
this.coingeckoPrices([WMATIC], "polygon-pos"),
Copy link
Contributor

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.

Comment on lines 245 to 256
switch (chainId) {
case 1:
return new relayFeeCalculator.EthereumQueries(
provider,
undefined,
undefined,
undefined,
undefined,
coingeckoProApiKey,
this.logger,
gasMarkup
);
Copy link
Contributor

@pxrl pxrl Aug 19, 2022

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?

Copy link
Contributor Author

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.

@kevinuma kevinuma requested a review from pxrl August 19, 2022 19:52
Copy link
Contributor

@mrice32 mrice32 left a 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.

Comment on lines 19 to 20
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
10: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

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`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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`;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment on lines 119 to 120
const totalGasCostWei = this.getTotalGasCost(deposit.destinationChainId);
if (!totalGasCostWei) {
Copy link
Contributor

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!).

Copy link
Contributor Author

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)

Comment on lines 123 to 129
this.logger.error({
at: "ProfitClient",
message: errorMsg,
allGasCostsFetched: this.totalGasCosts,
chainId,
});
throw new Error(errorMsg);
Copy link
Contributor

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.

Copy link
Contributor Author

@kevinuma kevinuma Aug 19, 2022

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.

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.
Copy link
Contributor

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?

Copy link
Contributor Author

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"

cgPrices = await this.coingeckoPrices(Object.keys(l1Tokens));
const [maticTokenPrice, otherTokenPrices] = await Promise.all([
// Add WMATIC for gas cost calculations.
this.coingeckoPrices([WMATIC], "polygon-pos"),
Copy link
Contributor

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.

Copy link
Contributor Author

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

Comment on lines 218 to 219
this.logger.error({ at: "ProfitClient", message: "Could not fetch all token prices 💳", mrkdwn });
throw new Error(mrkdwn);
Copy link
Contributor

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?

Copy link
Contributor Author

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

Comment on lines 224 to 226
const getGasCosts = [];
for (const chainId of this.enabledChainIds) {
getGasCosts.push(this.relayerFeeQueries[chainId].getGasCosts());
Copy link
Contributor

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()
));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion! Updated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants