diff --git a/lib/agent0/agent0/interactive_fuzz/fuzz_hyperdrive_balance.py b/lib/agent0/agent0/interactive_fuzz/fuzz_hyperdrive_balance.py index b2076ed8b9..58f4fb8134 100644 --- a/lib/agent0/agent0/interactive_fuzz/fuzz_hyperdrive_balance.py +++ b/lib/agent0/agent0/interactive_fuzz/fuzz_hyperdrive_balance.py @@ -62,7 +62,8 @@ def fuzz_hyperdrive_balance(num_trades: int, chain_config: LocalChain.Config, lo trade_list = generate_trade_list(num_trades, rng, interactive_hyperdrive) # Open some trades - trade_events = open_random_trades(trade_list, chain, rng, interactive_hyperdrive, advance_time=True) + # TODO set advance time to be true, but ensure all open positions are within one position duration + trade_events = open_random_trades(trade_list, chain, rng, interactive_hyperdrive, advance_time=False) # Close the trades close_random_trades(trade_events, rng) @@ -75,7 +76,11 @@ def fuzz_hyperdrive_balance(num_trades: int, chain_config: LocalChain.Config, lo invariant_check(initial_effective_share_reserves, interactive_hyperdrive) except FuzzAssertionException as error: dump_state_dir = chain.save_state(save_prefix="fuzz_long_short_maturity_values") - additional_info = {"fuzz_random_seed": random_seed, "dump_state_dir": dump_state_dir} + additional_info = { + "fuzz_random_seed": random_seed, + "dump_state_dir": dump_state_dir, + "trade_ticker": interactive_hyperdrive.get_ticker(), + } additional_info.update(error.exception_data) report = build_crash_trade_result( error, interactive_hyperdrive.hyperdrive_interface, agent.agent, additional_info=additional_info diff --git a/lib/agent0/agent0/interactive_fuzz/fuzz_long_short_maturity_values.py b/lib/agent0/agent0/interactive_fuzz/fuzz_long_short_maturity_values.py index f5b364762e..11b89e569b 100644 --- a/lib/agent0/agent0/interactive_fuzz/fuzz_long_short_maturity_values.py +++ b/lib/agent0/agent0/interactive_fuzz/fuzz_long_short_maturity_values.py @@ -120,7 +120,11 @@ def fuzz_long_short_maturity_values( invariant_check(trade, close_event, starting_checkpoint, maturity_checkpoint, interactive_hyperdrive) except FuzzAssertionException as error: dump_state_dir = chain.save_state(save_prefix="fuzz_long_short_maturity_values") - additional_info = {"fuzz_random_seed": random_seed, "dump_state_dir": dump_state_dir} + additional_info = { + "fuzz_random_seed": random_seed, + "dump_state_dir": dump_state_dir, + "trade_ticker": interactive_hyperdrive.get_ticker(), + } additional_info.update(error.exception_data) report = build_crash_trade_result( error, interactive_hyperdrive.hyperdrive_interface, agent.agent, additional_info=additional_info diff --git a/lib/agent0/agent0/interactive_fuzz/fuzz_path_independence.py b/lib/agent0/agent0/interactive_fuzz/fuzz_path_independence.py index 73f4f34882..b52c64aa10 100644 --- a/lib/agent0/agent0/interactive_fuzz/fuzz_path_independence.py +++ b/lib/agent0/agent0/interactive_fuzz/fuzz_path_independence.py @@ -6,6 +6,7 @@ import sys from typing import Any, NamedTuple, Sequence +import pandas as pd from fixedpointmath import FixedPoint from agent0.hyperdrive.crash_report import build_crash_trade_result, log_hyperdrive_crash_report @@ -84,6 +85,7 @@ def fuzz_path_independence( logging.info("Close trades in random order; check final state...") check_data: dict[str, Any] | None = None first_run_state_dump_dir: str | None = None + first_run_ticker: pd.DataFrame | None = None for iteration in range(num_paths_checked): print(f"{iteration=}") logging.info("iteration %s out of %s", iteration, num_paths_checked - 1) @@ -91,11 +93,13 @@ def fuzz_path_independence( chain.load_snapshot() # Randomly grab some trades & close them one at a time + # TODO guarantee closing trades within the same checkpoint close_random_trades(trade_events, rng) # Check the reserve amounts; they should be unchanged now that all of the trades are closed pool_state_df = interactive_hyperdrive.get_pool_state(coerce_float=False) + # TODO add present value check here # On first run, save final state if check_data is None: check_data = {} @@ -108,6 +112,7 @@ def fuzz_path_independence( check_data["effective_share_reserves"] = effective_share_reserves check_data["minimum_share_reserves"] = pool_state.pool_config.minimum_share_reserves first_run_state_dump_dir = chain.save_state(save_prefix="fuzz_path_independence") + first_run_ticker = interactive_hyperdrive.get_ticker() # On subsequent run, check against the saved final state else: @@ -123,6 +128,8 @@ def fuzz_path_independence( "fuzz_random_seed": random_seed, "first_run_state_dump_dir": first_run_state_dump_dir, "dump_state_dir": dump_state_dir, + "first_run_trade_ticker": first_run_ticker, + "trade_ticker": interactive_hyperdrive.get_ticker(), } additional_info.update(error.exception_data) report = build_crash_trade_result( @@ -234,17 +241,6 @@ def invariant_check( exception_data: dict[str, Any] = {} pool_state = interactive_hyperdrive.hyperdrive_interface.get_hyperdrive_state() - # Base balance - expected_base_balance = FixedPoint(check_data["hyperdrive_base_balance"]) - actual_base_balance = pool_state.hyperdrive_base_balance - if expected_base_balance != actual_base_balance: - difference_in_wei = abs(expected_base_balance.scaled_value - actual_base_balance.scaled_value) - exception_message.append(f"{expected_base_balance=} != {actual_base_balance=}, {difference_in_wei=}") - exception_data["invariance_check:expected_base_balance"] = expected_base_balance - exception_data["invariance_check:actual_base_balance"] = actual_base_balance - exception_data["invariance_check:base_balance_difference_in_wei"] = difference_in_wei - failed = True - # Effective share reserves expected_effective_share_reserves = FixedPoint(check_data["effective_share_reserves"]) actual_effective_share_reserves = interactive_hyperdrive.hyperdrive_interface.calc_effective_share_reserves( @@ -262,19 +258,6 @@ def invariant_check( exception_data["invariance_check:effective_share_reserves_difference_in_wei"] = difference_in_wei failed = True - # Minimum share reserves - expected_minimum_share_reserves = check_data["minimum_share_reserves"] - actual_minimum_share_reserves = pool_state.pool_config.minimum_share_reserves - if expected_minimum_share_reserves != actual_minimum_share_reserves: - difference_in_wei = abs(expected_minimum_share_reserves - actual_minimum_share_reserves) - exception_message.append( - f"{expected_minimum_share_reserves=} != {actual_minimum_share_reserves=}, {difference_in_wei=}" - ) - exception_data["invariance_check:expected_minimum_share_reserves"] = expected_minimum_share_reserves - exception_data["invariance_check:actual_minimum_share_reserves"] = actual_minimum_share_reserves - exception_data["invariance_check:minimum_share_reserves_difference_in_wei"] = difference_in_wei - failed = True - # Check that the subset of columns in initial db pool state and the latest pool state are equal expected_pool_state_df = check_data["initial_pool_state_df"] actual_pool_state_df = check_data["final_pool_state_df"] diff --git a/lib/agent0/agent0/interactive_fuzz/fuzz_profit_check.py b/lib/agent0/agent0/interactive_fuzz/fuzz_profit_check.py index 51c5ee6bd4..6ee0f6897e 100644 --- a/lib/agent0/agent0/interactive_fuzz/fuzz_profit_check.py +++ b/lib/agent0/agent0/interactive_fuzz/fuzz_profit_check.py @@ -105,6 +105,7 @@ def fuzz_profit_check(chain_config: LocalChain.Config | None = None, log_to_stdo additional_info = { "fuzz_random_seed": random_seed, "dump_state_dir": dump_state_dir, + "trade_ticker": interactive_hyperdrive.get_ticker(), } additional_info.update(error.exception_data) # TODO do better checking here or make agent optional in build_crash_trade_result diff --git a/lib/agent0/bin/fuzz_bot_invariant_checks.py b/lib/agent0/bin/fuzz_bot_invariant_checks.py index 2b2d3c9412..0ef5b5ed66 100644 --- a/lib/agent0/bin/fuzz_bot_invariant_checks.py +++ b/lib/agent0/bin/fuzz_bot_invariant_checks.py @@ -150,6 +150,7 @@ def run_invariant_checks( + pool_state.pool_info.shorts_outstanding / pool_state.pool_info.share_price + pool_state.gov_fees_accrued + pool_state.pool_info.withdrawal_shares_proceeds + + pool_state.pool_info.zombie_share_reserves ) actual_vault_shares = pool_state.vault_shares if not fp_isclose(expected_vault_shares, actual_vault_shares, abs_tol=epsilon): @@ -180,9 +181,7 @@ def run_invariant_checks( failed = True # The pool has more than the minimum share reserves - current_share_reserves = ( - pool_state.pool_info.share_reserves * pool_state.pool_info.share_price - pool_state.pool_info.long_exposure - ) + current_share_reserves = pool_state.pool_info.share_reserves minimum_share_reserves = pool_state.pool_config.minimum_share_reserves if not current_share_reserves >= minimum_share_reserves: exception_message.append( @@ -198,6 +197,9 @@ def run_invariant_checks( # Creating a checkpoint should never fail # TODO: add get_block_transactions() to interface + # NOTE: This wold be prone to false positives. + # If the transaction would have failed anyway, then we don't know + # that it failed bc of checkpoint failure or bc e.g., open long was for too much transactions = latest_block.get("transactions", None) if transactions is not None and isinstance(transactions, Sequence): # If any transaction is to hyperdrive then assert a checkpoint happened diff --git a/lib/agent0/bin/fuzz_bots.py b/lib/agent0/bin/fuzz_bots.py index 4333e377cf..e7175495e7 100644 --- a/lib/agent0/bin/fuzz_bots.py +++ b/lib/agent0/bin/fuzz_bots.py @@ -24,10 +24,14 @@ # Username binding of bots USERNAME = "test_bots" # The amount of base token each bot receives +# TODO update base budget to be 100% of liquidity, so that max trades cause large swings +# May want to add weighting to random draw so large trades are unlikely, or have arb bot +# ensure pool stays reasonable BASE_BUDGET_PER_BOT = FixedPoint(1000) ETH_BUDGET_PER_BOT = FixedPoint(10) # The slippage tolerance for trades -SLIPPAGE_TOLERANCE = FixedPoint("0.0001") # 0.1% slippage +# TODO randomly turn on/off slippage for each bot +SLIPPAGE_TOLERANCE = FixedPoint("0.1") # 10% slippage # Run this file with this flag set to true to close out all open positions LIQUIDATE = False diff --git a/lib/hyperlogs/hyperlogs/json_encoder.py b/lib/hyperlogs/hyperlogs/json_encoder.py index 2f25a22139..6dafabeeee 100644 --- a/lib/hyperlogs/hyperlogs/json_encoder.py +++ b/lib/hyperlogs/hyperlogs/json_encoder.py @@ -8,6 +8,7 @@ from typing import Any import numpy as np +import pandas as pd from fixedpointmath import FixedPoint from hexbytes import HexBytes from numpy.random._generator import Generator @@ -58,6 +59,8 @@ def default(self, o: Any) -> Any: return o.name if isinstance(o, bytes): return str(o) + if isinstance(o, pd.DataFrame): + return o.to_dict(orient="records") try: return o.__dict__ except AttributeError: