diff --git a/mev_inspect/abis/1inch/AggregationRouterV3.json b/mev_inspect/abis/1inch/AggregationRouterV3.json new file mode 100644 index 0000000..ceedb4b --- /dev/null +++ b/mev_inspect/abis/1inch/AggregationRouterV3.json @@ -0,0 +1 @@ +[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"Error","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"contract IERC20","name":"srcToken","type":"address"},{"indexed":false,"internalType":"contract IERC20","name":"dstToken","type":"address"},{"indexed":false,"internalType":"address","name":"dstReceiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"spentAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"returnAmount","type":"uint256"}],"name":"Swapped","type":"event"},{"inputs":[],"name":"destroy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IAggregationExecutor","name":"caller","type":"address"},{"components":[{"internalType":"contract IERC20","name":"srcToken","type":"address"},{"internalType":"contract IERC20","name":"dstToken","type":"address"},{"internalType":"address","name":"srcReceiver","type":"address"},{"internalType":"address","name":"dstReceiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minReturnAmount","type":"uint256"},{"internalType":"uint256","name":"flags","type":"uint256"},{"internalType":"bytes","name":"permit","type":"bytes"}],"internalType":"struct AggregationRouterV3.SwapDescription","name":"desc","type":"tuple"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"discountedSwap","outputs":[{"internalType":"uint256","name":"returnAmount","type":"uint256"},{"internalType":"uint256","name":"gasLeft","type":"uint256"},{"internalType":"uint256","name":"chiSpent","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IAggregationExecutor","name":"caller","type":"address"},{"components":[{"internalType":"contract IERC20","name":"srcToken","type":"address"},{"internalType":"contract IERC20","name":"dstToken","type":"address"},{"internalType":"address","name":"srcReceiver","type":"address"},{"internalType":"address","name":"dstReceiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minReturnAmount","type":"uint256"},{"internalType":"uint256","name":"flags","type":"uint256"},{"internalType":"bytes","name":"permit","type":"bytes"}],"internalType":"struct AggregationRouterV3.SwapDescription","name":"desc","type":"tuple"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[{"internalType":"uint256","name":"returnAmount","type":"uint256"},{"internalType":"uint256","name":"gasLeft","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"srcToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minReturn","type":"uint256"},{"internalType":"bytes32[]","name":"","type":"bytes32[]"}],"name":"unoswap","outputs":[{"internalType":"uint256","name":"returnAmount","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"srcToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minReturn","type":"uint256"},{"internalType":"bytes32[]","name":"pools","type":"bytes32[]"},{"internalType":"bytes","name":"permit","type":"bytes"}],"name":"unoswapWithPermit","outputs":[{"internalType":"uint256","name":"returnAmount","type":"uint256"}],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}] diff --git a/mev_inspect/classifiers/specs/__init__.py b/mev_inspect/classifiers/specs/__init__.py index 8473964..22da31c 100644 --- a/mev_inspect/classifiers/specs/__init__.py +++ b/mev_inspect/classifiers/specs/__init__.py @@ -10,6 +10,7 @@ from .compound import COMPOUND_CLASSIFIER_SPECS from .cryptopunks import CRYPTOPUNKS_CLASSIFIER_SPECS from .curve import CURVE_CLASSIFIER_SPECS from .erc20 import ERC20_CLASSIFIER_SPECS +from .one_inch import ONE_INCH_CLASSIFIER_SPECS from .opensea import OPENSEA_CLASSIFIER_SPECS from .uniswap import UNISWAP_CLASSIFIER_SPECS from .weth import WETH_ADDRESS, WETH_CLASSIFIER_SPECS @@ -27,6 +28,7 @@ ALL_CLASSIFIER_SPECS = ( + CRYPTOPUNKS_CLASSIFIER_SPECS + OPENSEA_CLASSIFIER_SPECS + BANCOR_CLASSIFIER_SPECS + + ONE_INCH_CLASSIFIER_SPECS ) _SPECS_BY_ABI_NAME_AND_PROTOCOL: Dict[ diff --git a/mev_inspect/classifiers/specs/one_inch.py b/mev_inspect/classifiers/specs/one_inch.py new file mode 100644 index 0000000..1081491 --- /dev/null +++ b/mev_inspect/classifiers/specs/one_inch.py @@ -0,0 +1,60 @@ +from typing import List, Optional + +from mev_inspect.schemas.classifiers import ClassifierSpec, SwapClassifier +from mev_inspect.schemas.swaps import Swap +from mev_inspect.schemas.traces import DecodedCallTrace, Protocol +from mev_inspect.schemas.transfers import Transfer + + +class OneInchSwapClassifier(SwapClassifier): + @staticmethod + def parse_swap( + trace: DecodedCallTrace, + prior_transfers: List[Transfer], + child_transfers: List[Transfer], + ) -> Optional[Swap]: + if trace.error is not None: + return None + + desc = trace.inputs["desc"] + [srcToken, dstToken, srcReceiver, dstReceiver, amountIn, *_] = desc + + transfer_out_candidates = [ + transfer + for transfer in child_transfers + if ( + transfer.token_address == dstToken + and transfer.to_address == dstReceiver + ) + ] + + if len(transfer_out_candidates) == 0: + raise RuntimeError("1inch expected at least one transfer out") + + return Swap( + abi_name=trace.abi_name, + transaction_hash=trace.transaction_hash, + transaction_position=trace.transaction_position, + block_number=trace.block_number, + trace_address=trace.trace_address, + contract_address=trace.to_address, + protocol=trace.protocol, + from_address=srcReceiver, + to_address=dstReceiver, + token_in_address=srcToken, + token_in_amount=amountIn, + token_out_address=dstToken, + token_out_amount=transfer_out_candidates[0].amount, + error=trace.error, + ) + + +ONE_INCH_ROUTER_SPEC = ClassifierSpec( + abi_name="AggregationRouterV3", + protocol=Protocol.one_inch, + classifiers={ + "swap(address,(address,address,address,address,uint256,uint256,uint256,bytes),bytes)": OneInchSwapClassifier, + }, +) + +ONE_INCH_CLASSIFIER_SPECS = [ONE_INCH_ROUTER_SPEC] diff --git a/mev_inspect/schemas/traces.py b/mev_inspect/schemas/traces.py index 68c1592..0664bd1 100644 --- a/mev_inspect/schemas/traces.py +++ b/mev_inspect/schemas/traces.py @@ -50,6 +50,7 @@ class Protocol(Enum): cryptopunks = "cryptopunks" bancor = "bancor" opensea = "opensea" + one_inch = "1inch" class ClassifiedTrace(Trace):