-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add
block_number_before_timestamp
; chain.mine()
; ezeth price chec…
…k script (#1728)
- Loading branch information
Showing
8 changed files
with
318 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
"""Script to check the ezeth vault share price.""" | ||
|
||
import argparse | ||
import os | ||
|
||
from dotenv import load_dotenv | ||
|
||
from agent0 import Chain, Hyperdrive | ||
from agent0.utils import block_number_before_timestamp | ||
|
||
load_dotenv(".env") | ||
DEV_RPC_URI = os.getenv("DEV_RPC_URI", "") | ||
RPC_URI = DEV_RPC_URI | ||
REGISTRY_ADDRESS = os.getenv("REGISTRY_ADDRESS", "") | ||
|
||
|
||
def main(start_block_timestamp: int, lookback_length: int): | ||
"""Main entry point. | ||
Arguments | ||
--------- | ||
start_block_timestamp: int | ||
The start block timestamp, in seconds (should be after the ezETH Hyperdrive pool was deployed). | ||
lookback_length: int | ||
The number of seconds to lookback from the start block. | ||
""" | ||
with Chain(RPC_URI, config=Chain.Config(no_postgres=True)) as chain: | ||
# Get the ezeth pool | ||
pool_name = "ElementDAO 182 Day ezETH Hyperdrive" | ||
registered_pools = Hyperdrive.get_hyperdrive_pools_from_registry( | ||
chain, | ||
registry_address=REGISTRY_ADDRESS, | ||
) | ||
ezeth_pool = [pool for pool in registered_pools if pool.name == pool_name][0] | ||
web3 = ezeth_pool.interface.web3 | ||
|
||
# If the start block is zero, use the current block | ||
if start_block_timestamp <= 0: | ||
start_block_timestamp = chain.block_time() | ||
start_block_number = chain.block_number() | ||
# Otherwise, find the block with the given blocktime | ||
else: | ||
start_block_number = block_number_before_timestamp(web3, start_block_timestamp) | ||
|
||
start_pool_state = ezeth_pool.interface.get_hyperdrive_state(block_identifier=start_block_number) | ||
start_vault_share_price = start_pool_state.pool_info.vault_share_price | ||
|
||
lookback_timestamp = start_block_timestamp - lookback_length | ||
lookback_block_number = block_number_before_timestamp(web3, lookback_timestamp) | ||
lookback_pool_state = ezeth_pool.interface.get_hyperdrive_state(block_identifier=lookback_block_number) | ||
lookback_vault_share_price = lookback_pool_state.pool_info.vault_share_price | ||
|
||
print( | ||
f"Calculating vault share price difference between block {start_block_number} and block {lookback_block_number}" | ||
) | ||
print(f"time = {start_block_timestamp}; vault share price = {start_vault_share_price}") | ||
print(f"time = {lookback_timestamp}; vault share price = {lookback_vault_share_price}") | ||
print(f"Difference: {start_vault_share_price - lookback_vault_share_price=}") | ||
if start_vault_share_price < lookback_vault_share_price: | ||
print("WARNING: NEGATIVE INTEREST!") | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser(description="Check EZETH pool vault share price difference.") | ||
parser.add_argument( | ||
"--start-block", | ||
type=int, | ||
default=0, # 0 will use current block. | ||
help="The starting block number.", | ||
) | ||
parser.add_argument( | ||
"--lookback", | ||
type=int, | ||
default=60 * 60 * 12, # 12 hours | ||
help="The number of blocks to lookback from the starting block.", | ||
) | ||
args = parser.parse_args() | ||
main(args.start_block, args.lookback) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
src/agent0/core/hyperdrive/interactive/local_chain_test.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
"""Tests for convenience functions in the Local Chain.""" | ||
|
||
import numpy as np | ||
import pytest | ||
|
||
from agent0 import LocalChain | ||
|
||
|
||
@pytest.mark.docker | ||
@pytest.mark.anvil | ||
def test_mine_blocks(fast_chain_fixture: LocalChain): | ||
"""Test that ensures block_before_timestamp always returns a block number | ||
that is closest to but before the timestamp. | ||
""" | ||
|
||
num_fuzz_runs = 50 | ||
|
||
time_between_blocks = fast_chain_fixture.config.block_timestamp_interval | ||
assert time_between_blocks is not None | ||
|
||
previous_block_number = fast_chain_fixture.block_number() | ||
previous_block_time = fast_chain_fixture.block_time() | ||
|
||
for _ in range(num_fuzz_runs): | ||
# Advance the chain a random number of blocks | ||
num_blocks = np.random.randint(1, 1_000) | ||
fast_chain_fixture.mine_blocks(num_blocks) | ||
|
||
# Get the new block number and time | ||
current_block_number = fast_chain_fixture.block_number() | ||
current_block_time = fast_chain_fixture.block_time() | ||
avg_time_between_blocks = (current_block_time - previous_block_time) / num_blocks | ||
|
||
# Check that we advanced the blocks correctly | ||
assert ( | ||
current_block_number - previous_block_number == num_blocks | ||
), f"{current_block_number-previous_block_number=} != {num_blocks=}" | ||
assert ( | ||
current_block_time - previous_block_time == num_blocks * time_between_blocks | ||
), f"{current_block_time-previous_block_time=} != {num_blocks * time_between_blocks=}" | ||
assert avg_time_between_blocks == time_between_blocks, f"{avg_time_between_blocks=} != {time_between_blocks=}" | ||
|
||
previous_block_number = current_block_number | ||
previous_block_time = current_block_time |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
"""General utility functions""" | ||
|
||
from .async_runner import async_runner | ||
from .block_before_timestamp import block_number_before_timestamp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
"""Get the number of the block that is at or immediately before the given timestamp.""" | ||
|
||
from __future__ import annotations | ||
|
||
from eth_typing import BlockNumber | ||
from web3 import Web3 | ||
from web3.types import Timestamp | ||
|
||
# pylint: disable=too-many-locals | ||
|
||
|
||
def block_number_before_timestamp(web3: Web3, block_timestamp: Timestamp | int) -> BlockNumber: | ||
"""Finds the closest block number that is before or at the given block time. | ||
Arguments | ||
--------- | ||
web3: Web3 | ||
The web3 instance. | ||
block_timestamp: BlockTime | int | ||
The block time to find the closest block to. | ||
Returns | ||
------- | ||
BlockNumber | ||
The closest block number to the given block time. | ||
""" | ||
# Get the current block number and timestamp | ||
block_timestamp = Timestamp(block_timestamp) | ||
current_block = web3.eth.get_block("latest") | ||
current_block_number = current_block.get("number", None) | ||
assert current_block_number is not None | ||
if current_block_number < 3: | ||
raise ValueError("The current block number must be >= 2.") | ||
current_block_timestamp = current_block.get("timestamp", None) | ||
assert current_block_timestamp is not None | ||
|
||
# Estimate the average block time | ||
earlier_block_number = current_block_number // 2 | ||
earlier_block_delta = current_block_number - earlier_block_number | ||
if earlier_block_delta <= 0: | ||
raise ValueError("Error estimating the delta blocks.") | ||
earlier_block_timestamp = web3.eth.get_block(earlier_block_number).get("timestamp", None) | ||
assert earlier_block_timestamp is not None | ||
avg_time_between_blocks = (current_block_timestamp - earlier_block_timestamp) / earlier_block_delta | ||
|
||
# Estimate the block number corresponding to the user provided timestamp | ||
delta_time = current_block_timestamp - block_timestamp | ||
estimated_block_number = current_block_number - (delta_time // avg_time_between_blocks) | ||
|
||
# Establish upper and lower bounds | ||
left = int(max(0, estimated_block_number - 100)) # search 100 blocks before estimated block | ||
right = int(min(current_block_number, estimated_block_number + 100)) # search 100 blocks after estimated block | ||
|
||
# Ensure bounds of binary search is within the target timestamp | ||
left_timestamp = web3.eth.get_block(left).get("timestamp", None) | ||
assert left_timestamp is not None | ||
if left_timestamp > block_timestamp: | ||
left = 0 | ||
right_timestamp = web3.eth.get_block(right).get("timestamp", None) | ||
assert right_timestamp is not None | ||
if right_timestamp < block_timestamp: | ||
right = current_block_number | ||
|
||
# Use a binary search to find the block | ||
while left <= right: | ||
mid = int((left + right) // 2) | ||
block = web3.eth.get_block(BlockNumber(mid)) | ||
search_block_timestamp = block.get("timestamp", None) | ||
assert search_block_timestamp is not None | ||
if search_block_timestamp > block_timestamp: | ||
# The mid point is later than the block we want, set right to mid-1 | ||
right = mid - 1 | ||
elif search_block_timestamp < block_timestamp: | ||
# The mid point is earlier than the block we want | ||
# The user could enter a time that is greater than the nearest block | ||
# timestamp but less than the next block timestamp | ||
next_block = web3.eth.get_block(BlockNumber(mid + 1)) | ||
next_block_timestamp = next_block.get("timestamp", None) | ||
assert next_block_timestamp is not None | ||
if next_block_timestamp > block_timestamp: | ||
# The user time is between the current and next block | ||
return BlockNumber(mid) | ||
# If the next block wasn't right, then set it to left | ||
left = mid + 1 | ||
else: | ||
# The mid point is the right block | ||
return BlockNumber(mid) | ||
return BlockNumber(left) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
"""Test for the block_before_timestamp function.""" | ||
|
||
import numpy as np | ||
import pytest | ||
|
||
from agent0 import LocalChain, LocalHyperdrive | ||
|
||
from .block_before_timestamp import block_number_before_timestamp | ||
|
||
|
||
@pytest.mark.docker | ||
@pytest.mark.anvil | ||
def test_block_number_before_timestamp(fast_chain_fixture: LocalChain): | ||
"""Test that ensures block_before_timestamp always returns a block number | ||
with a timestamp that is closest to but before the provided timestamp. | ||
""" | ||
# Run the test a bunch of times because the function is iterative. | ||
num_fuzz_runs = 2_000 | ||
|
||
# Advance the chain NUM_FUZZ_RUNS blocks | ||
initial_block_number = fast_chain_fixture.block_number() | ||
initial_block_time = fast_chain_fixture.block_time() | ||
|
||
fast_chain_fixture.mine_blocks(1) | ||
initial_block_plus_one_time = fast_chain_fixture.block_time() | ||
|
||
time_between_blocks = initial_block_plus_one_time - initial_block_time | ||
assert time_between_blocks >= 1 | ||
fast_chain_fixture.mine_blocks(num_fuzz_runs - 1) | ||
chain_block_number = fast_chain_fixture.block_number() | ||
assert chain_block_number == initial_block_number + num_fuzz_runs | ||
|
||
# Start out a few blocks behind the latest | ||
hyperdrive_interface = LocalHyperdrive(fast_chain_fixture, LocalHyperdrive.Config()).interface | ||
for fuzz_iter in range(num_fuzz_runs): | ||
# Grab a random block in the past & get the time | ||
if fuzz_iter == 0: # test an edge case on the first iteration | ||
test_block_number = initial_block_number + 3 | ||
elif fuzz_iter == 1: # test an edge case on the second iteration | ||
test_block_number = chain_block_number - 1 | ||
else: | ||
test_block_number = int(np.random.randint(initial_block_number + 3, chain_block_number - 1)) | ||
test_block_time = hyperdrive_interface.get_block_timestamp(hyperdrive_interface.get_block(test_block_number)) | ||
|
||
# Add a random amount of time that is less than the time between blocks | ||
time_delta = int(np.random.randint(0, time_between_blocks)) | ||
|
||
# Find the block that was closest to this timestamp | ||
inferred_block_number = block_number_before_timestamp(hyperdrive_interface.web3, test_block_time + time_delta) | ||
inferred_block_time = hyperdrive_interface.get_block_timestamp( | ||
hyperdrive_interface.get_block(inferred_block_number) | ||
) | ||
|
||
assert ( | ||
inferred_block_number == test_block_number | ||
), f"Inferred block number = {inferred_block_number} should be less than or equal to {test_block_number=}." | ||
assert ( | ||
inferred_block_time <= test_block_time | ||
), f"{inferred_block_time=} should be less than or equal to {test_block_time=}." |