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

Fuzzing fixes #1231

Merged
merged 4 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
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
5 changes: 4 additions & 1 deletion lib/agent0/bin/fuzz_bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@
# 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
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
Loading