Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: circulating supply helper #2

Merged
merged 12 commits into from
Sep 18, 2024
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: CI Workflow

on: [push, pull_request]
on: [push]

jobs:

Expand Down
67 changes: 53 additions & 14 deletions contracts/StablecoinLens.vy
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,67 @@

from interfaces import IPegKeeper
from interfaces import IController
from interfaces import IControllerFactory
from interfaces import IMonetaryPolicy

MAX_CONTROLLERS: constant(uint256) = 50000 # TODO check actual number
MAX_PEG_KEEPERS: constant(uint256) = 100 # TODO check actual number
# bound from factory
MAX_CONTROLLERS: constant(uint256) = 50000
# bound from monetary policy
MAX_PEG_KEEPERS: constant(uint256) = 1001
# could have been any other controller
WETH_CONTROLLER_IDX: constant(uint256) = 3

controllers: DynArray[address, MAX_CONTROLLERS]
peg_keepers: DynArray[address, MAX_PEG_KEEPERS]
# the crvusd controller factory
factory: immutable(IControllerFactory)


@deploy
def __init__(_factory: IControllerFactory):
factory = _factory


@view
@external
def circulating_supply() -> uint256:
total_supply: uint256 = 0
"""
@notice Compute the circulating supply for crvUSD, `totalSupply` is
incorrect since it takes into account all minted crvUSD (i.e. flashloans)
@dev This function sacrifices some gas to fetch peg keepers from a
unique source of truth to avoid having to manually maintain multiple
lists across several contracts.
For this reason we read the list of peg keepers contained in
the monetary policy returned by a controller in the factory.
factory -> weth controller -> monetary policy -> peg keepers
"""

circulating_supply: uint256 = 0

# Fetch the weth controller (index 3) under the assumption that
# weth will always be a valid collateral for crvUSD, therefore its
# monetary policy should always be up to date.
controller: IController = staticcall factory.controllers(3)
AlbertoCentonze marked this conversation as resolved.
Show resolved Hide resolved

# We obtain the address of the current monetary policy used by the
# weth controller because it contains a list of all the peg keepers.
monetary_policy: IMonetaryPolicy = staticcall controller.monetary_policy()

# TODO how to optimize this
for pk: address in self.peg_keepers:
if pk != empty(address):
total_supply += staticcall IPegKeeper(pk).debt()
# Iterate over the peg keepers (since it's a fixed size array we
# wait for a zero address to stop iterating).
for i: uint256 in range(MAX_PEG_KEEPERS):
pk: IPegKeeper = staticcall monetary_policy.peg_keepers(i)

if pk.address == empty(address):
# end of array
break

circulating_supply += staticcall pk.debt()

n_controllers: uint256 = staticcall factory.n_collaterals()

# TODO get correct value for this
n_controllers: uint256 = 0
for i: uint256 in range(n_controllers, bound=MAX_CONTROLLERS):
controller: address = self.controllers[i]
total_supply += staticcall IController(controller).total_debt()
controller = staticcall factory.controllers(i)

# add crvUSD minted by controller
circulating_supply += staticcall controller.total_debt()

return total_supply
return circulating_supply
11 changes: 11 additions & 0 deletions contracts/interfaces/IController.vyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# pragma version ~=0.4.0

import IMonetaryPolicy


@view
@external
def total_debt() -> uint256:
...


@view
@external
def monetary_policy() -> IMonetaryPolicy:
...
15 changes: 15 additions & 0 deletions contracts/interfaces/IControllerFactory.vyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# pragma version ~=0.4.0

import IController


@external
@view
def controllers(i: uint256) -> IController:
...


@external
@view
def n_collaterals() -> uint256:
...
9 changes: 9 additions & 0 deletions contracts/interfaces/IMonetaryPolicy.vyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# pragma version ~=0.4.0

import IPegKeeper


@view
@external
def peg_keepers(i: uint256) -> IPegKeeper:
...
2 changes: 2 additions & 0 deletions contracts/interfaces/IPegKeeper.vyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# pragma version ~=0.4.0

