Skip to content

Commit

Permalink
Alter snapshots logic (#39)
Browse files Browse the repository at this point in the history
* take snapshots automatically

* fix: larger dt

* ci: some parallelisation

* test: new test for new features

* test: fix hardcoded init value check

* fix: get rid of balanceOf

* chore: commit format
  • Loading branch information
heswithme authored Oct 17, 2024
1 parent 4dcb5ef commit 03f5e58
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 9 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,9 @@ jobs:
env:
ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
run: uv run pytest ${{ matrix.folder }}
run: |
if [ "${{ matrix.folder }}" == "tests/unitary" ]; then
uv run pytest ${{ matrix.folder }} -n=auto
else
uv run pytest ${{ matrix.folder }}
fi
22 changes: 16 additions & 6 deletions contracts/RewardsHandler.vy
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def __init__(

twa.__init__(
WEEK, # twa_window = 1 week
1, # min_snapshot_dt_seconds = 1 second
600, # min_snapshot_dt_seconds = 600 seconds
)

self._set_minimum_weight(minimum_weight)
Expand Down Expand Up @@ -188,15 +188,22 @@ def take_snapshot():
or the minimum amount of rewards can always be increased (if a malicious actor
deflates the value of the snapshot).
"""
self._take_snapshot()


@internal
def _take_snapshot():
"""
@notice Internal function to take a snapshot of the current deposited supply
ratio in the vault.
"""
# get the circulating supply from a helper contract.
# supply in circulation = controllers' debt + peg keppers' debt
circulating_supply: uint256 = staticcall self.stablecoin_lens.circulating_supply()

# obtain the supply of crvUSD contained in the vault by simply checking its
# balance since it's an ERC4626 vault. This will also take into account
# rewards that are not yet distributed.
supply_in_vault: uint256 = staticcall stablecoin.balanceOf(vault.address)
# obtain the supply of crvUSD contained in the vault by checking its totalAssets.
# This will not take into account rewards that are not yet distributed.
supply_in_vault: uint256 = staticcall vault.totalAssets()

# here we intentionally reduce the precision of the ratio because the
# dynamic weight interface expects a percentage in BPS.
Expand All @@ -206,11 +213,14 @@ def take_snapshot():


@external
def process_rewards():
def process_rewards(take_snapshot: bool = True):
"""
@notice Permissionless function that let anyone distribute rewards (if any) to
the crvUSD vault.
"""
# optional (advised) snapshot before distributing the rewards
if take_snapshot:
self._take_snapshot()

# prevent the rewards from being distributed untill the distribution rate
# has been set
Expand Down
4 changes: 3 additions & 1 deletion contracts/TWA.vy
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ def _take_snapshot(_value: uint256):
@notice Stores a snapshot of the tracked value.
@param _value The value to store.
"""
if self.last_snapshot_timestamp + self.min_snapshot_dt_seconds <= block.timestamp:
if (len(self.snapshots) == 0) or ( # First snapshot
self.last_snapshot_timestamp + self.min_snapshot_dt_seconds <= block.timestamp # after dt
):
self.last_snapshot_timestamp = block.timestamp
self.snapshots.append(
Snapshot(tracked_value=_value, timestamp=block.timestamp)
Expand Down
20 changes: 20 additions & 0 deletions tests/unitary/rewards_handler/test_process_rewards.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,23 @@ def test_no_rewards(rewards_handler, rate_manager):

with boa.reverts("no rewards to distribute"):
rewards_handler.process_rewards()


def test_snapshots_taking(rewards_handler, rate_manager, crvusd):
rewards_handler.set_distribution_time(1234, sender=rate_manager) # to enable process_rewards
assert rewards_handler.get_len_snapshots() == 0
boa.deal(crvusd, rewards_handler, 1)
rewards_handler.process_rewards()
assert crvusd.balanceOf(rewards_handler) == 0 # crvusd gone
assert rewards_handler.get_len_snapshots() == 1 # first snapshot taken

boa.deal(crvusd, rewards_handler, 1)
rewards_handler.process_rewards()
assert crvusd.balanceOf(rewards_handler) == 0 # crvusd gone (again)
assert rewards_handler.get_len_snapshots() == 1 # not changed since dt has not passed

boa.env.time_travel(seconds=rewards_handler.min_snapshot_dt_seconds())
boa.deal(crvusd, rewards_handler, 1)
rewards_handler.process_rewards()
assert crvusd.balanceOf(rewards_handler) == 0 # crvusd gone (they always go)
assert rewards_handler.get_len_snapshots() == 2 # changed since dt has passed
2 changes: 1 addition & 1 deletion tests/unitary/rewards_handler/test_rh_constructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ def test_default_behavior(
# eoa would be the deployer from which we revoke the role
assert not rewards_handler.hasRole(rewards_handler.DEFAULT_ADMIN_ROLE(), boa.env.eoa)
assert rewards_handler.eval("twa.twa_window") == 86_400 * 7
assert rewards_handler.eval("twa.min_snapshot_dt_seconds") == 1
assert rewards_handler.eval("twa.min_snapshot_dt_seconds") == 600 # 10 minutes
15 changes: 15 additions & 0 deletions tests/unitary/rewards_handler/test_take_snapshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import pytest
import boa


@pytest.mark.gas_profile
def test_take_snapshot_compute(rewards_handler):
n_days = 7
dt = 600
snaps_per_day = 86_400 // dt
for i_day in range(n_days):
for i_snap in range(snaps_per_day):
rewards_handler.take_snapshot()
boa.env.time_travel(seconds=dt)
twa = rewards_handler.compute_twa()
assert twa >= 0, f"Computed TWA is negative: {twa}"

0 comments on commit 03f5e58

Please sign in to comment.