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

Add L1 gas fee calculation on Optimism & forks #338

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
9 changes: 9 additions & 0 deletions fastlane_bot/config/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class ConfigNetwork(ConfigBase):
# USDT_KEY = "USDT-1ec7"
SELF_FUND = False

HAS_LAYER_ONE_GAS_FEE: bool
barakman marked this conversation as resolved.
Show resolved Hide resolved
# ACCOUNTS SECTION
#######################################################################################
BINANCE8_WALLET_ADDRESS = "0xF977814e90dA44bFA03b6295A0616a897441aceC"
Expand Down Expand Up @@ -421,6 +422,7 @@ class _ConfigNetworkMainnet(ConfigNetwork):
DEFAULT_PROVIDER = S.PROVIDER_ALCHEMY
RPC_ENDPOINT = "https://eth-mainnet.alchemyapi.io/v2/"
WEB3_ALCHEMY_PROJECT_ID = os.environ.get("WEB3_ALCHEMY_PROJECT_ID")
HAS_LAYER_ONE_GAS_FEE = False

MULTICALL_CONTRACT_ADDRESS = "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696"
# NATIVE_GAS_TOKEN_KEY = "ETH-EEeE"
Expand Down Expand Up @@ -468,6 +470,7 @@ class _ConfigNetworkArbitrumOne(ConfigNetwork):
RPC_ENDPOINT = "https://arb-mainnet.g.alchemy.com/v2/"
WEB3_ALCHEMY_PROJECT_ID = os.environ.get("WEB3_ALCHEMY_ARBITRUM")

HAS_LAYER_ONE_GAS_FEE = False
FASTLANE_CONTRACT_ADDRESS = "" # TODO
MULTICALL_CONTRACT_ADDRESS = "" # TODO

Expand Down Expand Up @@ -502,6 +505,7 @@ class _ConfigNetworkPolygon(ConfigNetwork):
RPC_ENDPOINT = "https://polygon-mainnet.g.alchemy.com/v2/"
WEB3_ALCHEMY_PROJECT_ID = os.environ.get("WEB3_ALCHEMY_POLYGON")

HAS_LAYER_ONE_GAS_FEE = False
FASTLANE_CONTRACT_ADDRESS = "" # TODO
MULTICALL_CONTRACT_ADDRESS = "" # TODO

Expand Down Expand Up @@ -538,6 +542,7 @@ class _ConfigNetworkPolygonZkevm(ConfigNetwork):
RPC_ENDPOINT = "https://polygonzkevm-mainnet.g.alchemy.com/v2/"
WEB3_ALCHEMY_PROJECT_ID = os.environ.get("WEB3_ALCHEMY_POLYGON_ZKEVM")

HAS_LAYER_ONE_GAS_FEE = False
FASTLANE_CONTRACT_ADDRESS = "" # TODO
MULTICALL_CONTRACT_ADDRESS = "" # TODO
# NATIVE_GAS_TOKEN_KEY = "ETH-EEeE"
Expand Down Expand Up @@ -570,6 +575,8 @@ class _ConfigNetworkOptimism(ConfigNetwork):
RPC_ENDPOINT = "https://opt-mainnet.g.alchemy.com/v2/"
WEB3_ALCHEMY_PROJECT_ID = os.environ.get("WEB3_ALCHEMY_OPTIMISM")

HAS_LAYER_ONE_GAS_FEE = True
GAS_ORACLE_ADDRESS = "0x4200000000000000000000000000000000000015"
FASTLANE_CONTRACT_ADDRESS = "" # TODO
MULTICALL_CONTRACT_ADDRESS = "" # TODO

Expand Down Expand Up @@ -606,6 +613,8 @@ class _ConfigNetworkBase(ConfigNetwork):
RPC_ENDPOINT = "https://base-mainnet.g.alchemy.com/v2/"
WEB3_ALCHEMY_PROJECT_ID = os.environ.get("WEB3_ALCHEMY_BASE")

HAS_LAYER_ONE_GAS_FEE = True
GAS_ORACLE_ADDRESS = "0x4200000000000000000000000000000000000015"
network_df = get_multichain_addresses(network="coinbase_base")
FASTLANE_CONTRACT_ADDRESS = "0x2AE2404cD44c830d278f51f053a08F54b3756e1c"
MULTICALL_CONTRACT_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"
Expand Down
8 changes: 7 additions & 1 deletion fastlane_bot/config/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from ..data.abi import (
BANCOR_V3_NETWORK_INFO_ABI,
CARBON_CONTROLLER_ABI,
FAST_LANE_CONTRACT_ABI,
FAST_LANE_CONTRACT_ABI, GAS_ORACLE_ABI,
barakman marked this conversation as resolved.
Show resolved Hide resolved
)

ETH_PRIVATE_KEY_BE_CAREFUL = os.environ.get("ETH_PRIVATE_KEY_BE_CAREFUL")
Expand Down Expand Up @@ -118,6 +118,12 @@ def __init__(self, network: ConfigNetwork, **kwargs):
abi=FAST_LANE_CONTRACT_ABI,
)