@view
@external
def debt() -> uint256:
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IStablecoinLens.vyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Functions
# pragma version ~=0.4.0

@view
@external
Expand Down
34 changes: 17 additions & 17 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ version = "0.1.0"
description = ""
authors = ["Curve.fi"]
readme = "README.md"
package-mode = false

[tool.black]
line-length = 100
Expand All @@ -14,11 +13,11 @@ target_version = ['py312']
python = "^3.12.6"
poetry = "^1.8.3"
vyper = "0.4.0"
titanoboa = { git = "https://github.com/vyperlang/titanoboa.git", branch = "master" }
titanoboa = { git = "https://github.com/vyperlang/titanoboa.git", rev = "4768207288ec8fb23a4817ae193bd707ab9d1e8f" }
snekmate = "0.1.0"

[tool.poetry.group.dev.dependencies]
mamushi = "0.0.4a1"
mamushi = "0.0.4a3"
pre-commit = "^3.8.0"
black = "24.8.0"
pytest = "^8.2.2"
Expand Down
26 changes: 26 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os

import boa
import pytest

boa.set_etherscan(api_key=os.getenv("ETHERSCAN_API_KEY"))


@pytest.fixture(scope="module")
def rpc_url():
return os.getenv("ETH_RPC_URL") or "https://rpc.ankr.com/eth"


@pytest.fixture(scope="module", autouse=True)
def forked_env(rpc_url):
boa.fork(rpc_url, block_identifier=18801970)


@pytest.fixture(scope="module")
def controller_factory():
return boa.from_etherscan("0xC9332fdCB1C491Dcc683bAe86Fe3cb70360738BC", "controller_factory")


@pytest.fixture(scope="module")
def lens(controller_factory):
return boa.load("contracts/StablecoinLens.vy", controller_factory)
5 changes: 5 additions & 0 deletions tests/integration/test_fork_circulating_supply.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def test_fork_circulating_supply(lens):
# TODO mint more crvusd and make sure this increases accordingly
# TODO increase flashloan debt ceiling and make
# sure supply doesn't increase
print("{:2e}".format(lens.circulating_supply()))
14 changes: 14 additions & 0 deletions tests/mocks/MockController.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# pragma version ~=0.4.0

from contracts.interfaces import IController
from contracts.interfaces import IMonetaryPolicy

implements: IController

_monetary_policy: address
total_debt: public(uint256)

@external
@view
def monetary_policy() -> IMonetaryPolicy:
return IMonetaryPolicy(self._monetary_policy)
18 changes: 18 additions & 0 deletions tests/mocks/MockControllerFactory.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# pragma version ~=0.4.0

from contracts.interfaces import IControllerFactory
from contracts.interfaces import IController

implements: IControllerFactory

_controllers: DynArray[IController, 10000]

@external
@view
def controllers(i: uint256) -> IController:
return self._controllers[i]

@external
@view
def n_collaterals() -> uint256:
return len(self._controllers)
13 changes: 13 additions & 0 deletions tests/mocks/MockMonetaryPolicy.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# pragma version ~=0.4.0

from contracts.interfaces import IMonetaryPolicy
from contracts.interfaces import IPegKeeper

implements: IMonetaryPolicy

peg_keeper: IPegKeeper

@external
@view
def peg_keepers(i: uint256) -> IPegKeeper:
return self.peg_keeper
4 changes: 1 addition & 3 deletions tests/unitary/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ def role_manager():
def vault(vault_factory, crvusd, role_manager):
vault_deployer = boa.load_partial("contracts/yearn/Vault.vy")

address = vault_factory.deploy_new_vault(
crvusd, "Staked crvUSD", "st-crvUSD", role_manager, 0
)
address = vault_factory.deploy_new_vault(crvusd, "Staked crvUSD", "st-crvUSD", role_manager, 0)

return vault_deployer.at(address)

Expand Down
16 changes: 0 additions & 16 deletions tests/unitary/rewards_handler/test_process_rewards.py

This file was deleted.

Loading
Loading