Skip to content

Commit

Permalink
Minimum transaction amount hotfix for fork fuzz. Log error if fuzz bo…
Browse files Browse the repository at this point in the history
…ts don't make trades on forked pool. (#1718)
  • Loading branch information
slundqui authored Oct 29, 2024
1 parent 24b43ff commit 7715078
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 21 deletions.
6 changes: 4 additions & 2 deletions scripts/fork_fuzz_bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,9 @@ def main(argv: Sequence[str] | None = None) -> None:
num_iterations=parsed_args.num_iterations_per_episode,
)
except Exception as e: # pylint: disable=broad-except
log_rollbar_exception(rollbar_log_prefix="Unexpected error", exception=e, log_level=logging.ERROR)
log_rollbar_exception(
rollbar_log_prefix="Fork FuzzBot: Unexpected error", exception=e, log_level=logging.ERROR
)
if parsed_args.pause_on_invariance_fail:
logging.error(
"Pausing pool (chain:%s port:%s) on crash %s",
Expand Down Expand Up @@ -396,7 +398,7 @@ def parse_arguments(argv: Sequence[str] | None = None) -> Args:
)
parser.add_argument(
"--num-iterations-per-episode",
default=1000,
default=300,
help="The number of iterations to run for each random pool config.",
)

Expand Down
10 changes: 3 additions & 7 deletions src/agent0/core/hyperdrive/policies/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def get_available_actions(
"""
pool_state = interface.current_pool_state
# The minimum transaction amount is dependent on if we're trading with
# base or vault shares
# base or vault shares. The config's min transaction amount is in units of base.
if interface.base_is_yield:
minimum_transaction_amount = interface.get_minimum_transaction_amount_shares()
else:
Expand Down Expand Up @@ -386,12 +386,8 @@ def add_liquidity_with_random_amount(
list[Trade[HyperdriveMarketAction]]
A list with a single Trade element for adding liquidity to a Hyperdrive pool.
"""
# The minimum transaction amount is dependent on if we're trading with
# base or vault shares
if interface.base_is_yield:
minimum_transaction_amount = interface.get_minimum_transaction_amount_shares()
else:
minimum_transaction_amount = interface.pool_config.minimum_transaction_amount
# The minimum transaction amount input is always compared against the pool config's minimum transaction amount
minimum_transaction_amount = interface.pool_config.minimum_transaction_amount

# take a guess at the trade amount, which should be about 10% of the agent’s budget
initial_trade_amount = FixedPoint(
Expand Down
3 changes: 2 additions & 1 deletion src/agent0/ethpy/hyperdrive/interface/_contract_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ def _get_minimum_transaction_amount_shares(
return FixedPoint(scaled_value=shares_value)
except PypechainCallException:
# Fallback to using vault share price
shares_value = minimum_transaction_amount_base / interface.current_pool_state.pool_info.vault_share_price
# We div_up to overestimate the min txn amount
shares_value = minimum_transaction_amount_base.div_up(interface.current_pool_state.pool_info.vault_share_price)
return shares_value


Expand Down
1 change: 0 additions & 1 deletion src/agent0/hyperfuzz/system_fuzz/invariant_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ def run_invariant_checks(
rollbar_data=rollbar_data,
rollbar_log_filter_func=rollbar_log_filter_func,
)
logging.info("Done")
return out_exceptions


Expand Down
85 changes: 75 additions & 10 deletions src/agent0/hyperfuzz/system_fuzz/run_fuzz_bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
from pypechain.core import PypechainCallException

from agent0 import Chain, Hyperdrive, LocalChain, LocalHyperdrive, PolicyZoo
from agent0.chainsync.db.hyperdrive import get_trade_events
from agent0.core.base.make_key import make_private_key
from agent0.core.hyperdrive.interactive.hyperdrive_agent import HyperdriveAgent
from agent0.ethpy.base import set_anvil_account_balance
from agent0.hyperfuzz import FuzzAssertionException
from agent0.hyperfuzz.system_fuzz.invariant_checks import run_invariant_checks
from agent0.hyperlogs.rollbar_utilities import log_rollbar_exception
from agent0.hyperlogs.rollbar_utilities import log_rollbar_exception, log_rollbar_message

ONE_HOUR_IN_SECONDS = 60 * 60
ONE_DAY_IN_SECONDS = ONE_HOUR_IN_SECONDS * 24
Expand Down Expand Up @@ -52,6 +53,8 @@
LP_SHARE_PRICE_GOVERNANCE_LP_FEE_RANGE: tuple[float, float] = (0, 0)
LP_SHARE_PRICE_GOVERNANCE_ZOMBIE_FEE_RANGE: tuple[float, float] = (0, 0)

TRADE_COUNT_PERIODIC_CHECK = 100


# pylint: disable=too-many-locals
def generate_fuzz_hyperdrive_config(rng: Generator, lp_share_price_test: bool, steth: bool) -> LocalHyperdrive.Config:
Expand Down Expand Up @@ -137,6 +140,67 @@ def generate_fuzz_hyperdrive_config(rng: Generator, lp_share_price_test: bool, s
)


def _check_trades_made_on_pool(
chain: Chain, hyperdrive_pools: Sequence[Hyperdrive], fuzz_start_block: int, iteration: int
):
# Get histogram counts of trades made on each pool
# Note we don't use interactive interface, as we want to do one query to get
# trade events for all tracked pools
assert chain.db_session is not None
trade_events = get_trade_events(chain.db_session, all_token_deltas=False)
# pylint: disable=protected-access
trade_events = chain._add_hyperdrive_name_to_dataframe(trade_events, "hyperdrive_address")
# Filter for trades since start of fuzzing
trade_events = trade_events[trade_events["block_number"] >= fuzz_start_block]
# Get counts
trade_counts = trade_events.groupby(["hyperdrive_name", "event_type"])["id"].count()

logging.info("Trade counts: %s", trade_counts)

# After 50 iterations, we expect all pools to make at least one trade
# Iteration at this point has already been incremented
if iteration % TRADE_COUNT_PERIODIC_CHECK == 0:
trade_counts = trade_counts.reset_index()
# Omission of rows means no trades of that type went through
for pool in hyperdrive_pools:
has_err = False
error_message = ""
if pool.name not in trade_counts["hyperdrive_name"].values:
has_err = True
error_message = f"Pool {pool.name} did not make any trades after {iteration} iterations"
else:
pool_trade_event_counts = trade_counts[trade_counts["hyperdrive_name"] == pool.name][
"event_type"
].values
if "OpenLong" not in pool_trade_event_counts:
has_err = True
error_message = f"Pool {pool.name} did not make any OpenLong trades after {iteration} iterations"
if "OpenShort" not in pool_trade_event_counts:
has_err = True
error_message = f"Pool {pool.name} did not make any OpenShort trades after {iteration} iterations"
if "CloseLong" not in pool_trade_event_counts:
has_err = True
error_message = f"Pool {pool.name} did not make any CloseLong trades after {iteration} iterations"
if "CloseShort" not in pool_trade_event_counts:
has_err = True
error_message = f"Pool {pool.name} did not make any CloseShort trades after {iteration} iterations"
if "AddLiquidity" not in pool_trade_event_counts:
has_err = True
error_message = (
f"Pool {pool.name} did not make any AddLiquidity trades after {iteration} iterations"
)
if "RemoveLiquidity" not in pool_trade_event_counts:
has_err = True
error_message = (
f"Pool {pool.name} did not make any RemoveLiquidity trades after {iteration} iterations"
)

if has_err:
error_message = "FuzzBots: " + error_message
logging.error(error_message)
log_rollbar_message(error_message, logging.ERROR)


def run_fuzz_bots(
chain: Chain,
hyperdrive_pools: Hyperdrive | Sequence[Hyperdrive],
Expand Down Expand Up @@ -245,7 +309,7 @@ def run_fuzz_bots(
policy=PolicyZoo.random,
policy_config=PolicyZoo.random.Config(
slippage_tolerance=slippage_tolerance,
trade_chance=FixedPoint("0.8"),
trade_chance=FixedPoint("1.0"),
randomly_ignore_slippage_tolerance=True,
),
)
Expand All @@ -266,7 +330,7 @@ def run_fuzz_bots(
policy=PolicyZoo.random_hold,
policy_config=PolicyZoo.random_hold.Config(
slippage_tolerance=slippage_tolerance,
trade_chance=FixedPoint("0.8"),
trade_chance=FixedPoint("1.0"),
randomly_ignore_slippage_tolerance=True,
max_open_positions_per_pool=1_000,
),
Expand All @@ -285,17 +349,20 @@ def run_fuzz_bots(
# Make trades until the user or agents stop us
logging.info("Trading...")
iteration = 0
# Get block before start fuzzing
fuzz_start_block = chain.block_number()

while True:
if num_iterations is not None and iteration >= num_iterations:
break
iteration += 1
# Execute the agent policies
trades = []
if run_async:
# There are race conditions throughout that need to be fixed here
raise NotImplementedError("Running async not implemented")

for pool in hyperdrive_pools:
logging.info("Trading on %s", pool.name)
# Execute the agent policies
for agent in agents:
# If we're checking invariance, and we're doing the lp share test,
# we need to get the pending pool state here before the trades.
Expand All @@ -315,7 +382,7 @@ def run_fuzz_bots(
# a contract call.
# TODO this can result in duplicate entries of the same error
log_rollbar_exception(
rollbar_log_prefix=f"Unexpected contract call error on pool {pool.name}",
rollbar_log_prefix=f"FuzzBots: Unexpected contract call error on pool {pool.name}",
exception=exc,
log_level=logging.ERROR,
)
Expand All @@ -325,8 +392,6 @@ def run_fuzz_bots(
# Otherwise, we ignore crashes, we want the bot to keep trading
# These errors will get logged regardless

trades.append(agent_trade)

# Check invariance on every iteration if we're not doing lp_share_price_test.
# Only check invariance if a trade was executed for lp_share_price_test.
# This is because the lp_share_price_test requires a trade to be executed
Expand Down Expand Up @@ -362,8 +427,8 @@ def run_fuzz_bots(
# Otherwise, we raise a new fuzz assertion exception wht the list of exceptions
raise FuzzAssertionException(*fuzz_exceptions)

# Logs trades
logging.debug([[trade.__name__ for trade in agent_trade] for agent_trade in trades])
# Check trades on pools and log if no trades have been made on any of the pools
_check_trades_made_on_pool(chain, hyperdrive_pools, fuzz_start_block, iteration)

# Check agent funds and refund if necessary
assert len(agents) > 0
Expand Down

0 comments on commit 7715078

Please sign in to comment.