Skip to content

Commit

Permalink
Fuzzing fixes (#1231)
Browse files Browse the repository at this point in the history
Fixes a collection of issues defined in
#1123
  • Loading branch information
Sheng Lundquist authored Jan 9, 2024
1 parent a2cf397 commit d7604e1
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 31 deletions.
9 changes: 7 additions & 2 deletions lib/agent0/agent0/interactive_fuzz/fuzz_hyperdrive_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 7 additions & 24 deletions lib/agent0/agent0/interactive_fuzz/fuzz_path_independence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -84,18 +85,21 @@ 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)
# Load the snapshot
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 = {}
Expand All @@ -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:
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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"]
Expand Down
1 change: 1 addition & 0 deletions lib/agent0/agent0/interactive_fuzz/fuzz_profit_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions lib/agent0/bin/fuzz_bot_invariant_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion lib/agent0/bin/fuzz_bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions lib/hyperlogs/hyperlogs/json_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit d7604e1

Please sign in to comment.