@venusprotocol/liquidity-hub 2.1.0-dev.7 → 2.1.0-dev.8
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.
- package/README.md +1 -1
- package/contracts/YieldGroup/base/NavGuard.sol +5 -4
- package/contracts/interfaces/IMigrator.sol +8 -6
- package/contracts/interfaces/INavGuard.sol +9 -8
- package/contracts/migrator/Migrator.sol +21 -9
- package/deployments/bscmainnet/Migrator.json +28 -4
- package/deployments/bscmainnet_addresses.json +1 -1
- package/deployments/bsctestnet/Migrator.json +28 -4
- package/deployments/bsctestnet_addresses.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -524,6 +524,6 @@ Ordering is guaranteed by the call-array index (`addHub` before any `addYieldGro
|
|
|
524
524
|
|
|
525
525
|
**Reconciliation:** a Hub is live the moment script 06 runs but discoverable only after its VIP — `deploy/ReconcileHubRegistry.s.sol` (read-only, unnumbered) diffs the committed `deployments/<network>/Hub_*.json` set against `registry.getHubs()` in both directions and reverts on any mismatch (shadow Hubs deployed-but-unregistered, or registered-but-untracked). Run it after every onboarding VIP and periodically from monitoring.
|
|
526
526
|
|
|
527
|
-
`deploy/config/<network>.json` holds only public addresses and parameters — `acm`, `governance`, `fluxLendingResolver`, `wbnb` / `vBNB` (the Migrator's native path; both keys are required
|
|
527
|
+
`deploy/config/<network>.json` holds only public addresses and parameters — `acm`, `governance`, `fluxLendingResolver`, `wbnb` / `vBNB` (the Migrator's native path; both keys are required and must be non-zero, since the constructor rejects a zero address), and per-asset `key` / `asset` / `name` / `symbol` / `decimalsOffset` / `initialMaxWithdrawalSize` / `blocksPerYear` / `feeRecipient`. The committed `bscmainnet.json` ships with `0x0` placeholders for governance/acm/resolver/recipient and must be filled before a real deploy. Secrets (deployer key, RPC URLs, Etherscan key) live only in `.env` (see `.env.example`). Deployed addresses are committed under `deployments/<network>/<name>.json`; `broadcast/` is gitignored and is not the source of truth.
|
|
528
528
|
|
|
529
529
|
**Networks:** RPC aliases are pre-wired for `bsc_mainnet`, `bsc_testnet`, `ethereum`, `sepolia` (one Etherscan V2 key across chains). v1 targets BNB Chain.
|
|
@@ -10,14 +10,15 @@ import { INavGuard } from "../../interfaces/INavGuard.sol";
|
|
|
10
10
|
* value is reported by a counterparty that nothing else validates.
|
|
11
11
|
* @dev The band vouches for a value: the last counterparty figure we accepted, grown at `driftBps`
|
|
12
12
|
* a year, plus whatever we have moved in or out since. {_guardedNav} holds valuation a gap
|
|
13
|
-
* either side of that and never reverts. The gaps are a percentage of
|
|
14
|
-
* only
|
|
13
|
+
* either side of that and never reverts. The gaps are a percentage of `anchor`. Our own moves
|
|
14
|
+
* shift only `centre`, so they do not widen the gaps until the next re-anchor, which adopts the
|
|
15
|
+
* whole position as the new `anchor`. Take everything out and the band closes; a deposit
|
|
15
16
|
* or {setNavGuardSnapshot} reopens it.
|
|
16
17
|
*
|
|
17
18
|
* Two stored numbers carry that, and {_currentBand} is the only place they are combined:
|
|
18
19
|
*
|
|
19
|
-
* - `anchor` is
|
|
20
|
-
* anything we have withdrawn beyond our own deposits.
|
|
20
|
+
* - `anchor` is what both gaps are sized off: the position value adopted at the last
|
|
21
|
+
* re-anchor, less anything we have withdrawn beyond our own deposits since.
|
|
21
22
|
* {_updateNavGuard} moves it once per `interval`.
|
|
22
23
|
* - `centre` is where the band sits: the anchor plus our own deposits since. {_recordInflow}
|
|
23
24
|
* and {_recordOutflow} move it.
|
|
@@ -7,8 +7,10 @@ pragma solidity 0.8.25;
|
|
|
7
7
|
* @notice One-click migration from an existing Venus Core position into a Liquidity Hub. In a
|
|
8
8
|
* single atomic transaction the Migrator pulls a user's Core vToken, redeems it to the
|
|
9
9
|
* underlying, and deposits that into the matching Hub, minting share tokens to `receiver`.
|
|
10
|
-
* @dev A single immutable, permissionless deployment serves every Hub — the target is
|
|
11
|
-
* call via the `hub` argument
|
|
10
|
+
* @dev A single immutable, permissionless deployment serves every registered Hub — the target is
|
|
11
|
+
* chosen per call via the `hub` argument and must be present in the HubRegistry, so a migration
|
|
12
|
+
* can only ever land in a governance-registered Hub. Two paths exist because Venus splits its
|
|
13
|
+
* markets by asset type:
|
|
12
14
|
* ERC-20-backed markets (vUSDT, vUSDC, ...) go through {migrateFromCore}; the native market
|
|
13
15
|
* (vBNB) redeems raw BNB and so has the dedicated {migrateFromCoreBNB} path that wraps to WBNB
|
|
14
16
|
* before depositing into the WBNB Hub.
|
|
@@ -36,6 +38,8 @@ interface IMigrator {
|
|
|
36
38
|
error ZeroAddress();
|
|
37
39
|
/// @notice A required amount argument was zero.
|
|
38
40
|
error ZeroAmount();
|
|
41
|
+
/// @notice The target `hub` is not a governance-registered Hub in the HubRegistry.
|
|
42
|
+
error HubNotRegistered(address hub);
|
|
39
43
|
/// @notice The vToken's underlying does not match the Hub's asset.
|
|
40
44
|
error AssetMismatch(address vTokenUnderlying, address hubAsset);
|
|
41
45
|
/// @notice The Core market returned a non-zero (failure) error code on redeem.
|
|
@@ -44,8 +48,6 @@ interface IMigrator {
|
|
|
44
48
|
error NothingReceived();
|
|
45
49
|
/// @notice The minted shares were below the caller's `minShares` floor.
|
|
46
50
|
error InsufficientShares(uint256 shares, uint256 minShares);
|
|
47
|
-
/// @notice The native (vBNB → WBNB) path is unavailable: this deployment has no WBNB/vBNB wired.
|
|
48
|
-
error BNBMigrationUnsupported();
|
|
49
51
|
/// @notice Native value was received from an address other than the configured vBNB market.
|
|
50
52
|
error OnlyVBNB();
|
|
51
53
|
|
|
@@ -95,8 +97,8 @@ interface IMigrator {
|
|
|
95
97
|
/**
|
|
96
98
|
* @notice Migrate a native vBNB position into the WBNB Hub.
|
|
97
99
|
* @dev Caller must first `approve` this Migrator to transfer `vBNBAmount` of the configured
|
|
98
|
-
* vBNB market. The redeemed BNB is wrapped to WBNB before deposit. Reverts unless
|
|
99
|
-
*
|
|
100
|
+
* vBNB market. The redeemed BNB is wrapped to WBNB before deposit. Reverts unless `hub` is a
|
|
101
|
+
* registered WBNB Hub (`IHub(hub).asset() == WBNB`) and `shares >= minShares`.
|
|
100
102
|
* This is the no-consent path — use {migrateFromCoreBNBWithConsent} to record consent.
|
|
101
103
|
* @param vBNBAmount vBNB receipts to pull and redeem.
|
|
102
104
|
* @param hub The WBNB Hub to deposit into.
|
|
@@ -9,8 +9,9 @@ pragma solidity 0.8.25;
|
|
|
9
9
|
* @dev The band vouches for a value: the last counterparty figure we accepted, grown at the
|
|
10
10
|
* expected rate, plus whatever we have moved in or out since. Valuation is held a gap either
|
|
11
11
|
* side of that, `min(max(observed, floor), cap)`, and never reverts. The gaps are a percentage
|
|
12
|
-
* of
|
|
13
|
-
* the
|
|
12
|
+
* of `anchor`. Our own moves shift only `centre`, so they do not widen the gaps until the next
|
|
13
|
+
* re-anchor, which adopts the whole position as the new `anchor`. Take everything out and the
|
|
14
|
+
* band closes; a deposit or governance reopens it.
|
|
14
15
|
*
|
|
15
16
|
* It bounds value, not price: a position marked as `quantity x price` can be inflated
|
|
16
17
|
* through either factor. Value is the product plus whatever has no price at all, so one band
|
|
@@ -46,9 +47,9 @@ interface INavGuard {
|
|
|
46
47
|
* `anchor` and `centre` are stored as of `driftFrom`, and each grows at `driftBps` from
|
|
47
48
|
* there when read. A closed band has both at zero: every armed side then values the
|
|
48
49
|
* position at nothing, and only a deposit or {setNavGuardSnapshot} reopens it.
|
|
49
|
-
* @param anchor The
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* @param anchor The whole position value accepted at the last re-anchor, or forced by
|
|
51
|
+
* governance, less what withdrawals have since taken past our own deposits. Never above
|
|
52
|
+
* `centre`. Both gaps are sized to it.
|
|
52
53
|
* @param centre `anchor` plus our own deposits since. What valuation is held around.
|
|
53
54
|
* @param anchoredAt When `anchor` was last set. `interval` is measured from here, and only a
|
|
54
55
|
* re-anchor moves it, so our own flows can never delay or bring one forward.
|
|
@@ -124,10 +125,10 @@ interface INavGuard {
|
|
|
124
125
|
|
|
125
126
|
/**
|
|
126
127
|
* @notice We moved value into or out of a resource, and its centre moved by exactly that. The
|
|
127
|
-
* clock is untouched, and the anchor moves only when a withdrawal
|
|
128
|
-
*
|
|
128
|
+
* clock is untouched, and the anchor moves only when a withdrawal takes more than our
|
|
129
|
+
* own deposits since the last re-anchor.
|
|
129
130
|
* @param resource Resource whose centre moved.
|
|
130
|
-
* @param anchor The
|
|
131
|
+
* @param anchor The anchor after the move. Zero right after a deposit means the
|
|
131
132
|
* deposit opened the band, and the band has no width until its first re-anchor.
|
|
132
133
|
* @param centre New stored centre, before drift.
|
|
133
134
|
*/
|
|
@@ -6,6 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s
|
|
|
6
6
|
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
7
7
|
|
|
8
8
|
import { IHub } from "../interfaces/IHub.sol";
|
|
9
|
+
import { IHubRegistry } from "../interfaces/IHubRegistry.sol";
|
|
9
10
|
import { IMigrator } from "../interfaces/IMigrator.sol";
|
|
10
11
|
import { IVBep20 } from "../interfaces/external/IVBep20.sol";
|
|
11
12
|
import { IWBNB } from "../interfaces/external/IWBNB.sol";
|
|
@@ -14,12 +15,16 @@ import { IWBNB } from "../interfaces/external/IWBNB.sol";
|
|
|
14
15
|
* @title Migrator
|
|
15
16
|
* @author Venus
|
|
16
17
|
* @notice One-click migration from a Venus Core position into a Liquidity Hub. A single immutable,
|
|
17
|
-
* permissionless deployment serves every Hub; the target Hub is supplied per call
|
|
18
|
+
* permissionless deployment serves every registered Hub; the target Hub is supplied per call
|
|
19
|
+
* and must be present in the HubRegistry.
|
|
18
20
|
* @dev Stateless by design — no proxy, no admin, no funds held between calls. Each migration is
|
|
19
21
|
* atomic: pull the user's vToken, redeem it to the underlying, deposit that into the Hub, and
|
|
20
22
|
* mint shares straight to the receiver. If any leg fails the whole call reverts and the user
|
|
21
23
|
* keeps their vToken.
|
|
22
24
|
*
|
|
25
|
+
* The target `hub` is checked against the immutable {HUB_REGISTRY} before any funds move, so a
|
|
26
|
+
* migration can only land in a Hub governance has registered.
|
|
27
|
+
*
|
|
23
28
|
* Accounting is done by balance delta, never by a self-reported amount: Core `redeem` returns
|
|
24
29
|
* a Compound error code (not the redeemed amount) and silently nets out any Comptroller
|
|
25
30
|
* treasury fee, so the Migrator measures what actually arrived and deposits exactly that. The
|
|
@@ -34,16 +39,24 @@ import { IWBNB } from "../interfaces/external/IWBNB.sol";
|
|
|
34
39
|
contract Migrator is IMigrator, ReentrancyGuard {
|
|
35
40
|
using SafeERC20 for IERC20;
|
|
36
41
|
|
|
37
|
-
/// @notice
|
|
42
|
+
/// @notice Governance-curated directory of valid Hubs.
|
|
43
|
+
IHubRegistry public immutable HUB_REGISTRY;
|
|
44
|
+
/// @notice Canonical wrapped-native token (WBNB).
|
|
38
45
|
IWBNB public immutable WBNB;
|
|
39
|
-
/// @notice Canonical Venus native market (vBNB)
|
|
46
|
+
/// @notice Canonical Venus native market (vBNB).
|
|
40
47
|
IVBep20 public immutable VBNB;
|
|
41
48
|
|
|
42
49
|
/**
|
|
43
|
-
* @param wbnb_ Wrapped-native token
|
|
44
|
-
* @param vbnb_ Venus native market (vBNB)
|
|
50
|
+
* @param wbnb_ Wrapped-native token (WBNB). Mandatory; the constructor reverts on the zero address.
|
|
51
|
+
* @param vbnb_ Venus native market (vBNB). Mandatory; the constructor reverts on the zero address.
|
|
52
|
+
* @param hubRegistry_ The HubRegistry every migration target is validated against. Mandatory;
|
|
53
|
+
* the constructor reverts on the zero address, since without it no Hub would be accepted.
|
|
45
54
|
*/
|
|
46
|
-
constructor(IWBNB wbnb_, IVBep20 vbnb_) {
|
|
55
|
+
constructor(IWBNB wbnb_, IVBep20 vbnb_, IHubRegistry hubRegistry_) {
|
|
56
|
+
if (address(wbnb_) == address(0) || address(vbnb_) == address(0) || address(hubRegistry_) == address(0)) {
|
|
57
|
+
revert ZeroAddress();
|
|
58
|
+
}
|
|
59
|
+
HUB_REGISTRY = hubRegistry_;
|
|
47
60
|
WBNB = wbnb_;
|
|
48
61
|
VBNB = vbnb_;
|
|
49
62
|
}
|
|
@@ -111,6 +124,7 @@ contract Migrator is IMigrator, ReentrancyGuard {
|
|
|
111
124
|
revert ZeroAddress();
|
|
112
125
|
}
|
|
113
126
|
if (vTokenAmount == 0) revert ZeroAmount();
|
|
127
|
+
if (!HUB_REGISTRY.isHub(hub)) revert HubNotRegistered(hub);
|
|
114
128
|
|
|
115
129
|
address asset = IHub(hub).asset();
|
|
116
130
|
address vTokenUnderlying = IVBep20(vToken).underlying();
|
|
@@ -135,11 +149,9 @@ contract Migrator is IMigrator, ReentrancyGuard {
|
|
|
135
149
|
private
|
|
136
150
|
returns (uint256 shares)
|
|
137
151
|
{
|
|
138
|
-
if (address(WBNB) == address(0) || address(VBNB) == address(0)) {
|
|
139
|
-
revert BNBMigrationUnsupported();
|
|
140
|
-
}
|
|
141
152
|
if (hub == address(0) || receiver == address(0)) revert ZeroAddress();
|
|
142
153
|
if (vBNBAmount == 0) revert ZeroAmount();
|
|
154
|
+
if (!HUB_REGISTRY.isHub(hub)) revert HubNotRegistered(hub);
|
|
143
155
|
|
|
144
156
|
address asset = IHub(hub).asset();
|
|
145
157
|
if (asset != address(WBNB)) revert AssetMismatch(address(WBNB), asset);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"address": "
|
|
3
|
-
"block":
|
|
2
|
+
"address": "0xb0D77DC86d4E6a8282eECF3d4d456B3b94F2a7aD",
|
|
3
|
+
"block": 115268335,
|
|
4
4
|
"chainId": 56,
|
|
5
5
|
"contract": "Migrator",
|
|
6
6
|
"abi": [
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
"name": "vbnb_",
|
|
17
17
|
"type": "address",
|
|
18
18
|
"internalType": "contract IVBep20"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "hubRegistry_",
|
|
22
|
+
"type": "address",
|
|
23
|
+
"internalType": "contract IHubRegistry"
|
|
19
24
|
}
|
|
20
25
|
],
|
|
21
26
|
"stateMutability": "nonpayable"
|
|
@@ -24,6 +29,19 @@
|
|
|
24
29
|
"type": "receive",
|
|
25
30
|
"stateMutability": "payable"
|
|
26
31
|
},
|
|
32
|
+
{
|
|
33
|
+
"type": "function",
|
|
34
|
+
"name": "HUB_REGISTRY",
|
|
35
|
+
"inputs": [],
|
|
36
|
+
"outputs": [
|
|
37
|
+
{
|
|
38
|
+
"name": "",
|
|
39
|
+
"type": "address",
|
|
40
|
+
"internalType": "contract IHubRegistry"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"stateMutability": "view"
|
|
44
|
+
},
|
|
27
45
|
{
|
|
28
46
|
"type": "function",
|
|
29
47
|
"name": "VBNB",
|
|
@@ -292,8 +310,14 @@
|
|
|
292
310
|
},
|
|
293
311
|
{
|
|
294
312
|
"type": "error",
|
|
295
|
-
"name": "
|
|
296
|
-
"inputs": [
|
|
313
|
+
"name": "HubNotRegistered",
|
|
314
|
+
"inputs": [
|
|
315
|
+
{
|
|
316
|
+
"name": "hub",
|
|
317
|
+
"type": "address",
|
|
318
|
+
"internalType": "address"
|
|
319
|
+
}
|
|
320
|
+
]
|
|
297
321
|
},
|
|
298
322
|
{
|
|
299
323
|
"type": "error",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"Hub_U": "0x0e5AA174d4F31b757a237eb1999DE151596788B0",
|
|
31
31
|
"Hub_USDC": "0x9D2D9592cF8DFbf59107fAab703d08494BE14617",
|
|
32
32
|
"Hub_USDT": "0x18AfDACF30F8671021dec4b78297E39d2FE87226",
|
|
33
|
-
"Migrator": "
|
|
33
|
+
"Migrator": "0xb0D77DC86d4E6a8282eECF3d4d456B3b94F2a7aD",
|
|
34
34
|
"SpokeBeacon": "0x652AAa1CD21f47a92D04680C1d9Bf99cC1Fb8975",
|
|
35
35
|
"SpokeSource_U": "0xcA3f9AFD7C4c1022E4A37a12671Ef57B8A7bfc51",
|
|
36
36
|
"SpokeSource_USDC": "0x4cd4aB2F88E39D3F3e481bb2555EcdF00266f717",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"address": "
|
|
3
|
-
"block":
|
|
2
|
+
"address": "0x907d344E53A6e51Cb3E01cb76495D6C5325d72e8",
|
|
3
|
+
"block": 124402350,
|
|
4
4
|
"chainId": 97,
|
|
5
5
|
"contract": "Migrator",
|
|
6
6
|
"abi": [
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
"name": "vbnb_",
|
|
17
17
|
"type": "address",
|
|
18
18
|
"internalType": "contract IVBep20"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "hubRegistry_",
|
|
22
|
+
"type": "address",
|
|
23
|
+
"internalType": "contract IHubRegistry"
|
|
19
24
|
}
|
|
20
25
|
],
|
|
21
26
|
"stateMutability": "nonpayable"
|
|
@@ -24,6 +29,19 @@
|
|
|
24
29
|
"type": "receive",
|
|
25
30
|
"stateMutability": "payable"
|
|
26
31
|
},
|
|
32
|
+
{
|
|
33
|
+
"type": "function",
|
|
34
|
+
"name": "HUB_REGISTRY",
|
|
35
|
+
"inputs": [],
|
|
36
|
+
"outputs": [
|
|
37
|
+
{
|
|
38
|
+
"name": "",
|
|
39
|
+
"type": "address",
|
|
40
|
+
"internalType": "contract IHubRegistry"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"stateMutability": "view"
|
|
44
|
+
},
|
|
27
45
|
{
|
|
28
46
|
"type": "function",
|
|
29
47
|
"name": "VBNB",
|
|
@@ -292,8 +310,14 @@
|
|
|
292
310
|
},
|
|
293
311
|
{
|
|
294
312
|
"type": "error",
|
|
295
|
-
"name": "
|
|
296
|
-
"inputs": [
|
|
313
|
+
"name": "HubNotRegistered",
|
|
314
|
+
"inputs": [
|
|
315
|
+
{
|
|
316
|
+
"name": "hub",
|
|
317
|
+
"type": "address",
|
|
318
|
+
"internalType": "address"
|
|
319
|
+
}
|
|
320
|
+
]
|
|
297
321
|
},
|
|
298
322
|
{
|
|
299
323
|
"type": "error",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"HubRegistryImpl": "0x4D2C18fB4520c2e4f7C754979e9a4F3BbC1BCe92",
|
|
23
23
|
"HubRegistryProxyAdmin": "0x9f8413eEE33D434F6D4f40C83181f32A831c9ef7",
|
|
24
24
|
"Hub_USDT": "0x7cE6ADF754D0eC81A6CF8ACd9C7454F45077dc61",
|
|
25
|
-
"Migrator": "
|
|
25
|
+
"Migrator": "0x907d344E53A6e51Cb3E01cb76495D6C5325d72e8",
|
|
26
26
|
"MockCentrifugeShare_USDT": "0xFF5254510b85D00a4f28fEb850bddF18d6deB043",
|
|
27
27
|
"MockCentrifugeVault_USDT": "0x8a3C1f806B635230b74741066DE9B0Aa902F83dC",
|
|
28
28
|
"SpokeBeacon": "0x58099BEaa6A00F493eF67fF953866DDA4a502389",
|