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

[Multi-Chain] Enable payments on other chains #425

Merged
merged 25 commits into from
Dec 5, 2024
Merged
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5966dba
add additional tokens to price fetching
fhenneke Nov 8, 2024
cdf9019
add blockchain parameter to dune fetcher
fhenneke Nov 26, 2024
1ce5d8e
rename weth to wrapped native token
fhenneke Nov 18, 2024
9f5eaee
add config for other chains
fhenneke Nov 8, 2024
26ffae0
change pull request node environment variable name
fhenneke Nov 18, 2024
9222211
fix bug with missing data
fhenneke Nov 26, 2024
504ad05
fix gnosis cap
fhenneke Nov 22, 2024
e48581c
use new vouching query
fhenneke Dec 2, 2024
36d79d2
fix time range
fhenneke Dec 2, 2024
f9ec687
add exception for missing vouches
fhenneke Dec 2, 2024
6a045cf
add log for native token exchange rate
fhenneke Dec 3, 2024
52afbd5
change name of payouts safe environment variable
fhenneke Dec 3, 2024
9606888
make protocol fee safe chain dependent
fhenneke Dec 3, 2024
3d6c205
update sample .env file
fhenneke Dec 4, 2024
b2b587e
fix bug in arbitrum payment network
fhenneke Dec 4, 2024
5761990
use chain agnostic environment variables
fhenneke Dec 4, 2024
cb2be14
change vouching query id to old query
fhenneke Dec 4, 2024
e2dc829
Merge branch 'main' into other_chains
fhenneke Dec 4, 2024
ca7d981
fix CI
fhenneke Dec 5, 2024
299e963
update payout safe addresss for testing
fhenneke Dec 5, 2024
a58bf93
change pull request env variable formating
fhenneke Dec 5, 2024
98323d7
add network to transfer file
fhenneke Dec 5, 2024
8d7cd14
lint fix
fhenneke Dec 5, 2024
b35b106
add network variable to github env variables
fhenneke Dec 5, 2024
424e89a
update .env.sample
fhenneke Dec 5, 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
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
@@ -27,4 +27,4 @@ jobs:
run:
python -m pytest tests/unit
env:
NODE_URL: https://rpc.ankr.com/eth
NODE_URL_MAINNET: https://rpc.ankr.com/eth
144 changes: 108 additions & 36 deletions src/config.py
Original file line number Diff line number Diff line change
@@ -41,6 +41,8 @@ class RewardConfig:
@staticmethod
def from_network(network: Network) -> RewardConfig:
"""Initialize reward config for a given network."""
cow_bonding_pool = Address("0x5d4020b9261f01b6f8a45db929704b0ad6f5e9e6")
service_fee_factor = Fraction(15, 100)
match network:
case Network.MAINNET:
return RewardConfig(
@@ -51,10 +53,32 @@ def from_network(network: Network) -> RewardConfig:
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"
service_fee_factor=service_fee_factor,
cow_bonding_pool=cow_bonding_pool,
)
case Network.GNOSIS:
return RewardConfig(
reward_token_address=Address(
"0x177127622c4a00f3d409b75571e12cb3c8973d3c"
),
batch_reward_cap_upper=30 * 10**18,
batch_reward_cap_lower=5 * 10**18,
quote_reward_cow=6 * 10**18,
quote_reward_cap_native=1 * 10**18,
service_fee_factor=service_fee_factor,
cow_bonding_pool=cow_bonding_pool,
)
case Network.ARBITRUM_ONE:
return RewardConfig(
reward_token_address=Address(
"0xcb8b5cd20bdcaea9a010ac1f8d835824f5c87a04"
),
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=service_fee_factor,
cow_bonding_pool=cow_bonding_pool,
)
case _:
raise ValueError(f"No reward config set up for network {network}.")
@@ -80,7 +104,7 @@ class ProtocolFeeConfig:
def from_network(network: Network) -> ProtocolFeeConfig:
"""Initialize protocol fee config for a given network."""
match network:
case Network.MAINNET:
case Network.MAINNET | Network.GNOSIS | Network.ARBITRUM_ONE:
Copy link
Contributor

@harisang harisang Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fhenneke This probably needs updating? I think we have different addresses per chain

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added appropriate addresses for each chain.

