Skip to content

Commit

Permalink
Merge pull request #338 from bancorprotocol/336-transactions-on-base-…
Browse files Browse the repository at this point in the history
…and-other-optimism-forks-should-account-for-l1-gas-fee

Add L1 gas fee calculation on Optimism & forks
  • Loading branch information
mikewcasale authored Feb 1, 2024
2 parents aee1755 + 7b0346c commit 64e46e8
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 15 deletions.
3 changes: 3 additions & 0 deletions fastlane_bot/config/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ class ConfigNetwork(ConfigBase):
PLATFORM_NAME_WRAP_UNWRAP = "wrap_or_unwrap"
PLATFORM_ID_WRAP_UNWRAP = 10

GAS_ORACLE_ADDRESS = None

CARBON_V1_FORKS = [CARBON_V1_NAME]

Expand Down Expand Up @@ -570,6 +571,7 @@ class _ConfigNetworkOptimism(ConfigNetwork):
RPC_ENDPOINT = "https://opt-mainnet.g.alchemy.com/v2/"
WEB3_ALCHEMY_PROJECT_ID = os.environ.get("WEB3_ALCHEMY_OPTIMISM")

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

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

GAS_ORACLE_ADDRESS = "0x4200000000000000000000000000000000000015"
network_df = get_multichain_addresses(network="coinbase_base")
FASTLANE_CONTRACT_ADDRESS = "0x2AE2404cD44c830d278f51f053a08F54b3756e1c"
MULTICALL_CONTRACT_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"
Expand Down
7 changes: 7 additions & 0 deletions fastlane_bot/config/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
BANCOR_V3_NETWORK_INFO_ABI,
CARBON_CONTROLLER_ABI,
FAST_LANE_CONTRACT_ABI,
GAS_ORACLE_ABI,
)

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

if network.GAS_ORACLE_ADDRESS:
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
64 changes: 49 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 @@ -30,7 +31,7 @@
# from fastlane_bot.tools.cpc import ConstantProductCurve
from fastlane_bot.config import Config
from fastlane_bot.data.abi import * # TODO: PRECISE THE IMPORTS or from .. import abi
from fastlane_bot.utils import num_format, log_format, num_format_float, int_prefix
from fastlane_bot.utils import num_format, log_format, num_format_float, int_prefix, count_bytes


@dataclass
Expand Down Expand Up @@ -375,13 +376,22 @@ 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.GAS_ORACLE_ADDRESS:
layer_one_gas_fee = self._get_layer_one_gas_fee_loop(signed_transaction=signed_arb_tx)
gas_cost_eth += layer_one_gas_fee

signed_arb_tx = signed_arb_tx.rawTransaction

# 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 +433,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 +744,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 +768,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 +810,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 +974,42 @@ 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)

def _get_layer_one_gas_fee_loop(self, signed_transaction) -> Decimal:
"""
Returns the expected layer one gas fee for a layer 2 Optimism transaction
:param signed_transaction: the signed ethereum TX
returns: Decimal
The total fee (in gas token) for the l1 gas fee
"""
return asyncio.get_event_loop().run_until_complete(self._get_layer_one_gas_fee(signed_transaction=signed_transaction))

async def _get_layer_one_gas_fee(self, signed_transaction) -> Decimal:
"""
Returns the expected layer one gas fee for a layer 2 Optimism transaction
: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.ConfigObj.GAS_ORACLE_CONTRACT.caller.basefee()
fixed_overhead = await self.ConfigObj.GAS_ORACLE_CONTRACT.caller.l1FeeOverhead()
dynamic_overhead = await self.ConfigObj.GAS_ORACLE_CONTRACT.caller.l1FeeScalar()
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
## Dividing by 10 ** 24 because dynamic_overhead is returned in PPM format, and to convert this from WEI format to decimal format (10 ** 18).
return Decimal(f"{l1_data_fee}e-24")



11 changes: 11 additions & 0 deletions fastlane_bot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import time
from _decimal import Decimal
from dataclasses import dataclass
from hexbytes import HexBytes
from typing import Tuple, List, Dict, Any

import pandas as pd
Expand Down Expand Up @@ -504,3 +505,13 @@ def find_latest_timestamped_folder(logging_path=None):


#%%
def count_bytes(data: HexBytes) -> (int, int):
"""
This function counts the number of zero and non-zero bytes in a given input data.
:param data: HexBytes
returns Tuple(int, int):
The zero & non zero count of bytes in the input
"""
zero_bytes = len([byte for byte in data if byte == 0])
non_zero_bytes = len(data) - zero_bytes
return zero_bytes, non_zero_bytes
Loading

0 comments on commit 64e46e8

Please sign in to comment.