@venusprotocol/liquidity-hub 2.1.0-dev.4 → 2.1.0-dev.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +111 -30
  2. package/contracts/Hub/Hub.sol +71 -63
  3. package/contracts/adapters/AdapterCentrifuge.sol +5 -0
  4. package/contracts/adapters/AdapterCoreV1.sol +6 -0
  5. package/contracts/adapters/AdapterFRV.sol +27 -7
  6. package/contracts/adapters/AdapterFlux.sol +6 -0
  7. package/contracts/adapters/AdapterSpokeV1.sol +428 -0
  8. package/contracts/interfaces/IHub.sol +6 -0
  9. package/contracts/interfaces/IHubLens.sol +71 -0
  10. package/contracts/interfaces/IResourceAdapter.sol +23 -7
  11. package/contracts/interfaces/IYieldGroupBase.sol +6 -1
  12. package/contracts/interfaces/external/ISpokeComptroller.sol +69 -0
  13. package/contracts/interfaces/external/IVTokenIsolated.sol +153 -0
  14. package/contracts/lens/HubCapacityMath.sol +610 -0
  15. package/contracts/lens/HubLens.sol +311 -0
  16. package/deployments/bscmainnet/AdapterCentrifuge.json +21 -2
  17. package/deployments/bscmainnet/AdapterCoreV1.json +21 -2
  18. package/deployments/bscmainnet/AdapterFRV.json +21 -2
  19. package/deployments/bscmainnet/AdapterFlux.json +21 -2
  20. package/deployments/bscmainnet/AdapterSpokeV1.json +339 -0
  21. package/deployments/bscmainnet/HubImpl.json +16 -3
  22. package/deployments/bscmainnet/HubLens.json +89 -0
  23. package/deployments/bscmainnet/SpokeBeacon.json +110 -0
  24. package/deployments/bscmainnet/SpokeSource_U.json +1083 -0
  25. package/deployments/bscmainnet/SpokeSource_USDC.json +1083 -0
  26. package/deployments/bscmainnet/SpokeSource_USDT.json +1083 -0
  27. package/deployments/bscmainnet/YieldGroupSpokeImpl.json +1083 -0
  28. package/deployments/bscmainnet_addresses.json +13 -6
  29. package/deployments/bsctestnet/AdapterCentrifuge.json +21 -2
  30. package/deployments/bsctestnet/AdapterCoreV1.json +21 -2
  31. package/deployments/bsctestnet/AdapterFRV.json +21 -2
  32. package/deployments/bsctestnet/AdapterFlux.json +21 -2
  33. package/deployments/bsctestnet/AdapterSpokeV1.json +339 -0
  34. package/deployments/bsctestnet/HubImpl.json +16 -3
  35. package/deployments/bsctestnet/HubLens.json +89 -0
  36. package/deployments/bsctestnet/SpokeBeacon.json +110 -0
  37. package/deployments/bsctestnet/SpokeSource_USDT.json +1083 -0
  38. package/deployments/bsctestnet/YieldGroupSpokeImpl.json +1083 -0
  39. package/deployments/bsctestnet_addresses.json +11 -6
  40. package/package.json +1 -1
