Skip to content

Commit

Permalink
lp share price fuzz testing updates (#1480)
Browse files Browse the repository at this point in the history
- LP share price now is a separate run with the `--lp-share-price-test`
flag that sets its own fuzz parameters.
- If this flag is set, we only run the lp share price test with 0 - 10%
variable rate and no fees.
  - If this flag isn't set, we skip the lp share price test

- Updating lp profitability invariant check with more epsilon
  • Loading branch information
slundqui authored May 21, 2024
1 parent a34876d commit ed0aa8a
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 24 deletions.
82 changes: 78 additions & 4 deletions scripts/local_fuzz_bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

from __future__ import annotations

import argparse
import random
import sys
from typing import NamedTuple, Sequence

import numpy as np

Expand All @@ -11,16 +14,32 @@
from agent0.hyperlogs.rollbar_utilities import initialize_rollbar


def main() -> None:
"""Runs local fuzz bots."""
def main(argv: Sequence[str] | None = None) -> None:
"""Runs local fuzz bots.
Arguments
---------
argv: Sequence[str]
A sequence containing the uri to the database server and the test epsilon.
"""
# TODO consolidate setup into single function

parsed_args = parse_arguments(argv)

log_to_rollbar = initialize_rollbar("localfuzzbots")

rng_seed = random.randint(0, 10000000)
rng = np.random.default_rng(rng_seed)

local_chain_config = LocalChain.Config(chain_port=11111, db_port=22222, block_timestamp_interval=12)
# Set different ports if we're doing lp share price test
if parsed_args.lp_share_price_test:
chain_port = 11111
db_port = 22222
else:
chain_port = 33333
db_port = 44444

local_chain_config = LocalChain.Config(chain_port=chain_port, db_port=db_port, block_timestamp_interval=12)

while True:
# Build interactive local hyperdrive
Expand All @@ -29,7 +48,9 @@ def main() -> None:
chain = LocalChain(local_chain_config)

# Fuzz over config values
hyperdrive_config = generate_fuzz_hyperdrive_config(rng, log_to_rollbar, rng_seed)
hyperdrive_config = generate_fuzz_hyperdrive_config(
rng, log_to_rollbar, rng_seed, lp_share_price_test=parsed_args.lp_share_price_test
)
hyperdrive_pool = LocalHyperdrive(chain, hyperdrive_config)

# TODO submit multiple transactions per block
Expand All @@ -43,10 +64,63 @@ def main() -> None:
random_advance_time=True,
random_variable_rate=True,
num_iterations=3000,
lp_share_price_test=parsed_args.lp_share_price_test,
)

chain.cleanup()


class Args(NamedTuple):
"""Command line arguments for the invariant checker."""

lp_share_price_test: bool


def namespace_to_args(namespace: argparse.Namespace) -> Args:
"""Converts argprase.Namespace to Args.
Arguments
---------
namespace: argparse.Namespace
Object for storing arg attributes.
Returns
-------
Args
Formatted arguments
"""
return Args(
lp_share_price_test=namespace.lp_share_price_test,
)


def parse_arguments(argv: Sequence[str] | None = None) -> Args:
"""Parses input arguments.
Arguments
---------
argv: Sequence[str]
The argv values returned from argparser.
Returns
-------
Args
Formatted arguments
"""
parser = argparse.ArgumentParser(description="Runs a loop to check Hyperdrive invariants at each block.")
parser.add_argument(
"--lp-share-price-test",
default=False,
action="store_true",
help="Runs the lp share price fuzz with specific fee and rate parameters.",
)

# Use system arguments if none were passed
if argv is None:
argv = sys.argv

return namespace_to_args(parser.parse_args())


