Skip to content

Commit

Permalink
test: integration e2e (#26)
Browse files Browse the repository at this point in the history
* chore: add leveldb for caching

* test: update address book

* test: add interface for tests

* test: wip test

* test: running integration tests, wip

* ci: adding boa cache

* ci: fixing boa cache

* revert: rm boa cache from ci

* chore: fix typo

* refactor: improve readability

* test: more integration tests

* chore: fix dev dependencies

* chore: create fixture for shared logic

* test: remove unnecessary approval

* style: udnerscore numbers

* test: reassign prev_d

* test: generalize fee_splitter_cap test

* test: removed redundant test

---------

Co-authored-by: heswithme <[email protected]>
  • Loading branch information
AlbertoCentonze and heswithme authored Oct 14, 2024
1 parent de3bc42 commit 17fff35
Show file tree
Hide file tree
Showing 10 changed files with 691 additions and 1,575 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ jobs:
- name: Set up Python 3.12.6
run: uv python install 3.12.6

# Ensure Titanoboa Cache Directory Exists
- name: Create Cache Directory
run: mkdir -p /home/runner/.cache/titanoboa

# Install dependencies with all extras (including dev)
- name: Install Requirements
run: uv sync --extra=dev
Expand Down
7 changes: 3 additions & 4 deletions contracts/RewardsHandler.vy
Original file line number Diff line number Diff line change
Expand Up @@ -245,15 +245,14 @@ def weight() -> uint256:
"""
@notice this function is part of the dynamic weight interface expected by the
FeeSplitter to know what percentage of funds should be sent for rewards
distribution to crvUSD stakerks.
distribution to crvUSD stakers.
@dev `minimum_weight` acts as a lower bound for the percentage of rewards that
should be distributed to stakers. This is useful to bootstrapping TVL by asking
for more at the beginning and can also be increased in the future if someone
tries to manipulate the time-weighted average of the tvl ratio.
"""
return max(
twa._compute() * self.scaling_factor // MAX_BPS, self.minimum_weight
)
raw_weight: uint256 = twa._compute() * self.scaling_factor // MAX_BPS
return max(raw_weight, self.minimum_weight)


################################################################
Expand Down
14 changes: 5 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,21 @@ requires-python = ">=3.10"
dependencies = [
"vyper>=0.4.0",
"snekmate==0.1.0",
"titanoboa", # Keep this as a placeholder in the dependencies array
"titanoboa", # Keep this as a placeholder in the dependencies array
]

[tool.uv.sources]
titanoboa = { git = "https://github.com/vyperlang/titanoboa.git", rev = "86df8936654db20686410488738d7abaf165a4c9" }

[project.optional-dependencies]
dev = [
[tool.uv]
dev-dependencies = [
"pytest-xdist>=3.6.1",
"mamushi==0.0.4a3",
"pre-commit==3.8.0",
"ruff==0.6.9",
"pytest==8.2.2",
"pytest-xdist==3.6.1",
]
scripts = [
"tqdm==4.66.5",
"curve-dao==1.0.0",
"jupyter==1.0.0",
"python-dotenv==1.0.1",
"plyvel-ci==1.5.1",
]

[tool.ruff]
Expand Down
7 changes: 6 additions & 1 deletion tests/integration/address_book.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# yearn vaults 3.0.3 factory
factory = "0x5577EdcB8A856582297CdBbB07055E6a6E38eb5f"
yearn_vault_factory = "0x5577EdcB8A856582297CdBbB07055E6a6E38eb5f"
crvusd = "0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E"
crvusd_controller_factory = "0xC9332fdCB1C491Dcc683bAe86Fe3cb70360738BC"
crvusd_fee_collector = "0xa2Bcd1a4Efbd04B63cd03f5aFf2561106ebCCE00"
fee_splitter = "0x22556558419eed2d0a1af2e7fd60e63f3199aca3"
dao_agent = "0x40907540d8a6C65c637785e8f8B742ae6b0b9968"
vault_original = "0xcA78AF7443f3F8FA0148b746Cb18FF67383CDF3f"
91 changes: 88 additions & 3 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,32 @@
boa.set_etherscan(api_key=os.getenv("ETHERSCAN_API_KEY"))


@pytest.fixture(autouse=True, scope="module")
def better_traces(forked_env):
# contains contracts that are not necessarily called
# but appear in the traces
boa.from_etherscan(ab.vault_original, "vault_original")


@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):
block_to_fork = 20826753
block_to_fork = 20928372
with boa.swap_env(boa.Env()):
boa.fork(url=rpc_url, block_identifier=block_to_fork)
# use this to disable caching
# boa.fork(url=rpc_url, block_identifier=block_to_fork, cache_file=None)
boa.env.enable_fast_mode()
yield


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


@pytest.fixture(scope="module")
Expand All @@ -33,9 +42,85 @@ def lens(controller_factory):

@pytest.fixture(scope="module")
def vault_factory():
return boa.from_etherscan("0x5577EdcB8A856582297CdBbB07055E6a6E38eb5f", "vault_factory")
return boa.from_etherscan(ab.yearn_vault_factory, "vault_factory")


@pytest.fixture(scope="module")
def fee_splitter(rewards_handler):
_fee_splitter_abi = boa.load_vyi("tests/integration/interfaces/IFeeSplitter.vyi")

_fee_splitter = _fee_splitter_abi.at(ab.fee_splitter)

receivers = [
# we add the rewards_handler as a receiver
(rewards_handler.address, 1_000),
# dao receives 10% less than what it currently does
# and it the excess receiver
(ab.crvusd_fee_collector, 9_000),
]

assert _fee_splitter.excess_receiver() == ab.crvusd_fee_collector

_fee_splitter.set_receivers(receivers, sender=ab.dao_agent)

return _fee_splitter


@pytest.fixture(scope="module")
def crvusd():
return boa.from_etherscan(ab.crvusd, "crvusd")


@pytest.fixture(scope="module")
def vault(vault_factory):
_vault_abi = boa.load_partial("contracts/yearn/VaultV3.vy")

_vault_addy = vault_factory.deploy_new_vault(
ab.crvusd,
"Savings crvUSD",
"scrvUSD",
ab.dao_agent,
86400 * 7, # 1 week
)

_vault = _vault_abi.at(_vault_addy)

# give the dao total control over the vault
_vault.set_role(ab.dao_agent, int("11111111111111", 2), sender=ab.dao_agent)
_vault.set_deposit_limit(2**256 - 1, sender=ab.dao_agent)
# monkeypatch the contract_name
_vault.contract_name = "scrvUSD"

return _vault


@pytest.fixture(scope="module")
def minimum_weight():
return 500


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

# TODO how to enforce this in prod?
time = vault.profitMaxUnlockTime()
rh.eval(f"self.distribution_time = {time}")

return rh


@pytest.fixture(scope="module")
def active_controllers(fee_splitter):
# useful to call dispatch_fees
# we skip the first one as the market is deprecated
return [fee_splitter.controllers(i) for i in range(1, 6)]
80 changes: 80 additions & 0 deletions tests/integration/interfaces/IFeeSplitter.vyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Events

event SetReceivers:
pass
event LivenessProtectionTriggered:
pass
event FeeDispatched:
receiver: address
weight: uint256
event OwnershipTransferred:
previous_owner: address
new_owner: address

# Structs

struct Receiver:
addr: address
weight: uint256

# Functions

@external
def transfer_ownership(new_owner: address):
...

@external
def renounce_ownership():
...

@view
@external
def owner() -> address:
...

@external
def update_controllers():
...

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

@view
@external
def allowed_controllers(arg0: address) -> bool:
...

@view
@external
def controllers(arg0: uint256) -> address:
...

@external
def dispatch_fees(controllers: DynArray[address, 50]):
...

@external
def set_receivers(receivers: DynArray[Receiver, 100]):
...

@view
@external
def excess_receiver() -> address:
...

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

@view
@external
def version() -> String[8]:
...

@view
@external
def receivers(arg0: uint256) -> Receiver:
...
Loading

0 comments on commit 17fff35

Please sign in to comment.