if network.HAS_LAYER_ONE_GAS_FEE:
self.GAS_ORACLE_CONTRACT = self.w3.eth.contract(
address=network.GAS_ORACLE_ADDRESS,
abi=GAS_ORACLE_ABI
)


if network.NETWORK in N.NETWORK_ETHEREUM:
self.BANCOR_NETWORK_INFO_CONTRACT = self.w3.eth.contract(
Expand Down
6 changes: 6 additions & 0 deletions fastlane_bot/data/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,12 @@
"""
)

GAS_ORACLE_ABI = json.loads(
"""
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DEPOSITOR_ACCOUNT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"basefee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"batcherHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"l1FeeOverhead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"l1FeeScalar","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"number","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sequenceNumber","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"_number","type":"uint64"},{"internalType":"uint64","name":"_timestamp","type":"uint64"},{"internalType":"uint256","name":"_basefee","type":"uint256"},{"internalType":"bytes32","name":"_hash","type":"bytes32"},{"internalType":"uint64","name":"_sequenceNumber","type":"uint64"},{"internalType":"bytes32","name":"_batcherHash","type":"bytes32"},{"internalType":"uint256","name":"_l1FeeOverhead","type":"uint256"},{"internalType":"uint256","name":"_l1FeeScalar","type":"uint256"}],"name":"setL1BlockValues","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"timestamp","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]
"""
)


# @formatter:on
# fmt: on
77 changes: 62 additions & 15 deletions fastlane_bot/helpers/txhelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
__VERSION__ = "1.0"
__DATE__ = "01/May/2023"

import asyncio
from _decimal import Decimal

# import itertools
Expand All @@ -22,7 +23,7 @@
from alchemy import Network, Alchemy
from web3 import Web3
from web3.exceptions import TimeExhausted
from web3.types import TxReceipt
from web3.types import TxReceipt, HexBytes

# from fastlane_bot.config import * # TODO: PRECISE THE IMPORTS or from .. import config
# from fastlane_bot.db.models import Token, Pool
Expand Down Expand Up @@ -375,13 +376,20 @@ def validate_and_submit_transaction(
else:
current_gas_price = arb_tx["gasPrice"]

signed_arb_tx = self.sign_transaction(arb_tx)


# Multiply expected gas by 0.8 to account for actual gas usage vs expected.
gas_cost_eth = (
Decimal(str(current_gas_price))
* Decimal(str(gas_estimate))
* Decimal(self.ConfigObj.EXPECTED_GAS_MODIFIER)
/ Decimal("10") ** Decimal("18")
)
if self.ConfigObj.network.HAS_LAYER_ONE_GAS_FEE:
layer_one_gas_fee = asyncio.get_event_loop().run_until_complete(self.get_layer_one_gas_fee(signed_transaction=signed_arb_tx))
barakman marked this conversation as resolved.
Show resolved Hide resolved
gas_cost_eth += layer_one_gas_fee

# Gas cost in usd can be estimated using the profit usd/eth rate
gas_cost_usd = gas_cost_eth * expected_profit_usd / expected_profit_eth
# Multiply by reward percentage, taken from the arb contract
Expand Down Expand Up @@ -423,13 +431,13 @@ def validate_and_submit_transaction(

# Submit the transaction
if "tenderly" in self.web3.provider.endpoint_uri:
tx_hash = self.submit_transaction(arb_tx=arb_tx)
tx_hash = self.submit_transaction(signed_arb_tx=signed_arb_tx)
elif self.ConfigObj.NETWORK in "ethereum":
tx_hash = self.submit_private_transaction(
arb_tx=arb_tx, block_number=block_number
signed_arb_tx=signed_arb_tx, block_number=block_number
)
else:
tx_hash = self.submit_transaction(arb_tx=arb_tx)
tx_hash = self.submit_transaction(signed_arb_tx=signed_arb_tx)
self.ConfigObj.logger.info(
f"[helpers.txhelpers.validate_and_submit_transaction] Arbitrage executed, tx hash: {tx_hash}"
)
Expand Down Expand Up @@ -734,19 +742,15 @@ def build_tx(
if value is not None:
tx_details["value"] = value
return tx_details
def submit_transaction(self, arb_tx: Dict) -> Any:
def submit_transaction(self, signed_arb_tx: Dict) -> Any:
"""
Submits the transaction to the blockchain.

arb_tx: the transaction to be submitted to the blockchain

returns: the transaction hash of the submitted transaction
"""
self.ConfigObj.logger.info(
f"[helpers.txhelpers.submit_transaction] Attempting to submit tx {arb_tx}"
)

signed_arb_tx = self.sign_transaction(arb_tx)
self.ConfigObj.logger.info(
f"[helpers.txhelpers.submit_transaction] Attempting to submit tx {signed_arb_tx}"
)
Expand All @@ -762,19 +766,18 @@ def submit_transaction(self, arb_tx: Dict) -> Any:
)
return None

def submit_private_transaction(self, arb_tx, block_number: int) -> Any:
def submit_private_transaction(self, signed_arb_tx, block_number: int) -> Any:
"""
Submits the transaction privately through Alchemy -> Flashbots RPC to mitigate frontrunning.

:param arb_tx: the transaction to be submitted to the blockchain
:param signed_arb_tx: the signed arb transaction to be submitted to the blockchain
:param block_number: the current block number

returns: The transaction receipt, or None if the transaction failed
"""
self.ConfigObj.logger.info(
f"[helpers.txhelpers.submit_private_transaction] Attempting to submit tx to Flashbots, please hold."
)
signed_arb_tx = self.sign_transaction(arb_tx).rawTransaction
signed_tx_string = signed_arb_tx.hex()

max_block_number = hex(block_number + 10)
Expand Down Expand Up @@ -805,7 +808,7 @@ def submit_private_transaction(self, arb_tx, block_number: int) -> Any:
self.ConfigObj.logger.info(
f"[helpers.txhelpers.submit_private_transaction] Transaction stuck in mempool for 120 seconds, cancelling."
)
self.cancel_private_transaction(arb_tx, block_number)
self.cancel_private_transaction(signed_arb_tx, block_number)
return None
else:
self.ConfigObj.logger.info(
Expand Down Expand Up @@ -969,13 +972,57 @@ def approve_token_for_arb_contract(self, token_address: str, approval_amount: in
base_gas_price=baseFee, max_priority_fee=max_priority, nonce=self.get_nonce()
)
)
approve_tx = self.sign_transaction(approve_tx)
self.ConfigObj.logger.info(f"Submitting approval for token: {token_address}")
return self.submit_transaction(approve_tx)
except Exception as e:
self.ConfigObj.logger.info(
f"(***2***) Error when building transaction: {e.__class__.__name__} {e}")
else:
return None
self.ConfigObj.logger.info(f"Submitting approval for token: {token_address}")
return self.submit_transaction(approve_tx)

async def get_layer_one_gas_fee(self, signed_transaction) -> Decimal:
Lesigh-3100 marked this conversation as resolved.
Show resolved Hide resolved
"""
Returns the expected layer one gas fee for a layer 2 Optimism transaction
barakman marked this conversation as resolved.
Show resolved Hide resolved
:param signed_transaction: the signed ethereum TX

returns: Decimal
The total fee (in gas token) for the l1 gas fee
"""
ethereum_base_fee = await self._get_layer_one_gas_price()
fixed_overhead = await self._get_layer_one_fee_overhead()
dynamic_overhead = await self._get_layer_one_fee_scalar()
barakman marked this conversation as resolved.
Show resolved Hide resolved
zero_bytes, non_zero_bytes = count_bytes(signed_transaction["rawTransaction"])
tx_data_gas = zero_bytes * 4 + non_zero_bytes * 16
tx_total_gas = (tx_data_gas + fixed_overhead) * dynamic_overhead
l1_data_fee = tx_total_gas * ethereum_base_fee
l1_data_fee = Decimal(str(l1_data_fee)) / Decimal("1000000") / Decimal(str(10 ** 18))
barakman marked this conversation as resolved.
Show resolved Hide resolved
return l1_data_fee

async def _get_layer_one_gas_price(self):
"""
Returns the layer one base fee from the Layer 2 gas oracle for Optimism blockchains
"""
return self.ConfigObj.GAS_ORACLE_CONTRACT.caller.basefee()
barakman marked this conversation as resolved.
Show resolved Hide resolved
async def _get_layer_one_fee_overhead(self):
"""
Returns the layer one fee overhead from the Layer 2 gas oracle for Optimism blockchains
"""
return self.ConfigObj.GAS_ORACLE_CONTRACT.caller.l1FeeOverhead()
barakman marked this conversation as resolved.
Show resolved Hide resolved
async def _get_layer_one_fee_scalar(self):
"""
Returns the layer one fee overhead from the Layer 2 gas oracle for Optimism blockchains
"""
return self.ConfigObj.GAS_ORACLE_CONTRACT.caller.l1FeeScalar()
barakman marked this conversation as resolved.
Show resolved Hide resolved

Lesigh-3100 marked this conversation as resolved.
Show resolved Hide resolved

def count_bytes(data: HexBytes) -> (int, int):
"""
:param data: HexBytes
This is the HexBytes object from a signed TX
barakman marked this conversation as resolved.
Show resolved Hide resolved
returns Tuple(int, int):
The zero & non zero count of bytes in a transaction
barakman marked this conversation as resolved.
Show resolved Hide resolved
"""
zero_bytes = len([byte for byte in data if byte == 0])
non_zero_bytes = len(data) - zero_bytes
return zero_bytes, non_zero_bytes
barakman marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading