From 01aac305b6ca050256bd6825280c3beb5c749d69 Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 17:45:14 +0200 Subject: [PATCH 01/14] add udp test --- src/constants.py | 3 + src/daemon.py | 4 + .../uniform_directed_prices_test.py | 77 +++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 src/monitoring_tests/uniform_directed_prices_test.py diff --git a/src/constants.py b/src/constants.py index b481d13..1dc7363 100644 --- a/src/constants.py +++ b/src/constants.py @@ -31,6 +31,9 @@ # threshold parameter to generate an alert when receiving kickbacks KICKBACKS_ALERT_THRESHOLD = 0.1 +# threshold to generate an alert for violating UDP +UDP_SENSITIVITY_THRESHOLD = 0.005 + # relevant addresses SETTLEMENT_CONTRACT_ADDRESS = "0x9008D19f58AAbD9eD0D60971565AA8510560ab41" MEV_BLOCKER_KICKBACKS_ADDRESS = "0xCe91228789B57DEb45e66Ca10Ff648385fE7093b" diff --git a/src/daemon.py b/src/daemon.py index 864ca65..6d6c3ce 100644 --- a/src/daemon.py +++ b/src/daemon.py @@ -27,6 +27,9 @@ from src.monitoring_tests.cost_coverage_zero_signed_fee import ( CostCoverageForZeroSignedFee, ) +from src.monitoring_tests.uniform_directed_prices_test import ( + UniformDirectedPricesTest, +) from src.constants import SLEEP_TIME_IN_SEC @@ -44,6 +47,7 @@ def main() -> None: BuffersMonitoringTest(), CombinatorialAuctionSurplusTest(), CostCoverageForZeroSignedFee(), + UniformDirectedPricesTest(), ] start_block: Optional[int] = None diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py new file mode 100644 index 0000000..95b396a --- /dev/null +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -0,0 +1,77 @@ +""" +Checks the uniform directed prices constraint that was introduced with CIP-38 +""" +# pylint: disable=logging-fstring-interpolation +from typing import Any +from src.monitoring_tests.base_test import BaseTest +from src.apis.orderbookapi import OrderbookAPI +from src.constants import ( + UDP_SENSITIVITY_THRESHOLD, +) + + +class UniformDirectedPricesTest(BaseTest): + """ + This test checks whether the Uniform Directed Prices constraint, + as introduced in CIP-38, is satisfied. + """ + + def __init__(self) -> None: + super().__init__() + self.orderbook_api = OrderbookAPI() + + def check_udp(self, competition_data: dict[str, Any]) -> bool: + """ + This function checks whether there are multiple orders in the same directed token pair, + and if so, checks wheter UDP is satisfied. + """ + solution = competition_data["solutions"][-1] + trades_dict = self.orderbook_api.get_uid_trades(solution) + if trades_dict is None: + return False + token_pairs = {} + for uid in trades_dict: + trade = trades_dict[uid] + sell_token = trade.data.sell_token + buy_token = trade.data.buy_token + sell_amount = trade.execution.sell_amount + buy_amount = trade.execution.buy_amount + if (sell_token, buy_token) not in token_pairs: + token_pairs[(sell_token, buy_token)] = [sell_amount / buy_amount] + else: + token_pairs[(sell_token, buy_token)].append(sell_amount / buy_amount) + for tp in token_pairs: + if len(token_pairs[tp]) == 1: + continue + rate = token_pairs[tp][0] + for r in token_pairs[tp]: + if r < rate: + rate = r + lower_r = rate * (1 - UDP_SENSITIVITY_THRESHOLD) + upper_r = rate * (1 + UDP_SENSITIVITY_THRESHOLD) + for r in token_pairs[tp]: + if r < lower_r or r > upper_r: + log_output = "\t".join( + [ + "Uniform Directed Prices test:", + f"Tx Hash: {competition_data['transactionHash']}", + f"Token pair: {tp}", + ] + ) + self.alert(log_output) + return True + + def run(self, tx_hash: str) -> bool: + """ + Wrapper function for the whole test. Checks if violation is more than + UDP_SENSITIVITY_THRESHOLD, in which case it generates an alert. + """ + solver_competition_data = self.orderbook_api.get_solver_competition_data( + tx_hash + ) + if solver_competition_data is None: + return False + + success = self.check_udp(solver_competition_data) + + return success From 2d452c41b1e44e289ddcbc0f9baa1908df666d7b Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 17:47:25 +0200 Subject: [PATCH 02/14] fix typo --- src/monitoring_tests/uniform_directed_prices_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index 95b396a..c289a01 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -23,7 +23,7 @@ def __init__(self) -> None: def check_udp(self, competition_data: dict[str, Any]) -> bool: """ This function checks whether there are multiple orders in the same directed token pair, - and if so, checks wheter UDP is satisfied. + and if so, checks whether UDP is satisfied. """ solution = competition_data["solutions"][-1] trades_dict = self.orderbook_api.get_uid_trades(solution) From b40933b30a24da4624ad6377bd7295aec81f439b Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 17:54:53 +0200 Subject: [PATCH 03/14] minor fixes --- .../uniform_directed_prices_test.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index c289a01..daf161c 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -40,22 +40,22 @@ def check_udp(self, competition_data: dict[str, Any]) -> bool: token_pairs[(sell_token, buy_token)] = [sell_amount / buy_amount] else: token_pairs[(sell_token, buy_token)].append(sell_amount / buy_amount) - for tp in token_pairs: - if len(token_pairs[tp]) == 1: + for token_pair in token_pairs: + if len(token_pairs[token_pair]) == 1: continue - rate = token_pairs[tp][0] - for r in token_pairs[tp]: - if r < rate: - rate = r + min_rate = token_pairs[token_pair][0] + for rate in token_pairs[token_pair]: + if rate < min_rate: + min_rate = rate lower_r = rate * (1 - UDP_SENSITIVITY_THRESHOLD) upper_r = rate * (1 + UDP_SENSITIVITY_THRESHOLD) - for r in token_pairs[tp]: - if r < lower_r or r > upper_r: + for rate in token_pairs[token_pair]: + if rate < lower_r or rate > upper_r: log_output = "\t".join( [ "Uniform Directed Prices test:", f"Tx Hash: {competition_data['transactionHash']}", - f"Token pair: {tp}", + f"Token pair: {token_pair}", ] ) self.alert(log_output) From dce94e5ac64e89c7acdc55d73a57d8a5c5e30c39 Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 17:58:05 +0200 Subject: [PATCH 04/14] pylint fixes --- .../uniform_directed_prices_test.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index daf161c..0ad5dfc 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -32,14 +32,14 @@ def check_udp(self, competition_data: dict[str, Any]) -> bool: token_pairs = {} for uid in trades_dict: trade = trades_dict[uid] - sell_token = trade.data.sell_token - buy_token = trade.data.buy_token - sell_amount = trade.execution.sell_amount - buy_amount = trade.execution.buy_amount - if (sell_token, buy_token) not in token_pairs: - token_pairs[(sell_token, buy_token)] = [sell_amount / buy_amount] + if (trade.data.sell_token, trade.data.buy_token) not in token_pairs: + token_pairs[(trade.data.sell_token, trade.data.buy_token)] = [ + trade.execution.sell_amount / trade.execution.buy_amount + ] else: - token_pairs[(sell_token, buy_token)].append(sell_amount / buy_amount) + token_pairs[(trade.data.sell_token, trade.data.buy_token)].append( + trade.execution.sell_amount / trade.execution.buy_amount + ) for token_pair in token_pairs: if len(token_pairs[token_pair]) == 1: continue From cc7f3074180c85292729725d8f007ae133e2e5ed Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 18:32:07 +0200 Subject: [PATCH 05/14] fix minor bug --- src/monitoring_tests/uniform_directed_prices_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index 0ad5dfc..8dd0957 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -47,8 +47,8 @@ def check_udp(self, competition_data: dict[str, Any]) -> bool: for rate in token_pairs[token_pair]: if rate < min_rate: min_rate = rate - lower_r = rate * (1 - UDP_SENSITIVITY_THRESHOLD) - upper_r = rate * (1 + UDP_SENSITIVITY_THRESHOLD) + lower_r = min_rate * (1 - UDP_SENSITIVITY_THRESHOLD) + upper_r = min_rate * (1 + UDP_SENSITIVITY_THRESHOLD) for rate in token_pairs[token_pair]: if rate < lower_r or rate > upper_r: log_output = "\t".join( From 12f73ac612d75e5355ba09e974a050ce7c71a7a1 Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 18:43:40 +0200 Subject: [PATCH 06/14] disable pylint error --- src/monitoring_tests/uniform_directed_prices_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index 8dd0957..2634598 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -1,7 +1,7 @@ """ Checks the uniform directed prices constraint that was introduced with CIP-38 """ -# pylint: disable=logging-fstring-interpolation +# pylint: disable=duplicate-code from typing import Any from src.monitoring_tests.base_test import BaseTest from src.apis.orderbookapi import OrderbookAPI From 679be4cd12d0743b991f37954b4ad3550d7ebcf9 Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 19:18:45 +0200 Subject: [PATCH 07/14] pylint fix --- src/monitoring_tests/uniform_directed_prices_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index 2634598..dd6fdb0 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -40,22 +40,22 @@ def check_udp(self, competition_data: dict[str, Any]) -> bool: token_pairs[(trade.data.sell_token, trade.data.buy_token)].append( trade.execution.sell_amount / trade.execution.buy_amount ) - for token_pair in token_pairs: - if len(token_pairs[token_pair]) == 1: + for (pair,trades_list) in token_pairs.items(): + if len(trades_list) == 1: continue - min_rate = token_pairs[token_pair][0] - for rate in token_pairs[token_pair]: + min_rate = trades_list[0] + for rate in trades_list: if rate < min_rate: min_rate = rate lower_r = min_rate * (1 - UDP_SENSITIVITY_THRESHOLD) upper_r = min_rate * (1 + UDP_SENSITIVITY_THRESHOLD) - for rate in token_pairs[token_pair]: + for rate in trades_list: if rate < lower_r or rate > upper_r: log_output = "\t".join( [ "Uniform Directed Prices test:", f"Tx Hash: {competition_data['transactionHash']}", - f"Token pair: {token_pair}", + f"Token pair: {pair}", ] ) self.alert(log_output) From b7d9f120b6b3d59a333e3c08c426d04833a11994 Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 19:21:06 +0200 Subject: [PATCH 08/14] black fix --- src/monitoring_tests/uniform_directed_prices_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index dd6fdb0..634746e 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -40,7 +40,7 @@ def check_udp(self, competition_data: dict[str, Any]) -> bool: token_pairs[(trade.data.sell_token, trade.data.buy_token)].append( trade.execution.sell_amount / trade.execution.buy_amount ) - for (pair,trades_list) in token_pairs.items(): + for (pair, trades_list) in token_pairs.items(): if len(trades_list) == 1: continue min_rate = trades_list[0] From ddde9e42e2ad3235baf8e9b707516d3f0a5aeaa7 Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 19:24:35 +0200 Subject: [PATCH 09/14] remove empty line --- src/monitoring_tests/uniform_directed_prices_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index 634746e..a3545f9 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -9,7 +9,6 @@ UDP_SENSITIVITY_THRESHOLD, ) - class UniformDirectedPricesTest(BaseTest): """ This test checks whether the Uniform Directed Prices constraint, From 2a557fef7276d5a42e19d0a2e8cf5f59a6948965 Mon Sep 17 00:00:00 2001 From: harisang Date: Fri, 8 Mar 2024 19:27:19 +0200 Subject: [PATCH 10/14] black fix.... --- src/monitoring_tests/uniform_directed_prices_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index a3545f9..634746e 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -9,6 +9,7 @@ UDP_SENSITIVITY_THRESHOLD, ) + class UniformDirectedPricesTest(BaseTest): """ This test checks whether the Uniform Directed Prices constraint, From 60d12f6f9d48354775115d08ee4b8972ad25951f Mon Sep 17 00:00:00 2001 From: Felix Henneke Date: Sat, 9 Mar 2024 12:10:18 +0100 Subject: [PATCH 11/14] black fix --- src/monitoring_tests/uniform_directed_prices_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index 634746e..8df9394 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -40,7 +40,7 @@ def check_udp(self, competition_data: dict[str, Any]) -> bool: token_pairs[(trade.data.sell_token, trade.data.buy_token)].append( trade.execution.sell_amount / trade.execution.buy_amount ) - for (pair, trades_list) in token_pairs.items(): + for pair, trades_list in token_pairs.items(): if len(trades_list) == 1: continue min_rate = trades_list[0] From fe1064de38d009993b6b07685dfb7dac6e189690 Mon Sep 17 00:00:00 2001 From: Felix Henneke Date: Mon, 18 Mar 2024 12:30:41 +0100 Subject: [PATCH 12/14] slight rewrite - renamed token_pairs to directional_prices - use min function to determine minimal exchange rate - add info to log message, and also log for smaller deviations - use Fractions to be compatible with backend code --- .../uniform_directed_prices_test.py | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index 8df9394..3aa1d8f 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -3,6 +3,7 @@ """ # pylint: disable=duplicate-code from typing import Any +from fractions import Fraction from src.monitoring_tests.base_test import BaseTest from src.apis.orderbookapi import OrderbookAPI from src.constants import ( @@ -27,38 +28,45 @@ def check_udp(self, competition_data: dict[str, Any]) -> bool: """ solution = competition_data["solutions"][-1] trades_dict = self.orderbook_api.get_uid_trades(solution) + if trades_dict is None: return False - token_pairs = {} - for uid in trades_dict: - trade = trades_dict[uid] - if (trade.data.sell_token, trade.data.buy_token) not in token_pairs: - token_pairs[(trade.data.sell_token, trade.data.buy_token)] = [ - trade.execution.sell_amount / trade.execution.buy_amount - ] - else: - token_pairs[(trade.data.sell_token, trade.data.buy_token)].append( - trade.execution.sell_amount / trade.execution.buy_amount - ) - for pair, trades_list in token_pairs.items(): - if len(trades_list) == 1: + + directional_prices: dict[tuple[str, str], list[Fraction]] = {} + for _, trade in trades_dict.items(): + token_pair = ( + trade.data.sell_token.lower(), + trade.data.buy_token.lower(), + ) + directional_price = Fraction( + trade.execution.sell_amount, trade.execution.buy_amount + ) + if token_pair not in directional_prices: + directional_prices[token_pair] = [] + directional_prices[token_pair].append(directional_price) + + for pair, prices_list in directional_prices.items(): + if len(prices_list) == 1: continue - min_rate = trades_list[0] - for rate in trades_list: - if rate < min_rate: - min_rate = rate - lower_r = min_rate * (1 - UDP_SENSITIVITY_THRESHOLD) + min_rate = min(prices_list) upper_r = min_rate * (1 + UDP_SENSITIVITY_THRESHOLD) - for rate in trades_list: - if rate < lower_r or rate > upper_r: - log_output = "\t".join( - [ - "Uniform Directed Prices test:", - f"Tx Hash: {competition_data['transactionHash']}", - f"Token pair: {pair}", - ] - ) + + log_output = "\t".join( + [ + "Uniform Directed Prices test:", + f"Tx Hash: {competition_data['transactionHash']}", + f"Winning Solver: {solution['solver']}", + f"Token pair: {pair}", + f"Directional prices: {[float(p) for p in prices_list]}", + ] + ) + for rate in prices_list: + if rate > min_rate * (1 + UDP_SENSITIVITY_THRESHOLD): self.alert(log_output) + break + elif rate > min_rate * (1 + UDP_SENSITIVITY_THRESHOLD / 10): + self.logger.info(log_output) + return True def run(self, tx_hash: str) -> bool: From 74324c0adad1aaef87508e53a7df46979d28633e Mon Sep 17 00:00:00 2001 From: Felix Henneke Date: Mon, 18 Mar 2024 12:38:43 +0100 Subject: [PATCH 13/14] fix pylint --- src/monitoring_tests/uniform_directed_prices_test.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/monitoring_tests/uniform_directed_prices_test.py b/src/monitoring_tests/uniform_directed_prices_test.py index 3aa1d8f..9142b01 100644 --- a/src/monitoring_tests/uniform_directed_prices_test.py +++ b/src/monitoring_tests/uniform_directed_prices_test.py @@ -49,7 +49,7 @@ def check_udp(self, competition_data: dict[str, Any]) -> bool: if len(prices_list) == 1: continue min_rate = min(prices_list) - upper_r = min_rate * (1 + UDP_SENSITIVITY_THRESHOLD) + max_rate = max(prices_list) log_output = "\t".join( [ @@ -60,12 +60,10 @@ def check_udp(self, competition_data: dict[str, Any]) -> bool: f"Directional prices: {[float(p) for p in prices_list]}", ] ) - for rate in prices_list: - if rate > min_rate * (1 + UDP_SENSITIVITY_THRESHOLD): - self.alert(log_output) - break - elif rate > min_rate * (1 + UDP_SENSITIVITY_THRESHOLD / 10): - self.logger.info(log_output) + if max_rate > min_rate * (1 + UDP_SENSITIVITY_THRESHOLD): + self.alert(log_output) + elif max_rate > min_rate * (1 + UDP_SENSITIVITY_THRESHOLD / 10): + self.logger.info(log_output) return True From 3f13b66aced5847cb66faed9ab9b88ff51eea2dd Mon Sep 17 00:00:00 2001 From: Felix Henneke Date: Mon, 18 Mar 2024 12:45:13 +0100 Subject: [PATCH 14/14] added test --- tests/e2e/uniform_directional_prices_test.py | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/e2e/uniform_directional_prices_test.py diff --git a/tests/e2e/uniform_directional_prices_test.py b/tests/e2e/uniform_directional_prices_test.py new file mode 100644 index 0000000..1efd204 --- /dev/null +++ b/tests/e2e/uniform_directional_prices_test.py @@ -0,0 +1,23 @@ +""" +Tests for combinatorial auction surplus test. +""" + +import unittest +from src.monitoring_tests.uniform_directed_prices_test import ( + UniformDirectedPricesTest, +) + + +class TestCombinatorialAuctionSurplus(unittest.TestCase): + def test_surplus(self) -> None: + surplus_test = UniformDirectedPricesTest() + # Fair prices + # tx_hash = "0x4c702f9a3e4593a16fed03229cb4d449a48eab5fb92030fc8ba596e78fef8d1c" + # self.assertTrue(surplus_test.run(tx_hash)) + # Unfair prices + tx_hash = "0x469ac0d0e430c67c94d26ae202e9e6710396a1968b1a6656be002eb4f2b7af65" + self.assertTrue(surplus_test.run(tx_hash)) + + +if __name__ == "__main__": + unittest.main()