Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions tests/test_3jane.py
Original file line number Diff line number Diff line change
Expand Up @@ -796,8 +796,6 @@ def test_accountable_recovery_rearms_alert(monkeypatch: pytest.MonkeyPatch) -> N

def test_accountable_alerts_use_the_protocol_dispatch_key(monkeypatch: pytest.MonkeyPatch) -> None:
"""Accountable HIGH/CRITICAL share the 3jane key so they can zero caps."""
from utils.dispatch import DISPATCHABLE_PROTOCOLS

module = load_3jane_module()
alerts: list = []
stub_cache(monkeypatch, module)
Expand All @@ -807,8 +805,6 @@ def test_accountable_alerts_use_the_protocol_dispatch_key(monkeypatch: pytest.Mo

assert alerts[-1].severity == module.AlertSeverity.CRITICAL
assert alerts[-1].protocol == module.PROTOCOL
assert module.ACCOUNTABLE_FEED.protocol == module.PROTOCOL
assert alerts[-1].protocol in DISPATCHABLE_PROTOCOLS


def test_accountable_unavailable_run_does_not_rearm_critical(monkeypatch: pytest.MonkeyPatch) -> None:
Expand Down
30 changes: 10 additions & 20 deletions tests/test_known_addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,18 @@ def test_chain_specific_takes_precedence(self) -> None:
# Different chain → no chain-specific entry, falls through to "".
self.assertEqual(known_addresses.lookup(8453, addr), "")

def test_watched_yearn_safe_label_from_safe_config(self) -> None:
self.assertEqual(
known_addresses.lookup(1, "0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52"),
"yChad (Yearn multisig/daddy)",
)

def test_yearn_proposer_bot_label_is_chain_agnostic(self) -> None:
self.assertEqual(
known_addresses.lookup(8453, "0x5e69fb460c9950f5ae90daffc4c4f32ecafacaa5"),
"Yearn yChad proposer bot",
)

def test_safe_utility_label_is_chain_agnostic(self) -> None:
self.assertEqual(
known_addresses.lookup(42161, "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D"),
"Safe MultiSendCallOnly",
)
def test_watched_safe_labels_from_safe_config(self) -> None:
safes = [
["YEARN_MS", "mainnet", "0x" + "AA" * 20, "yChad"],
["LIDO", "base-main", "0x" + "BB" * 20],
["OTHER", "unknown-network", "0x" + "CC" * 20],
]
with patch("protocols.safe.addresses.ALL_SAFE_ADDRESSES", safes):
labels = known_addresses._watched_safe_labels()

def test_operator_supplied_timelock_label(self) -> None:
self.assertEqual(
known_addresses.lookup(1, "0x88Ba032be87d5EF1fbE87336B7090767F367BF73"),
"Yearn TimelockController",
labels,
{(1, "0x" + "aa" * 20): "yChad", (8453, "0x" + "bb" * 20): "LIDO Safe"},
)


Expand Down
8 changes: 0 additions & 8 deletions tests/test_lender_borrower.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
RAY_TO_WAD,
STRATEGIES,
WAD,
AaveMarketConfig,
Evaluation,
RateSample,
StrategySnapshot,
Expand Down Expand Up @@ -75,13 +74,6 @@ def test_aave_borrow_apr_rejects_zero_rate() -> None:
aave_borrow_apr_wad((0, 0, 0, 0, 0))


def test_accumulator_config_targets_underlying_borrower() -> None:
config = STRATEGIES[1]
assert config.address == "0x13f6Cb609959a43c3bE29407766A683b42e26D28"
assert config.monitored_address == "0x41cfE42D221a591C6308Dcea419015Ba8570B380"
assert isinstance(config.market, AaveMarketConfig)


def test_accrue_market_adds_expected_interest() -> None:
market = (2_000_000, 2_000_000, 1_000_000, 1_000_000, 100, 0)
accrued = accrue_market(market, 10**14, 200)
Expand Down
1 change: 0 additions & 1 deletion tests/test_morpho_governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ def fake_get_data_for_chain(chain: Chain) -> None:
governance.main()

self.assertEqual(checked, list(governance.VAULTS_V1_BY_CHAIN))
self.assertIn(Chain.HYPEREVM, checked)


if __name__ == "__main__":
Expand Down
59 changes: 10 additions & 49 deletions tests/test_morpho_markets.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
MorphoV2MonitoringError,
get_vault_url,
)
from protocols.morpho.config import VAULTS_V2_BY_CHAIN, VaultConfig, get_collateral_vaults_by_asset
from protocols.morpho.config import VaultConfig
from protocols.morpho.markets import (
YV_COLLATERAL_AT_RISK_POINTS,
YV_COLLATERAL_STABLE_PRICE_SHOCK,
Expand Down Expand Up @@ -60,27 +60,6 @@ def _sample_metrics(market_id: str, asset_address: str = "0x" + "22" * 20) -> Ma


class TestMorphoV2Configuration(unittest.TestCase):
def test_katana_collateral_strategy_vaults_are_monitored(self) -> None:
configured = {vault.address.lower() for vault in VAULTS_V2_BY_CHAIN[Chain.KATANA]}
expected = {
"0x4284d4f9f4d61ea57b8f0943547c7c19c5b9b249",
"0xca44cbe1fb03691d43d2d93aa460e2fcb03878fe",
"0xa2d38c8a3d810ebcf4c2075821c5ec8f976bb692",
"0xac596ad9771a8d0d4df108ae0406e6f913aedceb",
"0x5920a6fc553af799542eda628adfcc9ea52e141c",
"0xbeeff2d5d126d4809195eea02b605423917bb6c6",
"0xbeef042bad4472c3f7eb9a73070703788b5362d7",
}

self.assertTrue(expected <= configured)

collateral_vaults = {
vault.address.lower()
for vaults in get_collateral_vaults_by_asset(Chain.KATANA, version=2).values()
for vault in vaults
}
self.assertEqual(collateral_vaults, expected)

def test_discovery_fails_if_api_omits_configured_vaults(self) -> None:
response = MagicMock()
response.json.return_value = {"data": {"vaultV2s": {"items": []}}}
Expand Down Expand Up @@ -488,11 +467,8 @@ def test_v2_low_liquidity_alert_flags_missing_liquidity_adapter(self) -> None:

self.assertIn("No liquidity adapter set", send.call_args.args[0].message)

def test_collateral_v2_vaults_get_low_liquidity_check(self) -> None:
collateral_vault = next(v for v in VAULTS_V2_BY_CHAIN[Chain.KATANA] if v.collateral_asset)
def test_analyze_v2_vault_runs_low_liquidity_check(self) -> None:
vault = self._v2_vault(0, self._market(90_000, 1_000))
vault.address = collateral_vault.address
vault.chain = Chain.KATANA

with (
patch("protocols.morpho.markets_v2.score_market_allocations"),
Expand Down Expand Up @@ -556,23 +532,24 @@ def test_parse_liquidity_market_fails_on_unresolvable_adapter(self) -> None:
_parse_liquidity_market(broken, "Example", Chain.MAINNET)

def test_zero_asset_group_is_retained_for_collateral_risk_check(self) -> None:
vault_address = "0x" + "77" * 20
asset_address = "0x" + "88" * 20
v1_vaults = [
{
"__typename": "Vault",
"address": "0xe107cCdeb8e20E499545C813f98Cc90619b29859",
"address": vault_address,
"name": "Yearn OG WBTC",
"chain": {"id": Chain.KATANA.chain_id},
"asset": {
"address": "0x0913DA6Da4b42f538B445599b46Bb4622342Cf52",
"symbol": "vbWBTC",
},
"asset": {"address": asset_address, "symbol": "vbWBTC"},
"state": {"totalAssetsUsd": 0},
"liquidity": {"usd": 0},
}
]
vault_config = VaultConfig("Yearn OG WBTC", vault_address, 1, asset_address)

groups = get_yv_collateral_liquidity_by_asset(Chain.KATANA, v1_vaults, [])
wbtc_group = groups["0x0913da6da4b42f538b445599b46bb4622342cf52"]
with patch.dict("protocols.morpho.config.VAULTS_V1_BY_CHAIN", {Chain.KATANA: (vault_config,)}):
groups = get_yv_collateral_liquidity_by_asset(Chain.KATANA, v1_vaults, [])
wbtc_group = groups[asset_address]

self.assertEqual(wbtc_group.combined_total_assets, 0)
self.assertEqual(wbtc_group.combined_liquidity, 0)
Expand All @@ -586,28 +563,12 @@ def test_market_risk_level_is_case_insensitive_and_defaults_to_5(self) -> None:
self.assertEqual(get_market_risk_level(market_id, Chain.MAINNET), 5)
self.assertEqual(get_market_risk_level("0x" + "00" * 32, Chain.HYPEREVM), 5)

def test_robinhood_stonk_vault_markets_are_tiered(self) -> None:
spy = "0x50bc39b5722fb5634c436d74c6787f3c125b879e7b73cf9e9ecc01bbb57b8e55"
spy_77 = "0xeb1739b2a69dd22a4285d366a06698a01018b37c24d2994fefe19cc72f313613"
nvda = "0x66306c087add8907752320b309934abcc354d21626de8115c79df49d9c214edc"
nvda_no_quote = "0x95312f02c1dae8407b2c80468a5657a9bd19c45ac277f1a61aa52c11444c5314"
usde = "0xc845da65a020ddca5f132efa8fea79676d8edfdea504226a4c01e7a9e34cddd6"
googl = "0x7408b08b03e05ffd2d21c797489a0e77a28e4bb36acb4e827c69e805d06272b4"

self.assertEqual(get_market_risk_level(spy, Chain.ROBINHOOD), 2)
self.assertEqual(get_market_risk_level(spy_77, Chain.ROBINHOOD), 3)
self.assertEqual(get_market_risk_level(nvda, Chain.ROBINHOOD), 3)
self.assertEqual(get_market_risk_level(nvda_no_quote, Chain.ROBINHOOD), 3)
self.assertEqual(get_market_risk_level(usde, Chain.ROBINHOOD), 2)
self.assertEqual(get_market_risk_level(googl, Chain.ROBINHOOD), 4)


class TestMorphoUrls(unittest.TestCase):
def test_robinhood_uses_morpho_chain_slug(self) -> None:
vault = "0x8c258F25fc6fBe3f01ccC828E2C3840A46a4a9A2"

self.assertEqual(get_vault_url(vault, Chain.ROBINHOOD), f"https://app.morpho.org/robinhood-chain/vault/{vault}")
self.assertEqual(Chain.ROBINHOOD.explorer_url, "https://robinhoodchain.blockscout.com")


if __name__ == "__main__":
Expand Down
49 changes: 23 additions & 26 deletions tests/test_pegged_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@
from utils.pegged_assets import (
BTC_USD_DEFILLAMA_KEY,
PEGGED_ASSETS,
PEGGED_ASSETS_BY_NAME,
PeggedAsset,
PegTarget,
get_asset,
price_deviation,
resolve_peg_prices,
)

# Asset set the registry must cover per the issue acceptance criteria.
REQUIRED_ASSETS = {"WBTC", "cbBTC", "LBTC", "iUSD", "cUSD", "USDe", "USDC", "USDT", "USDS"}

def _asset(peg: PegTarget = PegTarget.USD, depeg_pct: str = "0.02", downside_only: bool = False) -> PeggedAsset:
return PeggedAsset(
name="TEST",
defillama_key="ethereum:0x" + "11" * 20,
protocol="test",
peg=peg,
depeg_pct=Decimal(depeg_pct),
downside_only=downside_only,
)


class TestPriceDeviation(unittest.TestCase):
Expand All @@ -37,34 +45,30 @@ def test_zero_peg_raises(self):

class TestIsDepegged(unittest.TestCase):
def test_within_tolerance_is_not_depegged(self):
usdc = get_asset("USDC") # depeg_pct = 0.02
self.assertFalse(usdc.is_depegged(Decimal("0.99"), Decimal("1")))
self.assertFalse(_asset().is_depegged(Decimal("0.99"), Decimal("1")))

def test_beyond_tolerance_is_depegged(self):
usdc = get_asset("USDC")
self.assertTrue(usdc.is_depegged(Decimal("0.97"), Decimal("1")))
self.assertTrue(_asset().is_depegged(Decimal("0.97"), Decimal("1")))

def test_at_threshold_is_depegged(self):
usdc = get_asset("USDC")
self.assertTrue(usdc.is_depegged(Decimal("1.02"), Decimal("1")))
self.assertTrue(_asset().is_depegged(Decimal("1.02"), Decimal("1")))

def test_btc_asset_uses_peg_price(self):
cbbtc = get_asset("cbBTC") # depeg_pct = 0.02, peg = BTC
self.assertFalse(cbbtc.is_depegged(Decimal("60500"), Decimal("60000")))
self.assertTrue(cbbtc.is_depegged(Decimal("58000"), Decimal("60000")))
btc_asset = _asset(peg=PegTarget.BTC)
self.assertFalse(btc_asset.is_depegged(Decimal("60500"), Decimal("60000")))
self.assertTrue(btc_asset.is_depegged(Decimal("58000"), Decimal("60000")))

def test_downside_only_ignores_upside(self):
lbtc = get_asset("LBTC") # depeg_pct = 0.03, downside_only
# +5% above peg (LBTC legitimately trades above 1 BTC) -> not a depeg.
self.assertFalse(lbtc.is_depegged(Decimal("63000"), Decimal("60000")))
asset = _asset(peg=PegTarget.BTC, depeg_pct="0.03", downside_only=True)
# +5% above peg -> not a depeg.
self.assertFalse(asset.is_depegged(Decimal("63000"), Decimal("60000")))
# -5% below peg -> depeg.
self.assertTrue(lbtc.is_depegged(Decimal("57000"), Decimal("60000")))
self.assertTrue(asset.is_depegged(Decimal("57000"), Decimal("60000")))
# exactly at the downside threshold -> depeg.
self.assertTrue(lbtc.is_depegged(Decimal("58200"), Decimal("60000")))
self.assertTrue(asset.is_depegged(Decimal("58200"), Decimal("60000")))

def test_symmetric_asset_flags_upside(self):
usdc = get_asset("USDC") # depeg_pct = 0.02, symmetric
self.assertTrue(usdc.is_depegged(Decimal("1.05"), Decimal("1")))
self.assertTrue(_asset().is_depegged(Decimal("1.05"), Decimal("1")))


class TestResolvePegPrices(unittest.TestCase):
Expand All @@ -87,9 +91,6 @@ def test_missing_btc_price_raises(self):


class TestRegistry(unittest.TestCase):
def test_covers_required_assets(self):
self.assertTrue(REQUIRED_ASSETS.issubset(set(PEGGED_ASSETS_BY_NAME)))

def test_names_are_unique(self):
names = [a.name for a in PEGGED_ASSETS]
self.assertEqual(len(names), len(set(names)))
Expand All @@ -102,10 +103,6 @@ def test_every_asset_has_positive_depeg_tolerance(self):
for asset in PEGGED_ASSETS:
self.assertGreater(asset.depeg_pct, Decimal("0"), asset.name)

def test_btc_wrappers_are_downside_only(self):
for name in ("WBTC", "cbBTC", "LBTC"):
self.assertTrue(get_asset(name).downside_only, name)


if __name__ == "__main__":
unittest.main()
24 changes: 0 additions & 24 deletions tests/test_risk_anchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,30 +109,6 @@ def test_selectors_are_derived_from_signature_table(self) -> None:
}
self.assertEqual(_ANCHORS, expected)

def test_selectors_match_signatures(self) -> None:
# Recompute the selector for each anchored signature to catch typos.
expected = {
"acceptOwnership()": "0x79ba5097",
"mint(address,uint256)": "0x40c10f19",
"addOwnerWithThreshold(address,uint256)": "0x0d582f13",
"removeOwner(address,address,uint256)": "0xf8dc5dd9",
"swapOwner(address,address,address)": "0xe318b52b",
"changeThreshold(uint256)": "0x694e80c3",
"enableModule(address)": "0x610b5925",
"disableModule(address,address)": "0xe009cfde",
"setGuard(address)": "0xe19a9dd9",
"setFallbackHandler(address)": "0xf08a0323",
}
for sig, selector in expected.items():
self.assertEqual("0x" + function_signature_to_4byte_selector(sig).hex(), selector, sig)
self.assertIn(selector, _ANCHORS, f"{sig} ({selector}) not registered")

def test_safe_module_is_critical(self) -> None:
# enableModule can move funds with no owner signatures — highest band.
anchor = lookup("0x610b5925")
assert anchor is not None
self.assertEqual(anchor.level, "CRITICAL")


if __name__ == "__main__":
unittest.main()
18 changes: 14 additions & 4 deletions tests/test_small_parent_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@
"asset_decimals": 6,
}

