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

Revise the PR for issue 381 #431

Merged
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
44d6bed
Revision (ongoing work)
Mar 11, 2024
b3d57f4
Semantic
Mar 13, 2024
549fdbf
Fix the trade-splitter function
Mar 13, 2024
d3aaa31
Undo the previous commit
Mar 13, 2024
70f7b2a
Fix the trade-splitter
Mar 14, 2024
5bf7eb1
General fixes
Mar 14, 2024
a509265
Ongoing work
Mar 17, 2024
486e4cf
Ongoing work
Mar 17, 2024
3185de2
Improve documentation
Mar 17, 2024
b528ceb
Minor
Mar 17, 2024
c5f5176
Clean test
Mar 17, 2024
0ce9f0f
Ongoing work
Mar 17, 2024
4282190
Fix tests
Mar 17, 2024
e0656f5
Simplify tests
Mar 17, 2024
489e3e5
Improve test
Mar 17, 2024
7181dc0
Improve test
Mar 17, 2024
b027a1c
Improve documentation
Mar 17, 2024
96f90cf
Remove redundant code in tests
Mar 17, 2024
b0ec837
Remove jupyter tests 070 and 071
Mar 17, 2024
da3b346
Improve tests
Mar 17, 2024
f674e8a
Clean tests
Mar 18, 2024
a0e881c
Clean tests
Mar 18, 2024
ef18aa3
Clean test
Mar 18, 2024
1e5e589
Clean test
Mar 18, 2024
d063ae3
Update NBTest_070_TestCarbonTradeSplitter.py
Lesigh-3100 Mar 18, 2024
8fb1dc3
Rename 'id' to 'token_type'
Mar 18, 2024
42aaa51
Improve documentation
Mar 18, 2024
4c2574f
Improve the tests
Mar 18, 2024
71d47d2
Minor cleanup
Mar 18, 2024
6f39882
Minor cleanup
Mar 18, 2024
ce7cb6a
Update NBTest_070_TestCarbonTradeSplitter.py
Lesigh-3100 Mar 19, 2024
5e35d44
Clear potential confusion in the code which determines the value of `…
Mar 19, 2024
a6c8536
Rename `token_type` to `pool_type`
Mar 19, 2024
f752162
Fix docstring format
Mar 19, 2024
4c7d829
Improve documentation
Mar 19, 2024
2c4ad1d
Merge branch 'add-test-cases-to-revision' of https://github.com/banco…
Mar 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions fastlane_bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,15 @@
TxHelpersBase,
TradeInstruction,
Univ3Calculator,
RouteStruct, WrapUnwrapProcessor,
RouteStruct,
add_wrap_or_unwrap_trades_to_route,
split_carbon_trades
)
from fastlane_bot.helpers.routehandler import maximize_last_trade_per_tkn
from fastlane_bot.tools.cpc import ConstantProductCurve as CPC, CPCContainer, T
from fastlane_bot.tools.optimizer import CPCArbOptimizer
from .config.constants import FLASHLOAN_FEE_MAP
from .events.interface import QueryInterface
from .helpers.carbon_trade_splitter import CarbonTradeSplitter
from .modes.pairwise_multi import FindArbitrageMultiPairwise
from .modes.pairwise_multi_all import FindArbitrageMultiPairwiseAll
from .modes.pairwise_multi_pol import FindArbitrageMultiPairwisePol
Expand Down Expand Up @@ -1021,8 +1022,10 @@ def _handle_trade_instructions(
)

# Split Carbon Orders
split_calculated_trade_instructions = CarbonTradeSplitter(ConfigObj=self.ConfigObj).split_carbon_trades(
trade_instructions=calculated_trade_instructions)
split_calculated_trade_instructions = split_carbon_trades(
cfg=self.ConfigObj,
trade_instructions=calculated_trade_instructions
)

# Encode the trade instructions
encoded_trade_instructions = tx_route_handler.custom_data_encoder(
Expand All @@ -1040,7 +1043,12 @@ def _handle_trade_instructions(
)
]

route_struct_processed = WrapUnwrapProcessor(cfg=self.ConfigObj).add_wrap_or_unwrap_trades_to_route(trade_instructions=split_calculated_trade_instructions, route_struct=route_struct, flashloan_struct=flashloan_struct)
route_struct_processed = add_wrap_or_unwrap_trades_to_route(
cfg=self.ConfigObj,
flashloans=flashloan_struct,
routes=route_struct,
trade_instructions=split_calculated_trade_instructions,
)

route_struct_maximized = maximize_last_trade_per_tkn(route_struct=route_struct_processed)

Expand Down
5 changes: 2 additions & 3 deletions fastlane_bot/config/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,7 @@ class ConfigNetwork(ConfigBase):
VELOCIMETER_V1_NAME = "velocimeter_v1"
VELOCIMETER_V2_NAME = "velocimeter_v2"

PLATFORM_NAME_WRAP_UNWRAP = "wrap_or_unwrap"
PLATFORM_ID_WRAP_UNWRAP = 10
WRAP_UNWRAP_NAME = "wrap_or_unwrap"

GAS_ORACLE_ADDRESS = None

Expand Down Expand Up @@ -382,7 +381,7 @@ def __post_init__(self):
self.BANCOR_V3_NAME: 2,
self.BALANCER_NAME: 7,
self.CARBON_POL_NAME: 8,
self.PLATFORM_ID_WRAP_UNWRAP: 10,
self.WRAP_UNWRAP_NAME: 10,
self.UNISWAP_V2_NAME: 3,
self.UNISWAP_V3_NAME: 4,
self.SOLIDLY_V2_NAME: 11,
Expand Down
4 changes: 2 additions & 2 deletions fastlane_bot/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from .submithandler import TxSubmitHandler, TxSubmitHandlerBase
from .txhelpers import TxHelpers, TxHelper
from .univ3calc import Univ3Calculator
from .wrap_unwrap_processor import WrapUnwrapProcessor
from .carbon_trade_splitter import CarbonTradeSplitter
from .wrap_unwrap_processor import add_wrap_or_unwrap_trades_to_route
from .carbon_trade_splitter import split_carbon_trades
TxHelpersBase = TxHelpers


Expand Down
247 changes: 55 additions & 192 deletions fastlane_bot/helpers/carbon_trade_splitter.py
Original file line number Diff line number Diff line change
@@ -1,203 +1,66 @@
import json
barakman marked this conversation as resolved.
Show resolved Hide resolved
from typing import List, Dict

from json import loads, dumps
from fastlane_bot.helpers import TradeInstruction

def split_carbon_trades(cfg, trade_instructions: list[TradeInstruction]) -> list[TradeInstruction]:
"""
This method splits every trade instruction which includes a mix of gas tokens and/or a mix of Carbon deployments,
into several trade instructions. For example, `NATIVE/WRAPPED -> TKN` is split into `NATIVE -> TKN` and `WRAPPED -> TKN`.

class CarbonTradeSplitter:
NATIVE = "native"
WRAPPED = "wrapped"
NEITHER = "neither"

def __init__(self, ConfigObj):
self.ConfigObj = ConfigObj

def split_carbon_trades(
self, trade_instructions: List[TradeInstruction]
) -> List[TradeInstruction]:
"""Splits Carbon trades that cannot be aggregated into a single trade action.

This function split a single Carbon trade into multiple trades for cases that include a mix of tokens or different Carbon deployments.
For example NATIVE/WRAPPED -> TKN -> NATIVE -> TKN & WRAPPED -> TKN.

Args:
trade_instructions: The list of TradeInstruction objects.

Returns:
The processed list of TradeInstruction objects after splitting incompatible trades.

"""
new_trade_list = []
for trade in trade_instructions:
if not self._is_carbon_trade(trade):
new_trade_list.append(trade)
continue
new_trade_list.extend(self._extract_carbon_trades(trade))

return new_trade_list

def _is_carbon_trade(self, trade: TradeInstruction) -> bool:
"""Checks if a trade is on a Carbon deployment.

Args:
trade: a single TradeInstruction object.

Returns:
True if the trade is on a Carbon deployment. False otherwise.

"""
return trade.exchange_name in self.ConfigObj.CARBON_V1_FORKS

def _get_real_tkn(self, token_address: str, token_type: str):
"""Returns the correct token address for the trade.

This function returns the real token address for the pool. If the token isn't the native/wrapped gas token address, it just returns the token.
If the token is native/wrapped, it will use the token_type to return the correct address.

Args:
token_address: the token address
token_type: the self.NATIVE, self.WRAPPED, or SELF.NEITHER

Returns:
The correct token address for the pool.

"""
if token_address in [
self.ConfigObj.NATIVE_GAS_TOKEN_ADDRESS,
self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS,
]:
return self.ConfigObj.NATIVE_GAS_TOKEN_ADDRESS if token_type == self.NATIVE else self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS
return token_address

def _extract_carbon_trades(self, trade) -> List[TradeInstruction]:
"""
This function calls the functions containing the logic to extract Carbon trades.

Args:
trade: A single TradeInstruction object.

Returns:
A list of TradeInstruction objects.
Args:
- `trade_instructions`: A list of trade instructions.

"""
carbon_exchanges = self._process_carbon_trades(trade)
return self._create_new_trades_from_carbon_exchanges(
carbon_exchanges, trade
)
Returns:
- `new_trade_instructions`: A new list of trade instructions.
"""

def _process_carbon_trades(self, trade: TradeInstruction) -> Dict:
"""Separates Carbon trades by exchange & wrapped/native token address.
new_trade_instructions = []
for trade_instruction in trade_instructions:
if trade_instruction.exchange_name not in cfg.CARBON_V1_FORKS:
new_trade_instructions.append(trade_instruction)
continue

Processes Carbon trades and organizes them by exchange and token type.

Args:
trade: a single TradeInstruction object

Returns:
A dictionary containing a list of

"""
carbon_exchanges = {}

raw_tx_str = trade.raw_txs.replace("'", '"').replace('Decimal("', '').replace('")', '')
raw_txs = json.loads(raw_tx_str)

for _tx in raw_txs:
curve = trade.db.get_pool(cid=str(_tx["cid"]).split("-")[0])
exchange = curve.exchange_name
token_type = self._get_token_type(curve)

if exchange not in carbon_exchanges:
carbon_exchanges[exchange] = self._initialize_exchange_data()

self._update_exchange_data(
carbon_exchanges[exchange], token_type, _tx, trade
for tx in loads(trade_instruction.raw_txs.replace("'", '"').replace('Decimal("', '').replace('")', '')):
pool = trade_instruction.db.get_pool(cid=str(tx["cid"]).split("-")[0])

if cfg.NATIVE_GAS_TOKEN_ADDRESS in pool.get_tokens:
id = cfg.NATIVE_GAS_TOKEN_ADDRESS
mikewcasale marked this conversation as resolved.
Show resolved Hide resolved
elif cfg.WRAPPED_GAS_TOKEN_ADDRESS in pool.get_tokens:
id = cfg.WRAPPED_GAS_TOKEN_ADDRESS
else:
id = cfg.ZERO_ADDRESS

tx["tknin"] = _get_token_address(cfg, id, trade_instruction.tknin)
tx["tknout"] = _get_token_address(cfg, id, trade_instruction.tknout)

exchange_id = pool.exchange_name + id
if exchange_id in carbon_exchanges:
carbon_exchanges[exchange_id].append(tx)
else:
carbon_exchanges[exchange_id] = [tx]

for txs in carbon_exchanges.values():
new_trade_instructions.append(
TradeInstruction(
ConfigObj=cfg,
db=trade_instruction.db,
cid=txs[0]["cid"],
tknin=txs[0]["tknin"],
tknout=txs[0]["tknout"],
amtin=sum([tx["amtin"] for tx in txs]),
amtout=sum([tx["amtout"] for tx in txs]),
_amtin_wei=sum([tx["_amtin_wei"] for tx in txs]),
_amtout_wei=sum([tx["_amtout_wei"] for tx in txs]),
raw_txs=dumps(txs)
)
)

return carbon_exchanges

def _get_token_type(self, curve) -> str:
"""Determines if the trade uses native or wrapped gas tokens.

Args:
curve: the Pool object representing the curve.

Returns:
A string indicating if the curve contains native gas tokens, wrapped gas tokens, or neither.

"""
if self.ConfigObj.NATIVE_GAS_TOKEN_ADDRESS in curve.get_tokens:
return self.NATIVE
elif self.ConfigObj.WRAPPED_GAS_TOKEN_ADDRESS in curve.get_tokens:
return self.WRAPPED
return self.NEITHER

def _initialize_exchange_data(self) -> Dict:
"""Initializes data structure for a new exchange."""
return {
self.NATIVE: self._new_trade_data_structure(),
self.WRAPPED: self._new_trade_data_structure(),
self.NEITHER: self._new_trade_data_structure(),
}

def _new_trade_data_structure(self) -> Dict:
"""Initializes a new trade data structure."""
return {
"raw_txs": [],
"amtin": 0,
"amtout": 0,
"_amtin_wei": 0,
"_amtout_wei": 0,
}

def _update_exchange_data(
self, exchange_data: Dict, token_type: str, _tx: Dict, trade: TradeInstruction
):
"""Combines like-trades.

Update exchange data with information from a transaction.

Args:
exchange_data: The dictionary containing trades for each Carbon deployment being traded through.
token_type: a string indicating if the pool contains native/wrapped gas token, or neither.
_tx: the TX dictionary containing trade details.
trade: the TradeInstruction object.

"""
_tx["tknin"] = self._get_real_tkn(trade.tknin, token_type)
_tx["tknout"] = self._get_real_tkn(trade.tknout, token_type)

data = exchange_data[token_type]
data["raw_txs"].append(_tx)
data["amtin"] += _tx["amtin"]
data["amtout"] += _tx["amtout"]
data["_amtin_wei"] += _tx["_amtin_wei"]
data["_amtout_wei"] += _tx["_amtout_wei"]
data["tknin"] = _tx["tknin"]
data["tknout"] = _tx["tknout"]

def _create_new_trades_from_carbon_exchanges(
self,
carbon_exchanges: Dict,
original_trade: TradeInstruction,
):
"""Creates new TradeInstruction instances from processed Carbon exchanges data.

This function adds trades that were added as a result of splitting Carbon trades.

Args:
carbon_exchanges: The list of Carbon deployments being traded through in this trade.
original_trade: The original TradeInstruction object, utilized here to pass the db & Config objects.
new_trade_list: The updated list of TradeInstruction objects with any trades that were added from the splitting process.
return new_trade_instructions

"""
_extracted_trades = []
for data in carbon_exchanges.values():
for trade_data in data.values():
if trade_data["raw_txs"]:
trade_data["db"] = original_trade.db
trade_data["ConfigObj"] = original_trade.ConfigObj
trade_data["cid"] = trade_data["raw_txs"][0]["cid"]
trade_data["raw_txs"] = str(trade_data["raw_txs"])
_extracted_trades.append(TradeInstruction(**trade_data))
return _extracted_trades
def _get_token_address(cfg, id: str, token_address: str) -> str:
if id == cfg.NATIVE_GAS_TOKEN_ADDRESS and token_address == cfg.WRAPPED_GAS_TOKEN_ADDRESS:
return cfg.NATIVE_GAS_TOKEN_ADDRESS
if id == cfg.WRAPPED_GAS_TOKEN_ADDRESS and token_address == cfg.NATIVE_GAS_TOKEN_ADDRESS:
return cfg.WRAPPED_GAS_TOKEN_ADDRESS
return token_address
Loading