Skip to content

Commit 187e791

Browse files
harisangfhenneke
andauthored
Test for detecting discrepancies between native prices and ucp (#140)
This PR adds a test that compares the exchange rate provided by the winning solver on a trade against the exchange rate suggested by the native prices. It is meant to capture cases where: - there is surplus shift, or - some native price is very off --------- Co-authored-by: Felix Henneke <felix.henneke@protonmail.com>
1 parent f27fd04 commit 187e791

File tree

4 files changed

+110
-0
lines changed

4 files changed

+110
-0
lines changed

src/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
# threshold to generate an alert for high score
4141
HIGH_SCORE_THRESHOLD_ETH = 10
4242

43+
# threshold to generate an alert for price discrepancy test
44+
UCP_VS_NATIVE_SENSITIVITY_THRESHOLD = 0.5
45+
4346
# relevant addresses
4447
SETTLEMENT_CONTRACT_ADDRESS = "0x9008D19f58AAbD9eD0D60971565AA8510560ab41"
4548
MEV_BLOCKER_KICKBACKS_ADDRESSES = [

src/daemon.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from src.monitoring_tests.high_score_test import (
2121
HighScoreTest,
2222
)
23+
from src.monitoring_tests.price_sensitivity_test import PriceSensitivityTest
2324
from src.constants import SLEEP_TIME_IN_SEC, CHAIN_ID_TO_NAME
2425

2526

@@ -36,6 +37,7 @@ def main() -> None:
3637
tests = [
3738
SolverCompetitionSurplusTest(orderbook_api),
3839
HighScoreTest(orderbook_api),
40+
PriceSensitivityTest(orderbook_api),
3941
]
4042
# special case for mainnet as MEV Blocker only exists on mainnet
4143
if chain_name == "mainnet":
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Checks whether native prices are far from UCP for a trade
3+
"""
4+
5+
# pylint: disable=duplicate-code
6+
# pylint: disable=too-many-locals
7+
from typing import Any
8+
from fractions import Fraction
9+
from src.monitoring_tests.base_test import BaseTest
10+
from src.apis.orderbookapi import OrderbookAPI
11+
from src.constants import (
12+
UCP_VS_NATIVE_SENSITIVITY_THRESHOLD,
13+
)
14+
15+
16+
class PriceSensitivityTest(BaseTest):
17+
"""
18+
This test checks whether the exchange rate implied by native prices
19+
is far from exchange rate implied by UCP
20+
"""
21+
22+
def __init__(self, orderbook_api: OrderbookAPI) -> None:
23+
super().__init__()
24+
self.orderbook_api = orderbook_api
25+
26+
def check_prices(self, competition_data: dict[str, Any]) -> bool:
27+
"""
28+
This function checks whether native prices are far from ucp
29+
"""
30+
winning_solution = competition_data["solutions"][-1]
31+
trades_dict = self.orderbook_api.get_uid_trades(winning_solution)
32+
if trades_dict is None:
33+
return False
34+
35+
ucp = {
36+
token.lower(): int(price)
37+
for token, price in winning_solution["clearingPrices"].items()
38+
}
39+
40+
native_prices = {
41+
token.lower(): int(price)
42+
for token, price in competition_data["auction"]["prices"].items()
43+
}
44+
45+
for uid, trade in trades_dict.items():
46+
sell_token = trade.data.sell_token.lower()
47+
buy_token = trade.data.buy_token.lower()
48+
ucp_rate = Fraction(ucp[sell_token], ucp[buy_token])
49+
native_price_rate = Fraction(
50+
native_prices[sell_token], native_prices[buy_token]
51+
)
52+
53+
max_rate = max(ucp_rate, native_price_rate)
54+
min_rate = min(ucp_rate, native_price_rate)
55+
56+
if max_rate > (1 + UCP_VS_NATIVE_SENSITIVITY_THRESHOLD) * min_rate:
57+
log_output = "\t".join(
58+
[
59+
"Price sensitivity test:",
60+
f"Tx Hash: {competition_data['transactionHashes'][0]}",
61+
f"Winning Solver: {winning_solution['solver']}",
62+
f"Trade: {uid}",
63+
f"Gap: {float(max_rate / min_rate)}",
64+
]
65+
)
66+
self.alert(log_output)
67+
68+
return True
69+
70+
def run(self, tx_hash: str) -> bool:
71+
"""
72+
Wrapper function for the whole test. Checks if violation is more than
73+
UCP_VS_NATIVE_SENSITIVITY_THRESHOLD, in which case it generates an alert.
74+
"""
75+
solver_competition_data = self.orderbook_api.get_solver_competition_data(
76+
tx_hash
77+
)
78+
if solver_competition_data is None:
79+
return False
80+
81+
success = self.check_prices(solver_competition_data)
82+
83+
return success
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
Tests for surplus test.
3+
"""
4+
5+
import unittest
6+
from src.apis.orderbookapi import OrderbookAPI
7+
from src.monitoring_tests.price_sensitivity_test import (
8+
PriceSensitivityTest,
9+
)
10+
11+
12+
class TestPrices(unittest.TestCase):
13+
def test_prices(self) -> None:
14+
orderbook_api = OrderbookAPI("mainnet")
15+
price_sensitivity_test = PriceSensitivityTest(orderbook_api)
16+
# new competition format: no alert or info
17+
tx_hash = "0x524b92cfc81e9b590a701bf157ca1b0f21b05d7c37ebb39f332cd215c8db1046"
18+
self.assertTrue(price_sensitivity_test.run(tx_hash))
19+
20+
21+
if __name__ == "__main__":
22+
unittest.main()

0 commit comments

Comments
 (0)