Skip to content

Commit

Permalink
[Multi-Chain] Add config file (#412)
Browse files Browse the repository at this point in the history
This PR adds a config file. This configuration is supposed to be
extended to other chains in future PRs.

This PR adds a config `AccountingConfig` which consists of multple
configurations for different aspects of the accounting.

The config is used as a global config. At the moment it is not
explicitly passed to any function.

Next steps:
- Pass config explicitly to functions.
- Make some of the code more general to allow for using it on other
chains.
- Add support for other networks. (At the moment I only copied the
testing setup on other chains from the original code.)
- Make config more consistent.

There are still hardcoded constants somewhere in the code. One place is
when fetching prices in converting token values, via an ID. The id
cannot be part of the config due to circular imports. Instead, an
address or id string should be passed and the pricing code should turn
it into an appropriate type.

### How to review
The first two commits (the names are wrong, I might force push correct
ones) add a config and use the config throughout the code. The third
commit just removes the old constants file.

One discussion should mostly be about the first commit, i.e. the one
adding the configuration classes in `config.py`.
- Should the config be structured differently?
- Do we need special treatment of secrets?

Another discussion would be around how to move forward from here.
- Should we pass the config struct more explicitly? (That is what I did
in the more complete code I have.)
- Which parameters are probably still missing for making it work on
other chains?

---------

Co-authored-by: Haris Angelidakis <[email protected]>
  • Loading branch information
fhenneke and harisang authored Nov 15, 2024
1 parent 47fba95 commit 0870ebf
Show file tree
Hide file tree
Showing 16 changed files with 417 additions and 170 deletions.
9 changes: 3 additions & 6 deletions src/abis/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@
# TODO - following this issue: https://github.com/ethereum/web3.py/issues/3017
from web3.contract import Contract # type: ignore

from src.constants import PROJECT_ROOT
from src.config import config
from src.logger import set_log

ABI_PATH = PROJECT_ROOT / Path("src/abis")

WETH9_ADDRESS = Web3().to_checksum_address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")

ABI_PATH = config.io_config.project_root_dir / Path("src/abis")

log = set_log(__name__)

Expand Down Expand Up @@ -65,7 +62,7 @@ def get_contract(

def weth9(web3: Optional[Web3] = None) -> Contract | Type[Contract]:
"""Returns an instance of WETH9 Contract"""
return IndexedContract.WETH9.get_contract(web3, WETH9_ADDRESS)
return IndexedContract.WETH9.get_contract(web3, config.payment_config.weth_address)


def erc20(
Expand Down
296 changes: 296 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
"""Config for solver accounting."""

from __future__ import annotations

import os
from dataclasses import dataclass
from enum import Enum
from fractions import Fraction
from pathlib import Path

from eth_typing.evm import ChecksumAddress
from dotenv import load_dotenv
from dune_client.types import Address
from gnosis.eth.ethereum_network import EthereumNetwork
from web3 import Web3

load_dotenv()


class Network(Enum):
"""Network class for networks supported by the accounting."""

MAINNET = "mainnet"
GNOSIS = "gnosis"
ARBITRUM_ONE = "arbitrum"
BASE = "base"


@dataclass(frozen=True)
class RewardConfig:
"""Configuration for reward mechanism."""

reward_token_address: Address
cow_bonding_pool: Address
batch_reward_cap_upper: int
batch_reward_cap_lower: int
quote_reward_cow: int
quote_reward_cap_native: int
service_fee_factor: Fraction

@staticmethod
def from_network(network: Network) -> RewardConfig:
"""Initialize reward config for a given network."""
match network:
case Network.MAINNET:
return RewardConfig(
reward_token_address=Address(
"0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"
),
batch_reward_cap_upper=12 * 10**15,
batch_reward_cap_lower=10 * 10**15,
quote_reward_cow=6 * 10**18,
quote_reward_cap_native=6 * 10**14,
service_fee_factor=Fraction(15, 100),
cow_bonding_pool=Address(
"0x5d4020b9261f01b6f8a45db929704b0ad6f5e9e6"
),
)
case _:
raise ValueError(f"No reward config set up for network {network}.")


@dataclass(frozen=True)
class ProtocolFeeConfig:
"""Configuration for protocol and partner fees.
Attributes:
protocol_fee_safe -- address to forward protocol fees to
partner_fee_cut -- fraction of partner fees withheld from integration partners
partner_fee_reduced_cut -- reduced amount withheld from partner specified as reduced_cut_address
reduced_cut_address -- partner fee recipient who pays the reduced cut partner_fee_reduced_cut
"""

protocol_fee_safe: Address
partner_fee_cut: float
partner_fee_reduced_cut: float
reduced_cut_address: str

@staticmethod
def from_network(network: Network) -> ProtocolFeeConfig:
"""Initialize protocol fee config for a given network."""
match network:
case Network.MAINNET:
return ProtocolFeeConfig(
protocol_fee_safe=Address(
"0xB64963f95215FDe6510657e719bd832BB8bb941B"
),
partner_fee_cut=0.15,
partner_fee_reduced_cut=0.10,
reduced_cut_address="0x63695Eee2c3141BDE314C5a6f89B98E62808d716",
)
case _:
raise ValueError(
f"No protocol fee config set up for network {network}."
)


@dataclass(frozen=True)
class BufferAccountingConfig:
"""Configuration for buffer accounting."""

include_slippage: bool

@staticmethod
def from_network(network: Network) -> BufferAccountingConfig:
"""Initialize buffer accounting config for a given network."""
match network:
case Network.MAINNET:
return BufferAccountingConfig(include_slippage=True)
case _:
raise ValueError(
f"No buffer accounting config set up for network {network}."
)


@dataclass(frozen=True)
class OrderbookConfig:
"""Configuration for orderbook fetcher"""

prod_db_url: str
barn_db_url: str

@staticmethod
def from_env() -> OrderbookConfig:
"""Initialize orderbook config from environment variables."""
prod_db_url = os.environ.get("PROD_DB_URL", "")
barn_db_url = os.environ.get("BARN_DB_URL", "")

return OrderbookConfig(prod_db_url=prod_db_url, barn_db_url=barn_db_url)


@dataclass(frozen=True)
class DuneConfig:
"""Configuration for DuneFetcher."""

dune_api_key: str
dune_blockchain: str

@staticmethod
def from_network(network: Network) -> DuneConfig:
"""Initialize dune config for a given network."""
dune_api_key = os.environ.get("DUNE_API_KEY", "")
match network:
case Network.MAINNET:
return DuneConfig(dune_api_key=dune_api_key, dune_blockchain="ethereum")
case _:
raise ValueError(f"No dune config set up for network {network}.")


@dataclass(frozen=True)
class NodeConfig:
"""Configuration for web3 node."""

node_url: str

@staticmethod
def from_network(network: Network) -> NodeConfig:
"""Initialize node config for a given network."""
match network:
case Network.MAINNET:
node_url = os.environ.get("NODE_URL", "")
case _:
raise ValueError(f"No node config set up for network {network}.")

return NodeConfig(node_url=node_url)


@dataclass(frozen=True)
class PaymentConfig:
"""Configuration of payment."""

# pylint: disable=too-many-instance-attributes

network: EthereumNetwork
cow_token_address: Address
payment_safe_address: ChecksumAddress
signing_key: str | None
safe_queue_url: str
verification_docs_url: str
weth_address: ChecksumAddress

@staticmethod
def from_network(network: Network) -> PaymentConfig:
"""Initialize payment config for a given network."""
signing_key = os.getenv("PROPOSER_PK")
if signing_key == "":
signing_key = None

docs_url = "https://www.notion.so/cownation/Solver-Payouts-3dfee64eb3d449ed8157a652cc817a8c"

network_short_name = {
Network.MAINNET: "eth",
Network.GNOSIS: "gno",
}

match network:
case Network.MAINNET:
payment_safe_address = Web3.to_checksum_address(
os.environ.get(
"SAFE_ADDRESS", "0xA03be496e67Ec29bC62F01a428683D7F9c204930"
)
)
short_name = network_short_name[network]
safe_url = (
f"https://app.safe.global/{short_name}:{payment_safe_address}"
)
safe_queue_url = f"{safe_url}/transactions/queue"

return PaymentConfig(
network=EthereumNetwork.MAINNET,
cow_token_address=Address(
"0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"
),
payment_safe_address=Web3.to_checksum_address(
"0xA03be496e67Ec29bC62F01a428683D7F9c204930"
),
signing_key=signing_key,
safe_queue_url=safe_queue_url,
verification_docs_url=docs_url,
weth_address=Web3.to_checksum_address(
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
),
)
case _:
raise ValueError(f"No payment config set up for network {network}.")


@dataclass(frozen=True)
class IOConfig:
"""Configuration of input and output."""

log_config_file: Path
project_root_dir: Path
query_dir: Path
csv_output_dir: Path
dashboard_dir: Path
slack_channel: str | None
slack_token: str | None

@staticmethod
def from_env() -> IOConfig:
"""Initialize io config from environment variables."""
slack_channel = os.getenv("SLACK_CHANNEL", None)
slack_token = os.getenv("SLACK_TOKEN", None)

project_root_dir = Path(__file__).parent.parent
file_out_dir = project_root_dir / Path("out")
log_config_file = project_root_dir / Path("logging.conf")
query_dir = project_root_dir / Path("queries")
dashboard_dir = project_root_dir / Path("dashboards/solver-rewards-accounting")

return IOConfig(
project_root_dir=project_root_dir,
log_config_file=log_config_file,
query_dir=query_dir,
csv_output_dir=file_out_dir,
dashboard_dir=dashboard_dir,
slack_channel=slack_channel,
slack_token=slack_token,
)


@dataclass(frozen=True)
class AccountingConfig:
"""Full configuration for solver accounting."""

# pylint: disable=too-many-instance-attributes

payment_config: PaymentConfig
orderbook_config: OrderbookConfig
dune_config: DuneConfig
node_config: NodeConfig
reward_config: RewardConfig
protocol_fee_config: ProtocolFeeConfig
buffer_accounting_config: BufferAccountingConfig
io_config: IOConfig

@staticmethod
def from_network(network: Network) -> AccountingConfig:
"""Initialize accounting config for a given network."""

return AccountingConfig(
payment_config=PaymentConfig.from_network(network),
orderbook_config=OrderbookConfig.from_env(),
dune_config=DuneConfig.from_network(network),
node_config=NodeConfig.from_network(network),
reward_config=RewardConfig.from_network(network),
protocol_fee_config=ProtocolFeeConfig.from_network(network),
buffer_accounting_config=BufferAccountingConfig.from_network(network),
io_config=IOConfig.from_env(),
)


config = AccountingConfig.from_network(Network(os.environ.get("NETWORK", "mainnet")))

web3 = Web3(Web3.HTTPProvider(config.node_config.node_url))
52 changes: 0 additions & 52 deletions src/constants.py

This file was deleted.

Loading

0 comments on commit 0870ebf

Please sign in to comment.