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

Adds support for custom flashloan fees #360

Closed
wants to merge 11 commits into from
14 changes: 10 additions & 4 deletions fastlane_bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@ def calculate_profit(
CCm: CPCContainer,
best_profit: Decimal,
fl_token: str,
flashloan_fee_amt: int,
) -> Tuple[Decimal, Decimal, Decimal]:
"""
Calculate the actual profit in USD.
Expand All @@ -786,6 +787,8 @@ def calculate_profit(
The flashloan token.
fl_token_with_weth: str
The flashloan token with weth.
flashloan_fee_amt: int
The flashloan fee amount.

Returns
-------
Expand All @@ -795,6 +798,7 @@ def calculate_profit(
sort_sequence = ['bancor_v2','bancor_v3'] + self.ConfigObj.UNI_V2_FORKS + self.ConfigObj.UNI_V3_FORKS

best_profit_fl_token = best_profit
flashloan_fee_amt_fl_token = Decimal(str(flashloan_fee_amt))
if fl_token not in [self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS, self.ConfigObj.NATIVE_GAS_TOKEN_ADDRESS]:
price_curves = self.get_prices_simple(CCm, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS, fl_token)
sorted_price_curves = self.custom_sort(price_curves, sort_sequence)
Expand All @@ -803,15 +807,16 @@ def calculate_profit(
self.ConfigObj.logger.debug(f"[bot.calculate_profit sorted_price_curves] {sorted_price_curves}")
if len(sorted_price_curves)>0:
fltkn_eth_conversion_rate = sorted_price_curves[0][-1]
best_profit_eth = Decimal(str(best_profit_fl_token)) / Decimal(str(fltkn_eth_conversion_rate))
flashloan_fee_amt_eth = Decimal(str(flashloan_fee_amt_fl_token)) / Decimal(str(fltkn_eth_conversion_rate))
best_profit_eth = Decimal(str(best_profit_fl_token)) / Decimal(str(fltkn_eth_conversion_rate)) - flashloan_fee_amt_eth
barakman marked this conversation as resolved.
Show resolved Hide resolved
self.ConfigObj.logger.debug(f"[bot.calculate_profit] {fl_token, best_profit_fl_token, fltkn_eth_conversion_rate, best_profit_eth, 'ETH'}")
else:
self.ConfigObj.logger.error(
f"[bot.calculate_profit] Failed to get conversion rate for {fl_token} and {self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS}. Raise"
)
raise
else:
best_profit_eth = best_profit_fl_token
best_profit_eth = best_profit_fl_token - flashloan_fee_amt_fl_token
barakman marked this conversation as resolved.
Show resolved Hide resolved

try:
price_curves_usd = self.get_prices_simple(CCm, self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS, self.ConfigObj.STABLECOIN_ADDRESS)
Expand All @@ -822,6 +827,7 @@ def calculate_profit(
usd_eth_conversion_rate = Decimal(str(sorted_price_curves_usd[0][-1]))
except Exception:
usd_eth_conversion_rate = Decimal("NaN")

best_profit_usd = best_profit_eth * usd_eth_conversion_rate
self.ConfigObj.logger.debug(f"[bot.calculate_profit_usd] {'ETH', best_profit_eth, usd_eth_conversion_rate, best_profit_usd, 'USD'}")
return best_profit_fl_token, best_profit_eth, best_profit_usd
Expand Down Expand Up @@ -967,7 +973,7 @@ def _handle_trade_instructions(
calculated_trade_instructions
)

flashloan_struct = tx_route_handler.generate_flashloan_struct(
flashloan_struct, flashloan_fee_amt = tx_route_handler.generate_flashloan_struct(
trade_instructions_objects=calculated_trade_instructions
)

Expand All @@ -981,7 +987,7 @@ def _handle_trade_instructions(

# Use helper function to calculate profit
best_profit_fl_token, best_profit_eth, best_profit_usd = self.calculate_profit(
CCm, best_profit, fl_token
CCm, best_profit, fl_token, flashloan_fee_amt
)

# Log the best trade instructions
Expand Down
13 changes: 13 additions & 0 deletions fastlane_bot/config/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
This file constains the constants used in the fastlane_bot package.

(c) Copyright Bprotocol foundation 2023.
Licensed under MIT License.
"""