@@ -0,0 +1,69 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.25;
3
+
4
+ /**
5
+ * @title ISpokeComptroller
6
+ * @author Venus
7
+ * @notice Minimal subset of the `SpokeComptroller` (isolated-pools) interface needed by the Spoke
8
+ * Yield Group adapter: the supply allowlist that gates who may fund a hub-funded market,
9
+ * plus the cap and pause accessors the deposit-capacity math reads.
10
+ * @dev Deliberately NOT the shared isolated-pools `Comptroller`. {isSupplyAllowlistEnabled} and
11
+ * {isAllowedSupplier} exist only on the spoke fork, so a call to either reverts against any
12
+ * other Comptroller — which is what makes `AdapterSpokeV1.validateRegistration` reject a
13
+ * non-spoke market without needing an explicit type probe. {isMarketListed} carries no such
14
+ * signal, since the shared `Comptroller` declares it too; it is read to tie a market to the
15
+ * Comptroller it names.
16
+ *
17
+ * `actionPaused` takes a `uint8` rather than importing the isolated-pools `Action` enum, whose
18
+ * ABI encoding is identical. The deployed enum is
19
+ * `{ MINT, REDEEM, BORROW, REPAY, SEIZE, LIQUIDATE, TRANSFER, ENTER_MARKET, EXIT_MARKET }`, so
20
+ * `MINT = 0` and `REDEEM = 1` — the same ordering as the Core Comptroller's.
21
+ */
22
+ interface ISpokeComptroller {
23
+ /**
24
+ * @notice Per-market supply cap, in underlying units.
25
+ * @dev The uncapped sentinel is one-sided. Here `type(uint256).max` skips the check entirely,
26
+ * while `0` is a real cap of zero that rejects every mint (`preMintHook` compares
27
+ * `nextTotalSupply > supplyCap`). The Core Comptroller has no uncapped sentinel; there `0`
28
+ * is the value that disables minting.
29
+ * @param vToken Market to query.
30
+ * @return cap Supply cap in underlying units; `type(uint256).max` disables the check.
31
+ */
32
+ function supplyCaps(address vToken) external view returns (uint256 cap);
33
+
34
+ /**
35
+ * @notice Whether a specific action is paused on a given market.
36
+ * @param market vToken market to query.
37
+ * @param action Action enum value: `0 = MINT`, `1 = REDEEM`, etc.
38
+ * @return paused True iff the action is paused.
39
+ */
40
+ function actionPaused(address market, uint8 action) external view returns (bool paused);
41
+
42
+ /**
43
+ * @notice Whether the Comptroller lists `vToken` as one of its markets.
44
+ * @dev `preMintHook` reverts `MarketNotListed` for anything else, and `unlistMarket` can flip a
45
+ * live market back to unlisted once every action is paused and its caps are zero.
46
+ * @param vToken Market to query.
47
+ * @return listed True iff `vToken` is a listed market of this Comptroller.
48
+ */
49
+ function isMarketListed(address vToken) external view returns (bool listed);
50
+
51
+ /**
52
+ * @notice Whether `vToken` accepts supply only from the accounts on its supply allowlist.
53
+ * @dev Disabled by default, so a market can be listed before the allowlist is armed.
54
+ * @param vToken Market to query.
55
+ * @return enabled True iff the allowlist gates minting on this market.
56
+ */
57
+ function isSupplyAllowlistEnabled(address vToken) external view returns (bool enabled);
58
+
59
+ /**
60
+ * @notice Whether `supplier` may mint `vToken` while its supply allowlist is enabled.
61
+ * @dev `preMintHook` checks the account CREDITED with the vTokens, not the account paying. The
62
+ * adapter is delegatecalled, so the credited account is the YieldGroup, and the YieldGroup
63
+ * address is what has to be allowlisted.
64
+ * @param vToken Market whose allowlist to read.
65
+ * @param supplier Account to test.
66
+ * @return allowed True iff `supplier` is on `vToken`'s supply allowlist.
67
+ */
68
+ function isAllowedSupplier(address vToken, address supplier) external view returns (bool allowed);
69
+ }
@@ -0,0 +1,153 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.25;
3
+
4
+ /**
5
+ * @title IVTokenIsolated
6
+ * @author Venus
7
+ * @notice Minimal subset of the Venus isolated-pools `VToken` interface needed by the Spoke Yield
8
+ * Group adapter. Kept narrow on purpose, and kept SEPARATE from the Core `IVToken`: the two
9
+ * share most selectors but differ in semantics on every line that matters to an adapter, so
10
+ * one interface serving both would attach the wrong contract to the wrong reader.
11
+ * @dev The four semantic differences from Core's `VBep20`, each verified against
12
+ * `isolated-pools/contracts/VToken.sol`:
13
+ *
14
+ * 1. **`badDebt` is inside the exchange rate.** `_exchangeRateStored` computes
15
+ * `(totalCash + totalBorrows + badDebt - totalReserves) / totalSupply`. `healAccount` moves
16
+ * an unrecoverable shortfall out of `totalBorrows` and into `badDebt`, leaving that
17
+ * numerator unchanged — so the rate does NOT fall when a loss is recorded and no supplier is
18
+ * marked down. The written-off amount is missing cash instead, until it is recovered
19
+ * through `badDebtRecovered`.
20
+ * 2. **Redeem is bounded by cash NET of reserves.** `_redeemFresh` reverts
21
+ * `RedeemTransferOutNotPossible` when `getCash() - totalReserves < redeemAmount`, not when
22
+ * `getCash() < redeemAmount`. Reserves are not payable liquidity.
23
+ * 3. **Both redeems OVER-deliver.** `redeem(t)` transfers `truncate(exchangeRate x t)`, and
24
+ * `redeemUnderlying(a)` burns `ceil(a / exchangeRate)` tokens and transfers what those are
25
+ * worth — `>= a` by up to one vToken unit of underlying. Callers must transfer the exact
26
+ * requested amount onward and retain the surplus; they must NOT assume an exact delivery.
27
+ * 4. **No per-redeem pool fee.** Isolated pools take their cut through `reserveFactorMantissa`
28
+ * (already netted out of the exchange rate) and route it to the ProtocolShareReserve. There
29
+ * is no `treasuryPercent` equivalent, so there is nothing to gross up on redeem.
30
+ *
31
+ * Shared with Core: `mint` pulls `mintAmount` from `msg.sender` and credits vTokens to
32
+ * `msg.sender`; both mutating calls return a Compound-style error code that isolated pools
33
+ * always sets to `NO_ERROR` (failures revert instead); `mintBehalf` /
34
+ * `redeemUnderlyingBehalf` exist but are on no path here, because the adapter is delegatecalled
35
+ * and so is already `msg.sender` from the vToken's perspective.
36
+ */
37
+ interface IVTokenIsolated {
38
+ // -------------------------------- Mutating -------------------------------
39
+
40
+ /**
41
+ * @notice Supply `mintAmount` of underlying and receive vTokens.
42
+ * @dev Accrues interest first, so the supply-cap check in `preMintHook` runs against a
43
+ * POST-accrual exchange rate — one notch tighter than the rate a `maxDeposit` view reads.
44
+ * @param mintAmount Underlying units to deposit.
45
+ * @return errorCode Compound-style error code; always `0` (failures revert).
46
+ */
47
+ function mint(uint256 mintAmount) external returns (uint256 errorCode);
48
+
49
+ /**
50
+ * @notice Burn exactly `redeemTokens` vTokens and receive `truncate(exchangeRate x redeemTokens)`
51
+ * of underlying.
52
+ * @dev The token-denominated redeem, and the one an adapter should reach for: the burn is the
53
+ * caller's own number, so it can be held inside a known balance by construction.
54
+ * {redeemUnderlying} instead DERIVES the burn from the request and rounds it up, and
55
+ * `_redeemFresh` subtracts that derived count from the caller's balance in checked
56
+ * arithmetic — a request one unit above `balanceOfUnderlying` panics rather than failing
57
+ * gracefully.
58
+ *
59
+ * Reverts `"redeemAmount is zero"` when the payout truncates away, which cannot happen for a
60
+ * count sized to cover a non-zero amount of underlying.
61
+ * @param redeemTokens vToken units to burn.
62
+ * @return errorCode Compound-style error code; always `0` (failures revert).
63
+ */
64
+ function redeem(uint256 redeemTokens) external returns (uint256 errorCode);
65
+
66
+ /**
67
+ * @notice Burn vTokens and receive AT LEAST `redeemAmount` of underlying.
68
+ * @dev Over-delivers by up to one vToken unit of underlying (see note 3 in the contract NatSpec).
69
+ * Reverts `"redeemAmount is zero"` when the payout truncates away. Declared for completeness
70
+ * of the market's redeem surface; see {redeem} for why the burn it derives makes it the
71
+ * wrong primitive for a caller that has to stay inside a known balance.
72
+ * @param redeemAmount Underlying units to withdraw.
73
+ * @return errorCode Compound-style error code; always `0` (failures revert).
74
+ */
75
+ function redeemUnderlying(uint256 redeemAmount) external returns (uint256 errorCode);
76
+
77
+ /**
78
+ * @notice Settle accrued interest into the stored exchange rate up to the current block or
79
+ * timestamp.
80
+ * @dev Also sweeps accrued reserves to the ProtocolShareReserve once
81
+ * `reduceReservesBlockDelta` has elapsed. That lowers `getCash()` and `totalReserves()` by
82
+ * the same amount, so any quantity derived from `getCash() - totalReserves()` is invariant
83
+ * across the sweep.
84
+ * @return errorCode Compound-style error code; always `0` (failures revert).
85
+ */
86
+ function accrueInterest() external returns (uint256 errorCode);
87
+
88
+ // ---------------------------------- Views --------------------------------
89
+
90
+ /**
91
+ * @notice vToken balance of `account`, in vToken units (NOT underlying).
92
+ * @param account Holder to query.
93
+ * @return balance vToken units held by `account`.
94
+ */
95
+ function balanceOf(address account) external view returns (uint256 balance);
96
+
97
+ /// @notice Total vToken supply, in vToken units.
98
+ /// @return supply Total outstanding vTokens.
99
+ function totalSupply() external view returns (uint256 supply);
100
+
101
+ /**
102
+ * @notice Last-settled underlying-per-vToken exchange rate, scaled by `1e18`.
103
+ * @dev INCLUDES `badDebt` in its numerator. It is the rate the market mints, redeems and prices
104
+ * caps at, and the rate `AdapterSpokeV1` values a position at.
105
+ * @return rate Exchange rate scaled by `1e18`.
106
+ */
107
+ function exchangeRateStored() external view returns (uint256 rate);
108
+
109
+ /**
110
+ * @notice Underlying cash the market considers itself to hold.
111
+ * @dev Reserves sit inside this figure but are not redeemable; subtract `totalReserves()` before
112
+ * treating it as payable liquidity.
113
+ *
114
+ * Deliberately not pinned to a definition. Implementations differ on whether this is the
115
+ * market's raw underlying balance or an internally tracked figure that ignores unsolicited
116
+ * transfers, and the two disagree by exactly any donation the market has received. Reading
117
+ * it rather than reconstructing it keeps valuation and liquidity consistent under either:
118
+ * `_redeemFresh` gates on this same quantity, so whatever it counts is also what it will pay
119
+ * out, and whatever it ignores is value no caller could have redeemed anyway.
120
+ * @return cash Underlying units the market counts as cash.
121
+ */
122
+ function getCash() external view returns (uint256 cash);
123
+
124
+ /// @notice Accrued protocol reserves, in underlying units. Not payable to suppliers.
125
+ /// @return reserves Underlying units reserved for the protocol.
126
+ function totalReserves() external view returns (uint256 reserves);
127
+
128
+ /// @notice The ERC-20 underlying this market is backed by.
129
+ /// @return token Address of the underlying ERC-20.
130
+ function underlying() external view returns (address token);
131
+
132
+ /// @notice Comptroller of the pool this market belongs to.
133
+ /// @return comptrollerAddress Address of the pool's Comptroller.
134
+ function comptroller() external view returns (address comptrollerAddress);
135
+
136
+ /**
137
+ * @notice Current supply interest rate per slot, scaled by `1e18`.
138
+ * @dev A "slot" is a block on a block-based market and a second on a time-based one; annualise
139
+ * with {blocksOrSecondsPerYear}, never with a chain-level constant.
140
+ * @return rate Per-slot supply rate, scaled by `1e18`.
141
+ */
142
+ function supplyRatePerBlock() external view returns (uint256 rate);
143
+
144
+ /**
145
+ * @notice Slots per year for this market: blocks per year on a block-based market, seconds per
146
+ * year on a time-based one.
147
+ * @dev An immutable set at construction (`TimeManagerV8`), so it is the only annualiser that
148
+ * matches this market's rate unit. A YieldGroup-level `blocksPerYear` cannot, because one
149
+ * YieldGroup can hold markets of both kinds.
150
+ * @return periods Slots per year.
151
+ */
152
+ function blocksOrSecondsPerYear() external view returns (uint256 periods);
153
+ }