if __name__ == "__main__":
main()
47 changes: 34 additions & 13 deletions src/agent0/hyperfuzz/system_fuzz/invariant_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def run_invariant_checks(
raise_error_on_failure: bool = False,
log_to_rollbar: bool = True,
pool_name: str | None = None,
lp_share_price_test: bool | None = None,
) -> None:
"""Run the invariant checks.
Expand Down Expand Up @@ -54,6 +55,9 @@ def run_invariant_checks(
If True, log to rollbar if any invariant check fails.
pool_name: str | None
The name of the pool for crash reporting information.
lp_share_price_test: bool | None, optional
If True, only test the lp share price. If False, skips the lp share price test.
If None (default), runs all tests.
"""
# TODO cleanup
# pylint: disable=too-many-locals
Expand All @@ -65,17 +69,35 @@ def run_invariant_checks(
exception_message: list[str] = ["Continuous Fuzz Bots Invariant Checks"]
exception_data: dict[str, Any] = {}

results: list[InvariantCheckResults] = [
_check_eth_balances(pool_state),
_check_base_balances(pool_state, interface.base_is_eth),
_check_total_shares(pool_state),
_check_minimum_share_reserves(pool_state),
_check_solvency(pool_state),
_check_present_value_greater_than_idle_shares(interface, pool_state),
_check_lp_share_price(interface, test_epsilon, pool_state),
_check_checkpointing_should_never_fail(interface, pool_state),
_check_initial_lp_profitable(pool_state),
]
results: list[InvariantCheckResults]
if lp_share_price_test is None:
results = [
_check_lp_share_price(interface, test_epsilon, pool_state),
_check_eth_balances(pool_state),
_check_base_balances(pool_state, interface.base_is_eth),
_check_total_shares(pool_state),
_check_minimum_share_reserves(pool_state),
_check_solvency(pool_state),
_check_present_value_greater_than_idle_shares(interface, pool_state),
_check_checkpointing_should_never_fail(interface, pool_state),
_check_initial_lp_profitable(pool_state),
]
else:
if lp_share_price_test:
results = [
_check_lp_share_price(interface, test_epsilon, pool_state),
]
else:
results = [
_check_eth_balances(pool_state),
_check_base_balances(pool_state, interface.base_is_eth),
_check_total_shares(pool_state),
_check_minimum_share_reserves(pool_state),
_check_solvency(pool_state),
_check_present_value_greater_than_idle_shares(interface, pool_state),
_check_checkpointing_should_never_fail(interface, pool_state),
_check_initial_lp_profitable(pool_state),
]

for failed, message, data in results:
any_check_failed = failed | any_check_failed
Expand Down Expand Up @@ -355,9 +377,8 @@ def _check_lp_share_price(

def _check_initial_lp_profitable(pool_state: PoolState, epsilon: FixedPoint | None = None) -> InvariantCheckResults:

# There's a rounding difference of 1 wei due to rounding lp_rate down
if epsilon is None:
epsilon = FixedPoint(scaled_value=1)
epsilon = FixedPoint("0.005")

failed = False
exception_message = ""
Expand Down
44 changes: 37 additions & 7 deletions src/agent0/hyperfuzz/system_fuzz/run_local_fuzz_bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

# Position and checkpoint duration are in units of hours, as
# the `factory_checkpoint_duration_resolution` is 1 hour
POSITION_DURATION_HOURS_RANGE: tuple[int, int] = (91, ONE_YEAR_IN_HOURS)
POSITION_DURATION_HOURS_RANGE: tuple[int, int] = (91, 2 * ONE_YEAR_IN_HOURS)
CHECKPOINT_DURATION_HOURS_RANGE: tuple[int, int] = (1, 24)

# The initial time stretch APR
Expand All @@ -41,8 +41,18 @@
# The fee percentage. The range controls all 4 fees
FEE_RANGE: tuple[float, float] = (0.0001, 0.2)

# Special case for checking block to block lp share price
LP_SHARE_PRICE_VARIABLE_RATE_RANGE: tuple[float, float] = (0, 0.1)
LP_SHARE_PRICE_FLAT_FEE_RANGE: tuple[float, float] = (0, 0)
LP_SHARE_PRICE_CURVE_FEE_RANGE: tuple[float, float] = (0, 0)
LP_SHARE_PRICE_GOVERNANCE_LP_FEE_RANGE: tuple[float, float] = (0, 0)
LP_SHARE_PRICE_GOVERNANCE_ZOMBIE_FEE_RANGE: tuple[float, float] = (0, 0)

def generate_fuzz_hyperdrive_config(rng: Generator, log_to_rollbar: bool, rng_seed: int) -> LocalHyperdrive.Config:

# pylint: disable=too-many-locals
def generate_fuzz_hyperdrive_config(
rng: Generator, log_to_rollbar: bool, rng_seed: int, lp_share_price_test: bool
) -> LocalHyperdrive.Config:
"""Fuzz over hyperdrive config.
Arguments
Expand All @@ -53,6 +63,8 @@ def generate_fuzz_hyperdrive_config(rng: Generator, log_to_rollbar: bool, rng_se
If True, log errors to rollbar.
rng_seed: int
Seed for the rng.
lp_share_price_test: bool
If True, uses lp share price test fuzz parameters.
Returns
-------
Expand Down Expand Up @@ -81,8 +93,22 @@ def generate_fuzz_hyperdrive_config(rng: Generator, log_to_rollbar: bool, rng_se
checkpoint_duration = checkpoint_duration_hours * ONE_HOUR_IN_SECONDS

initial_time_stretch_apr = FixedPoint(rng.uniform(*INITIAL_TIME_STRETCH_APR_RANGE))

if lp_share_price_test:
variable_rate_range = LP_SHARE_PRICE_VARIABLE_RATE_RANGE
flat_fee_range = LP_SHARE_PRICE_FLAT_FEE_RANGE
curve_fee_range = LP_SHARE_PRICE_CURVE_FEE_RANGE
governance_lp_fee_range = LP_SHARE_PRICE_GOVERNANCE_LP_FEE_RANGE
governance_zombie_fee_range = LP_SHARE_PRICE_GOVERNANCE_ZOMBIE_FEE_RANGE
else:
variable_rate_range = VARIABLE_RATE_RANGE
flat_fee_range = FEE_RANGE
curve_fee_range = FEE_RANGE
governance_lp_fee_range = FEE_RANGE
governance_zombie_fee_range = FEE_RANGE

# Generate flat fee in terms of APR
flat_fee = FixedPoint(rng.uniform(*FEE_RANGE) * (position_duration / ONE_YEAR_IN_SECONDS))
flat_fee = FixedPoint(rng.uniform(*flat_fee_range) * (position_duration / ONE_YEAR_IN_SECONDS))

return LocalHyperdrive.Config(
preview_before_trade=True,
Expand All @@ -95,16 +121,16 @@ def generate_fuzz_hyperdrive_config(rng: Generator, log_to_rollbar: bool, rng_se
initial_liquidity=FixedPoint(rng.uniform(*INITIAL_LIQUIDITY_RANGE)),
initial_fixed_apr=initial_time_stretch_apr,
initial_time_stretch_apr=initial_time_stretch_apr,
initial_variable_rate=FixedPoint(rng.uniform(*VARIABLE_RATE_RANGE)),
initial_variable_rate=FixedPoint(rng.uniform(*variable_rate_range)),
minimum_share_reserves=FixedPoint(rng.uniform(*MINIMUM_SHARE_RESERVES_RANGE)),
minimum_transaction_amount=FixedPoint(rng.uniform(*MINIMUM_TRANSACTION_AMOUNT_RANGE)),
circuit_breaker_delta=FixedPoint(rng.uniform(*CIRCUIT_BREAKER_DELTA_RANGE)),
position_duration=position_duration,
checkpoint_duration=checkpoint_duration,
curve_fee=FixedPoint(rng.uniform(*FEE_RANGE)),
curve_fee=FixedPoint(rng.uniform(*curve_fee_range)),
flat_fee=flat_fee,
governance_lp_fee=FixedPoint(rng.uniform(*FEE_RANGE)),
governance_zombie_fee=FixedPoint(rng.uniform(*FEE_RANGE)),
governance_lp_fee=FixedPoint(rng.uniform(*governance_lp_fee_range)),
governance_zombie_fee=FixedPoint(rng.uniform(*governance_zombie_fee_range)),
)


Expand Down Expand Up @@ -179,6 +205,7 @@ def run_local_fuzz_bots(
random_advance_time: bool = False,
random_variable_rate: bool = False,
num_iterations: int | None = None,
lp_share_price_test: bool = False,
) -> None:
"""Runs fuzz bots on a hyperdrive pool.
Expand Down Expand Up @@ -217,6 +244,8 @@ def run_local_fuzz_bots(
If True, will randomly change the rate between sets of trades. Defaults to False.
num_iterations: int | None, optional
The number of iterations to run. Defaults to None (infinite)
lp_share_price_test: bool, optional
If True, will test the LP share price. Defaults to False.
"""
# TODO cleanup
# pylint: disable=too-many-arguments
Expand Down Expand Up @@ -314,6 +343,7 @@ def run_local_fuzz_bots(
test_epsilon=invariance_test_epsilon,
raise_error_on_failure=raise_error_on_failed_invariance_checks,
log_to_rollbar=log_to_rollbar,
lp_share_price_test=lp_share_price_test,
)

# Check agent funds and refund if necessary
Expand Down

0 comments on commit ed0aa8a

Please sign in to comment.