FLASHLOAN_FEE_MAP = {
# should be in the form of {network: fee * 1e6}
"ethereum": 0,
"coinbase_base": 0,
"fantom": 300, # Flashloans come with a 0.03% Fee from Beethoven X (Balancer fork)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why represent percentage like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this not a standard approach in the industry, to specify in PPM? In any case, just following what we did in the simulator, and as well how Uniswap does it, etc. Happy to change if there is a more preferred method.

Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. It is not standard, but an implementation-decision (typically per DEX).
  2. Unless this is related to something the you need to process before passing as input to a contract function or after receiving as output from a contract function, there is no reason for you to use this resolution to begin with; the only reason for it being used onchain, is the lack of non-integer-arithmetic support, which is obviously not an issue in offchain scripts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case, what is your preferred implementation approach / alternative?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not just use 0.0003 (i.e., multiply by that value wherever needed)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd go with the most obvious - either 0.0003 # 0.03% or 0.03 # %

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been updated.

Copy link
Collaborator

@barakman barakman Feb 13, 2024

Choose a reason for hiding this comment

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

An important note to make here is that Mike's original decision to use a PPM factor, was actually a good one in the sense of avoiding floating-point calculations as much as possible (i.e., the calculation would subsequently turn into amount * fee // 1e6, which is purely integer-arithmetic).
But not critical at this point, especially after he's already changed that.

}
12 changes: 8 additions & 4 deletions fastlane_bot/helpers/routehandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import pandas as pd

from .tradeinstruction import TradeInstruction
from ..config.constants import FLASHLOAN_FEE_MAP
from ..events.interface import Pool
from ..tools.cpc import T

Expand Down Expand Up @@ -501,13 +502,13 @@ def get_custom_address(
return pool.tkn0_address


def generate_flashloan_struct(self, trade_instructions_objects: List[TradeInstruction]) -> List:
def generate_flashloan_struct(self, trade_instructions_objects: List[TradeInstruction]) -> Tuple[List, int]:
barakman marked this conversation as resolved.
Show resolved Hide resolved
"""
Generates the flashloan struct for submitting FlashLoanAndArbV2 transactions
:param trade_instructions_objects: a list of TradeInstruction objects

:return:
int
Tuple[List, int], the flashloan struct and the flashloan fee amount
"""
return self._get_flashloan_struct(trade_instructions_objects=trade_instructions_objects)

Expand All @@ -529,7 +530,7 @@ def _get_flashloan_platform_id(self, tkn: str) -> int:
else:
return 7

def _get_flashloan_struct(self, trade_instructions_objects: List[TradeInstruction]) -> List:
def _get_flashloan_struct(self, trade_instructions_objects: List[TradeInstruction]) -> Tuple[List, int]:
Copy link
Contributor

@zavelevsky zavelevsky Feb 11, 2024

Choose a reason for hiding this comment

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

where do you actually return the fee? I can't find where you add the fee to the Tuple

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be corrected now.

"""
Turns an object containing trade instructions into a struct with flashloan tokens and amounts ready to send to the smart contract.
:param flash_tokens: an object containing flashloan tokens in the format {tkn: {"tkn": tkn_address, "flash_amt": tkn_amt}}
Expand All @@ -538,10 +539,13 @@ def _get_flashloan_struct(self, trade_instructions_objects: List[TradeInstructio
flashloans = []
balancer = {"platformId": 7, "sourceTokens": [], "sourceAmounts": []}
has_balancer = False
flashloan_fee_amt = 0
flashloan_fee = FLASHLOAN_FEE_MAP.get(self.ConfigObj.NETWORK, 0)
for tkn in flash_tokens.keys():
platform_id = self._get_flashloan_platform_id(tkn)
source_token = flash_tokens[tkn]["tkn"]
source_amounts = abs(flash_tokens[tkn]["flash_amt"])
flashloan_fee_amt += int(flashloan_fee * source_amounts // 1e6) if flashloan_fee > 0 else 0
if platform_id == 7:
has_balancer = True
balancer["sourceTokens"].append(source_token)
Expand All @@ -553,7 +557,7 @@ def _get_flashloan_struct(self, trade_instructions_objects: List[TradeInstructio
if has_balancer:
flashloans.append(balancer)

return flashloans
return flashloans, flashloan_fee_amt

def native_gas_token_to_wrapped(self, tkn: str):
"""
Expand Down
Loading