return ProtocolFeeConfig(
protocol_fee_safe=Address(
"0xB64963f95215FDe6510657e719bd832BB8bb941B"
@@ -106,12 +130,16 @@ def from_network(network: Network) -> BufferAccountingConfig:
"""Initialize buffer accounting config for a given network."""
match network:
case Network.MAINNET:
return BufferAccountingConfig(include_slippage=True)
include_slippage = True
case Network.GNOSIS | Network.ARBITRUM_ONE:
include_slippage = False
case _:
raise ValueError(
f"No buffer accounting config set up for network {network}."
)

return BufferAccountingConfig(include_slippage=include_slippage)


@dataclass(frozen=True)
class OrderbookConfig:
@@ -121,10 +149,20 @@ class OrderbookConfig:
barn_db_url: str

@staticmethod
def from_env() -> OrderbookConfig:
def from_network(network: Network) -> 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", "")
match network:
case Network.MAINNET:
prod_db_url = os.environ.get("PROD_DB_URL_MAINNET", "")
barn_db_url = os.environ.get("BARN_DB_URL_MAINNET", "")
case Network.GNOSIS:
prod_db_url = os.environ.get("PROD_DB_URL_GNOSIS", "")
barn_db_url = os.environ.get("BARN_DB_URL_GNOSIS", "")
case Network.ARBITRUM_ONE:
prod_db_url = os.environ.get("PROD_DB_URL_ARBITRUM", "")
barn_db_url = os.environ.get("BARN_DB_URL_ARBITRUM", "")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Came here from https://github.com/cowprotocol/infrastructure/pull/2349.
This seems like it complicates the configuration. Why not continue to always read the URLs from PROD_DB_URL and BARN_DB_URL and expect the job to be run with the correctly configured env variables?

Same comment applies to NODE_URL.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In principle it is possible to have one config file with all environment variables per chain, and remove all post-fixes. With our infrastructure it would be one config file and one secret file per chain then, right?

At some point we had planned to compute all payments in one run and then all secrets would need to be available from the same environment. But this is not planned anymore.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With our infrastructure it would be one config file and one secret file per chain then, right?

Yeah, I think so. Both approaches can definitely work. Just seemed surprising to me that you'd basically have 2 places where you branch based on the network. First when you decide which ENV variable to write your values into and later when you decide which ENV variable to read from. 🤷‍♂️

case _:
raise ValueError(f"No orderbook config set up for network {network}.")

return OrderbookConfig(prod_db_url=prod_db_url, barn_db_url=barn_db_url)

@@ -142,10 +180,16 @@ def from_network(network: Network) -> DuneConfig:
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")
dune_blockchain = "ethereum"
case Network.GNOSIS:
dune_blockchain = "gnosis"
case Network.ARBITRUM_ONE:
dune_blockchain = "arbitrum"
case _:
raise ValueError(f"No dune config set up for network {network}.")

return DuneConfig(dune_api_key=dune_api_key, dune_blockchain=dune_blockchain)


@dataclass(frozen=True)
class NodeConfig:
@@ -158,7 +202,11 @@ 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", "")
node_url = os.environ.get("NODE_URL_MAINNET", "")
case Network.GNOSIS:
node_url = os.environ.get("NODE_URL_GNOSIS", "")
case Network.ARBITRUM_ONE:
node_url = os.environ.get("NODE_URL_ARBITRUM", "")
case _:
raise ValueError(f"No node config set up for network {network}.")

@@ -177,7 +225,7 @@ class PaymentConfig:
signing_key: str | None
safe_queue_url: str
verification_docs_url: str
weth_address: ChecksumAddress
wrapped_native_token_address: ChecksumAddress

@staticmethod
def from_network(network: Network) -> PaymentConfig:
@@ -188,42 +236,66 @@ def from_network(network: Network) -> PaymentConfig:

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_network = EthereumNetwork.MAINNET
payment_safe_address = Web3.to_checksum_address(
os.environ.get(
"SAFE_ADDRESS", "0xA03be496e67Ec29bC62F01a428683D7F9c204930"
"SAFE_ADDRESS_MAINNET",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized that the name is not very descriptive. Something like PAYOUTS_SAFE would be more descriptive

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to PAYOUTS_SAFE_ADDRESS_${NETWORK}.

"0xA03be496e67Ec29bC62F01a428683D7F9c204930",
)
)
short_name = network_short_name[network]
safe_url = (
f"https://app.safe.global/{short_name}:{payment_safe_address}"
short_name = "eth"

cow_token_address = Address(
"0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"
)
wrapped_native_token_address = Web3.to_checksum_address(
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
)
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 Network.GNOSIS:
payment_network = EthereumNetwork.GNOSIS
payment_safe_address = Web3.to_checksum_address(
os.environ.get("SAFE_ADDRESS_GNOSIS", "")
)
short_name = "gno"

cow_token_address = Address(
"0x177127622c4a00f3d409b75571e12cb3c8973d3c"
)
wrapped_native_token_address = Web3.to_checksum_address(
"0xe91d153e0b41518a2ce8dd3d7944fa863463a97d"
)
case Network.ARBITRUM_ONE:
payment_network = EthereumNetwork.GNOSIS
payment_safe_address = Web3.to_checksum_address(
os.environ.get("SAFE_ADDRESS_ARBITRUM", "")
)
short_name = "arb1"

cow_token_address = Address(
"0xcb8b5cd20bdcaea9a010ac1f8d835824f5c87a04"
)
wrapped_native_token_address = Web3.to_checksum_address(
"0x82af49447d8a07e3bd95bd0d56f35241523fbab1"
)
case _:
raise ValueError(f"No payment config set up for network {network}.")

safe_url = f"https://app.safe.global/{short_name}:{payment_safe_address}"
safe_queue_url = f"{safe_url}/transactions/queue"

return PaymentConfig(
network=payment_network,
cow_token_address=cow_token_address,
payment_safe_address=payment_safe_address,
signing_key=signing_key,
safe_queue_url=safe_queue_url,
verification_docs_url=docs_url,
wrapped_native_token_address=wrapped_native_token_address,
)


@dataclass(frozen=True)
class IOConfig:
@@ -281,7 +353,7 @@ def from_network(network: Network) -> AccountingConfig:

return AccountingConfig(
payment_config=PaymentConfig.from_network(network),
orderbook_config=OrderbookConfig.from_env(),
orderbook_config=OrderbookConfig.from_network(network),
dune_config=DuneConfig.from_network(network),
node_config=NodeConfig.from_network(network),
reward_config=RewardConfig.from_network(network),
13 changes: 12 additions & 1 deletion src/fetch/dune.py
Original file line number Diff line number Diff line change
@@ -22,13 +22,16 @@ class DuneFetcher:

dune: DuneClient
period: AccountingPeriod
blockchain: str

def __init__(
self,
dune: DuneClient,
blockchain: str,
period: AccountingPeriod,
):
self.dune = dune
self.blockchain = blockchain
self.period = period
# Already have period set, so we might as well store this upon construction.
# This may become an issue when we make the fetchers async;
@@ -39,6 +42,13 @@ def _period_params(self) -> list[QueryParameter]:
"""Easier access to these parameters."""
return self.period.as_query_params()

def _network_and_period_params(self) -> list[QueryParameter]:
"""Easier access to parameters for network and accounting period."""
network_param = QueryParameter.text_type("blockchain", self.blockchain)
period_params = self._period_params()

return period_params + [network_param]

@staticmethod
def _parameterized_query(
query_data: QueryData, params: list[QueryParameter]
@@ -71,7 +81,7 @@ def get_block_interval(self) -> tuple[str, str]:
"""Returns block numbers corresponding to date interval"""
results = self._get_query_results(
self._parameterized_query(
QUERIES["PERIOD_BLOCK_INTERVAL"], self._period_params()
QUERIES["PERIOD_BLOCK_INTERVAL"], self._network_and_period_params()
)
)
assert len(results) == 1, "Block Interval Query should return only 1 result!"
@@ -87,6 +97,7 @@ def get_vouches(self) -> list[DuneRecord]:
params=[
QueryParameter.date_type("end_time", self.period.end),
QueryParameter.enum_type("vouch_cte_name", "named_results"),
QueryParameter.text_type("blockchain", self.blockchain),
],
)
)
14 changes: 10 additions & 4 deletions src/fetch/payouts.py
Original file line number Diff line number Diff line change
@@ -107,12 +107,12 @@ def from_series(cls, frame: Series) -> RewardAndPenaltyDatum:
)
solver = frame["solver"]
reward_target = frame["reward_target"]
if reward_target is None:
if pandas.isna(reward_target):
log.warning(f"Solver {solver} without reward_target. Using solver")
reward_target = solver

buffer_accounting_target = frame["buffer_accounting_target"]
if buffer_accounting_target is None:
if pandas.isna(buffer_accounting_target):
log.warning(
f"Solver {solver} without buffer_accounting_target. Using solver"
)
@@ -516,7 +516,13 @@ def construct_payouts(
for service_fee_flag in service_fee_df["service_fee"]
]

reward_target_df = pandas.DataFrame(dune.get_vouches())
vouches = dune.get_vouches()
if vouches:
reward_target_df = pandas.DataFrame(dune.get_vouches())
else:
reward_target_df = DataFrame(
columns=["solver", "solver_name", "reward_target", "pool_address"]
)
# construct slippage df
if ignore_slippage_flag or (not config.buffer_accounting_config.include_slippage):
slippage_df_temp = pandas.merge(
@@ -534,7 +540,7 @@ def construct_payouts(
slippage_df = slippage_df.rename(columns={"solver_address": "solver"})

reward_token = config.reward_config.reward_token_address
native_token = Address(config.payment_config.weth_address)
native_token = Address(config.payment_config.wrapped_native_token_address)
price_day = dune.period.end - timedelta(days=1)
exchange_rate_native_to_cow = exchange_rate_atoms(
native_token, reward_token, price_day
8 changes: 8 additions & 0 deletions src/fetch/prices.py
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ class TokenId(Enum):
"""Coin Ids for coin paprika"""

ETH = "eth-ethereum"
XDAI = "xdai-xdai"
COW = "cow-cow-protocol-token"
USDC = "usdc-usd-coin"

@@ -40,9 +41,16 @@ def decimals(self) -> int:


TOKEN_ADDRESS_TO_ID = {
# mainnet tokens
Address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"): TokenId.ETH,
Address("0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"): TokenId.COW,
Address("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"): TokenId.USDC,
# gnosis tokens
Address("0xe91d153e0b41518a2ce8dd3d7944fa863463a97d"): TokenId.XDAI,
Address("0x177127622c4a00f3d409b75571e12cb3c8973d3c"): TokenId.COW,
# arbitrum tokens
Address("0xcb8b5cd20bdcaea9a010ac1f8d835824f5c87a04"): TokenId.COW,
Address("0x82af49447d8a07e3bd95bd0d56f35241523fbab1"): TokenId.ETH,
}


5 changes: 3 additions & 2 deletions src/fetch/transfer_file.py
Original file line number Diff line number Diff line change
@@ -75,7 +75,7 @@ def auto_propose(
transactions = prepend_unwrap_if_necessary(
client,
config.payment_config.payment_safe_address,
wrapped_native_token=config.payment_config.weth_address,
wrapped_native_token=config.payment_config.wrapped_native_token_address,
transactions=[t.as_multisend_tx() for t in transfers],
)
if len(transactions) > len(transfers):
@@ -117,13 +117,14 @@ def main() -> None:

config = AccountingConfig.from_network(Network(os.environ["NETWORK"]))

accounting_period = AccountingPeriod(args.start, length_days=1)
accounting_period = AccountingPeriod(args.start, length_days=7)

orderbook = MultiInstanceDBFetcher(
[config.orderbook_config.prod_db_url, config.orderbook_config.barn_db_url]
)
dune = DuneFetcher(
dune=DuneClient(config.dune_config.dune_api_key),
blockchain=config.dune_config.dune_blockchain,
period=accounting_period,
)

4 changes: 2 additions & 2 deletions src/queries.py
Original file line number Diff line number Diff line change
@@ -32,11 +32,11 @@ def with_params(self, params: list[QueryParameter]) -> QueryBase:
QUERIES = {
"PERIOD_BLOCK_INTERVAL": QueryData(
name="Block Interval for Accounting Period",
q_id=3333356,
q_id=4227027,
),
"VOUCH_REGISTRY": QueryData(
name="Vouch Registry",
q_id=1541516,
q_id=4339624,
),
"SERVICE_FEE_STATUS": QueryData(
name="CIP-48 Service fee status",
Loading