Skip to content

Commit

Permalink
refactor: separate supply lens contract (#37)
Browse files Browse the repository at this point in the history
* feat: stablecoin lens as separate contract

* test: testing stablecoin lens

* feat: adjust deployment notebook

* feat: zero lens check

* chore: mamushi formatter

* test: adjusted lens manager
  • Loading branch information
heswithme authored Oct 17, 2024
1 parent 10ab433 commit c79a447
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 51 deletions.
51 changes: 37 additions & 14 deletions contracts/RewardsHandler.vy
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ from contracts.interfaces import IDynamicWeight

implements: IDynamicWeight

from contracts.interfaces import IStablecoinLens

# yearn vault's interface
from interfaces import IVault

Expand All @@ -51,6 +53,7 @@ from interfaces import IVault
# to adjust the rate while only the dao (which has the `DEFAULT_ADMIN_ROLE`)
# can appoint `RATE_MANAGER`s
from snekmate.auth import access_control

initializes: access_control
exports: (
# we don't expose `supportsInterface` from access control
Expand All @@ -64,9 +67,6 @@ exports: (
)

# import custom modules that contain helper functions.
import StablecoinLens as lens
initializes: lens

import TWA as twa
initializes: twa
exports: (
Expand All @@ -92,13 +92,18 @@ event ScalingFactorUpdated:
new_scaling_factor: uint256


event StablecoinLensUpdated:
new_stablecoin_lens: IStablecoinLens


################################################################
# CONSTANTS #
################################################################


RATE_MANAGER: public(constant(bytes32)) = keccak256("RATE_MANAGER")
RECOVERY_MANAGER: public(constant(bytes32)) = keccak256("RECOVERY_MANAGER")
LENS_MANAGER: public(constant(bytes32)) = keccak256("LENS_MANAGER")
WEEK: constant(uint256) = 86_400 * 7 # 7 days
MAX_BPS: constant(uint256) = 10**4 # 100%

Expand All @@ -123,6 +128,9 @@ scaling_factor: public(uint256)
# the minimum amount of rewards requested to the FeeSplitter.
minimum_weight: public(uint256)

# stablecoin circulating supply contract
stablecoin_lens: public(IStablecoinLens)


################################################################
# CONSTRUCTOR #
Expand All @@ -133,20 +141,20 @@ minimum_weight: public(uint256)
def __init__(
_stablecoin: IERC20,
_vault: IVault,
_lens: IStablecoinLens,
minimum_weight: uint256,
scaling_factor: uint256,
controller_factory: lens.IControllerFactory,
admin: address,
):
lens.__init__(controller_factory)

# initialize access control
access_control.__init__()
# admin (most likely the dao) controls who can be a rate manager
access_control._grant_role(access_control.DEFAULT_ADMIN_ROLE, admin)
# admin itself is a RATE_MANAGER and RECOVERY_MANAGER
access_control._grant_role(RATE_MANAGER, admin)
access_control._grant_role(RECOVERY_MANAGER, admin)
access_control._grant_role(LENS_MANAGER, admin)

# deployer does not control this contract
access_control._revoke_role(access_control.DEFAULT_ADMIN_ROLE, msg.sender)

Expand All @@ -158,6 +166,7 @@ def __init__(
self._set_minimum_weight(minimum_weight)
self._set_scaling_factor(scaling_factor)

self._set_stablecoin_lens(_lens)
stablecoin = _stablecoin
vault = _vault

Expand All @@ -180,9 +189,9 @@ def take_snapshot():
deflates the value of the snapshot).
"""

# get the circulating supply from a helper function.
# get the circulating supply from a helper contract.
# supply in circulation = controllers' debt + peg keppers' debt
circulating_supply: uint256 = lens._circulating_supply()
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
Expand All @@ -205,9 +214,7 @@ def process_rewards():

# prevent the rewards from being distributed untill the distribution rate
# has been set
assert (
staticcall vault.profitMaxUnlockTime() != 0
), "rewards should be distributed over time"
assert (staticcall vault.profitMaxUnlockTime() != 0), "rewards should be distributed over time"

# any crvUSD sent to this contract (usually through the fee splitter, but
# could also come from other sources) will be used as a reward for scrvUSD
Expand Down Expand Up @@ -354,6 +361,24 @@ def _set_scaling_factor(new_scaling_factor: uint256):
log ScalingFactorUpdated(new_scaling_factor)


@external
def set_stablecoin_lens(_lens: address):
"""
@notice Setter for the stablecoin lens that determines stablecoin circulating supply.
@param _lens The address of the new stablecoin lens.
"""
access_control._check_role(LENS_MANAGER, msg.sender)
self._set_stablecoin_lens(IStablecoinLens(_lens))


@internal
def _set_stablecoin_lens(_lens: IStablecoinLens):
assert _lens.address != empty(address), "no lens"
self.stablecoin_lens = _lens

log StablecoinLensUpdated(_lens)


@external
def recover_erc20(token: IERC20, receiver: address):
"""
Expand All @@ -370,6 +395,4 @@ def recover_erc20(token: IERC20, receiver: address):
# when funds are recovered the whole balanced is sent to a trusted address.
balance_to_recover: uint256 = staticcall token.balanceOf(self)
assert extcall token.transfer(
receiver, balance_to_recover, default_return_value=True
)
assert extcall token.transfer(receiver, balance_to_recover, default_return_value=True)
6 changes: 6 additions & 0 deletions contracts/StablecoinLens.vy
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ def __init__(_factory: IControllerFactory):
factory = _factory


@view
@external
def circulating_supply() -> uint256:
return self._circulating_supply()


@view
@internal
def _circulating_supply() -> uint256:
Expand Down
4 changes: 4 additions & 0 deletions contracts/interfaces/IStablecoinLens.vyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@view
@external
def circulating_supply() -> uint256:
...
39 changes: 31 additions & 8 deletions scripts/deploy_scrvusd.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@
"id": "12",
"metadata": {},
"source": [
"### 3. RewardsHandler"
"### 3. Stablecoin Lens"
]
},
{
Expand All @@ -184,15 +184,38 @@
"metadata": {},
"outputs": [],
"source": [
"# III. Finally deploy the RewardsHandler\n",
"# III. Now deploy Stablecoin Lens\n",
"StablecoinLens_deployer = boa.load_partial(\"../contracts/StablecoinLens.vy\")\n",
"\n",
"stablecoin_lens = StablecoinLens_deployer(ab.crvusd_controller_factory)\n",
"\n",
"print(f\"Stablecoin lens deployed at {stablecoin_lens.address}\")"
]
},
{
"cell_type": "markdown",
"id": "14",
"metadata": {},
"source": [
"### 4. RewardsHandler"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15",
"metadata": {},
"outputs": [],
"source": [
"# IV. Finally deploy the RewardsHandler\n",
"RewardsHandler_deployer = boa.load_partial(\"../contracts/RewardsHandler.vy\")\n",
"\n",
"rewards_handler = RewardsHandler_deployer(\n",
" ab.crvusd, # stablecoin\n",
" vault_address, # vault\n",
" stablecoin_lens, # lens\n",
" 500, # minimum weight (5%)\n",
" 10_000, # scaling factor (over MAX_BPS)\n",
" ab.crvusd_controller_factory, # controller factory\n",
" ab.dao_agent, # WE CERTAIN ABOUT THIS CONTRACT? [TODO]\n",
")\n",
"\n",
Expand All @@ -201,7 +224,7 @@
},
{
"cell_type": "markdown",
"id": "14",
"id": "16",
"metadata": {},
"source": [
"# II. Post-deployment setup"
Expand All @@ -210,7 +233,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "15",
"id": "17",
"metadata": {},
"outputs": [],
"source": [
Expand Down Expand Up @@ -239,7 +262,7 @@
},
{
"cell_type": "markdown",
"id": "16",
"id": "18",
"metadata": {},
"source": [
"## 3. Vote in the DAO"
Expand All @@ -248,7 +271,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "17",
"id": "19",
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -260,7 +283,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "18",
"id": "20",
"metadata": {},
"outputs": [],
"source": []
Expand Down
14 changes: 7 additions & 7 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ def controller_factory():
return boa.from_etherscan(ab.crvusd_controller_factory, "controller_factory")


@pytest.fixture()
def lens(controller_factory):
return boa.load("contracts/StablecoinLens.vy", controller_factory)


@pytest.fixture()
def vault_factory():
return boa.from_etherscan(ab.yearn_vault_factory, "vault_factory")
Expand Down Expand Up @@ -101,21 +96,26 @@ def minimum_weight():


@pytest.fixture()
def rewards_handler(vault, minimum_weight):
def rewards_handler(vault, minimum_weight, stablecoin_lens):
rh = boa.load(
"contracts/RewardsHandler.vy",
ab.crvusd,
vault,
stablecoin_lens,
minimum_weight, # 5%
10_000, # 1
ab.crvusd_controller_factory,
ab.dao_agent,
)
vault.set_role(rh, 2**11 | 2**5, sender=ab.dao_agent)

return rh


@pytest.fixture()
def stablecoin_lens():
return boa.load("contracts/StablecoinLens.vy", ab.crvusd_controller_factory)


@pytest.fixture()
def active_controllers(fee_splitter):
# useful to call dispatch_fees
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/test_dynamic_weight.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ def raw_weight() -> uint256:


def test_fee_splitter_cap(
fee_splitter, crvusd, vault, rewards_handler, active_controllers, new_depositor
fee_splitter, crvusd, vault, rewards_handler, active_controllers, new_depositor, stablecoin_lens
):
# test were we ask for so much that we hit the cap
fee_splitter_cap = fee_splitter.receivers(0)[1]

circulating_supply = rewards_handler.eval("lens._circulating_supply()")
circulating_supply = stablecoin_lens.circulating_supply()

# with the supply at the time of the fork
# if we deposit 10_000_000 crvUSD in the vault
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/test_stablecoin_lens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FORK_BLOCK_SUPPLY = 62182303636759107878108289


def test_circulating_supply(stablecoin_lens):
# circ supply for block of fork
assert stablecoin_lens.circulating_supply() == FORK_BLOCK_SUPPLY
9 changes: 0 additions & 9 deletions tests/mocks/MockLens.vy

This file was deleted.

18 changes: 16 additions & 2 deletions tests/unitary/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ def rate_manager(request, rewards_handler):
return _rate_manager


@pytest.fixture(params=[CURVE_DAO, boa.env.generate_address("lens_manager")])
def lens_manager(request, rewards_handler):
_lens_manager = request.param
if _lens_manager != CURVE_DAO:
rewards_handler.grantRole(rewards_handler.LENS_MANAGER(), _lens_manager, sender=CURVE_DAO)

return _lens_manager


@pytest.fixture(params=[CURVE_DAO, boa.env.generate_address("recovery_manager")])
def recovery_manager(request, rewards_handler):
_recovery_manager = request.param
Expand Down Expand Up @@ -156,16 +165,21 @@ def rewards_handler(
role_manager,
minimum_weight,
scaling_factor,
mock_controller_factory,
stablecoin_lens,
curve_dao,
dev_deployer,
):
rewards_handler_deployer = boa.load_partial("contracts/RewardsHandler.vy")
with boa.env.prank(dev_deployer):
rh = rewards_handler_deployer(
crvusd, vault, minimum_weight, scaling_factor, mock_controller_factory, curve_dao
crvusd, vault, stablecoin_lens, minimum_weight, scaling_factor, curve_dao
)

vault.set_role(rh, 2**11 | 2**5, sender=role_manager)

return rh


@pytest.fixture()
def stablecoin_lens(mock_controller_factory):
return boa.load("contracts/StablecoinLens.vy", mock_controller_factory)
12 changes: 12 additions & 0 deletions tests/unitary/rewards_handler/test_internal_set_stablecoin_lens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import boa


def test_default_behavior(rewards_handler):
new_lens = boa.env.generate_address()
rewards_handler.internal._set_stablecoin_lens(new_lens)
assert rewards_handler.stablecoin_lens() == new_lens


def test_zero_address(rewards_handler):
with boa.reverts("no lens"):
rewards_handler.internal._set_stablecoin_lens("0x0000000000000000000000000000000000000000")
Loading

0 comments on commit c79a447

Please sign in to comment.