EXCLUDED_INITIATOR = "0x" + "e9" * 20


@pytest.fixture(autouse=True)
def _excluded_initiators(monkeypatch: pytest.MonkeyPatch) -> None:
"""Exclude a fake Katana initiator so tests don't depend on the real config."""
monkeypatch.setattr(
monitor, "IGNORED_TRANSACTION_SENDERS", {Chain.KATANA.chain_id: frozenset({EXCLUDED_INITIATOR})}
)


def make_event(
*,
Expand Down Expand Up @@ -127,7 +137,7 @@ def test_process_event_raises_without_alert_sender() -> None:
@pytest.mark.parametrize("flow_type", ["deposit", "withdrawal"])
@pytest.mark.parametrize(
"initiator",
["0xe9312419edc29e7784ac2f5aa2fd2ac0f38ff2af", "0xe9312419edc29E7784AC2F5AA2Fd2ac0f38fF2Af"],
[EXCLUDED_INITIATOR, "0x" + "E9" * 20],
)
def test_process_event_excludes_katana_initiator(flow_type: str, initiator: str) -> None:
"""Excluded initiators do not consume aggregate capacity or emit summaries."""
Expand All @@ -147,7 +157,7 @@ def test_process_event_excludes_katana_initiator(flow_type: str, initiator: str)
def test_process_event_exclusion_is_chain_scoped() -> None:
"""The same address remains monitored outside Katana."""
event = make_event(chain_id=Chain.MAINNET.chain_id)
event["transactionFrom"] = "0xe9312419edc29E7784AC2F5AA2Fd2ac0f38fF2Af"
event["transactionFrom"] = EXCLUDED_INITIATOR
records: list[monitor.SmallFlowRecord] = []

assert monitor.process_event(event, {"0xparent": VAULT}, 10_000, records.append)
Expand All @@ -160,7 +170,7 @@ def test_process_event_does_not_exclude_other_participants(initiator: str | None
event = make_event(flow_type="withdrawal", chain_id=Chain.KATANA.chain_id)
event["transactionFrom"] = initiator
for field in ("sender", "owner", "receiver"):
event[field] = "0xe9312419edc29E7784AC2F5AA2Fd2ac0f38fF2Af"
event[field] = EXCLUDED_INITIATOR
records: list[monitor.SmallFlowRecord] = []

assert monitor.process_event(event, {"0xparent": VAULT}, 10_000, records.append)
Expand All @@ -172,7 +182,7 @@ def test_monitor_flow_type_advances_past_excluded_events(monkeypatch: pytest.Mon
"""Excluded events advance the cursor without preventing later flows from alerting."""
chain_id = Chain.KATANA.chain_id
first = make_event(flow_type=flow_type, chain_id=chain_id)
first["transactionFrom"] = "0xe9312419edc29E7784AC2F5AA2Fd2ac0f38fF2Af"
first["transactionFrom"] = EXCLUDED_INITIATOR
second = make_event(flow_type=flow_type, chain_id=chain_id, block_number=101)
third = {**first, "blockNumber": 102}
pages = iter([[first], [second], [third], []])
Expand Down
Loading
Loading