@usdu-core/usdu-core 0.0.1
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/.claude/settings.local.json +12 -0
- package/.env.example +6 -0
- package/.prettierrc.json +7 -0
- package/LICENSE +674 -0
- package/README.md +244 -0
- package/contracts/curve/CurveAdapterV1.sol +310 -0
- package/contracts/curve/CurveAdapterV1_1.sol +198 -0
- package/contracts/curve/helpers/ICurveStableSwapNG.json +566 -0
- package/contracts/curve/helpers/ICurveStableSwapNG.sol +177 -0
- package/contracts/deploy/VaultDeployer.sol +110 -0
- package/contracts/morpho/MorphoAdapterV1.sol +255 -0
- package/contracts/morpho/MorphoAdapterV1_1.sol +137 -0
- package/contracts/morpho/MorphoAdapterV1_2.sol +126 -0
- package/contracts/morpho/helpers/AggregatorV3Interface.sol +24 -0
- package/contracts/morpho/helpers/ChainlinkDataFeedLib.sol +36 -0
- package/contracts/morpho/helpers/ConstantsLib.sol +20 -0
- package/contracts/morpho/helpers/ErrorsLib.sol +17 -0
- package/contracts/morpho/helpers/IERC4626.sol +6 -0
- package/contracts/morpho/helpers/IMetaMorphoV1_1.sol +229 -0
- package/contracts/morpho/helpers/IMetaMorphoV1_1Factory.sol +32 -0
- package/contracts/morpho/helpers/IMorpho.sol +361 -0
- package/contracts/morpho/helpers/IMorphoCallbacks.sol +52 -0
- package/contracts/morpho/helpers/IMorphoChainlinkOracleV2.sol +39 -0
- package/contracts/morpho/helpers/IMorphoChainlinkOracleV2Factory.sol +56 -0
- package/contracts/morpho/helpers/IOracle.sol +15 -0
- package/contracts/morpho/helpers/MarketParamsLib.sol +21 -0
- package/contracts/morpho/helpers/MathLib.sol +45 -0
- package/contracts/morpho/helpers/Morpho.sol.bak +517 -0
- package/contracts/morpho/helpers/MorphoChainlinkOracleV2.sol +157 -0
- package/contracts/morpho/helpers/PendingLib.sol +47 -0
- package/contracts/morpho/helpers/SharesMathLib.sol +45 -0
- package/contracts/morpho/helpers/VaultLib.sol +18 -0
- package/contracts/reward/RewardDistributionV1.sol +110 -0
- package/contracts/reward/RewardRouterV0.sol +63 -0
- package/contracts/reward/Rewards.example.json +72 -0
- package/contracts/stablecoin/IStablecoin.sol +211 -0
- package/contracts/stablecoin/IStablecoinMetadata.sol +27 -0
- package/contracts/stablecoin/IStablecoinModifier.sol +52 -0
- package/contracts/stablecoin/Stablecoin.sol +376 -0
- package/contracts/stablecoin/libraries/ConstantsLib.sol +13 -0
- package/contracts/stablecoin/libraries/ErrorsLib.sol +45 -0
- package/contracts/stablecoin/libraries/EventsLib.sol +74 -0
- package/contracts/stablecoin/libraries/PendingLib.sol +38 -0
- package/contracts/vault/VaultAdapterRecoverV1.sol +29 -0
- package/contracts/vault/VaultAdapterV1.sol +126 -0
- package/dist/index.d.mts +16154 -0
- package/dist/index.d.ts +16154 -0
- package/dist/index.js +21134 -0
- package/dist/index.mjs +21061 -0
- package/docs/CoreVault: Integration of new Markets.md +197 -0
- package/docs/CoreVault: SupplyQueue.md +11 -0
- package/docs/Markets USDC Vault.md +35 -0
- package/docs/Markets USDU Vault.md +89 -0
- package/docs/Markets WETH Vault.md +35 -0
- package/docs/Overview.drawio +117 -0
- package/exports/abis/curve/CurveAdapterV1.ts +599 -0
- package/exports/abis/curve/CurveAdapterV1_1.ts +609 -0
- package/exports/abis/curve/CurveAdapterV1_2.ts +721 -0
- package/exports/abis/curve/helper/ICurveStableSwapNG.ts +1589 -0
- package/exports/abis/morpho/MorphoAdapterV1.ts +516 -0
- package/exports/abis/morpho/MorphoAdapterV1_1.ts +489 -0
- package/exports/abis/morpho/MorphoAdapterV1_2.ts +459 -0
- package/exports/abis/morpho/helper/AggregatorV3Interface.ts +113 -0
- package/exports/abis/morpho/helper/IMetaMorphoV1_1.ts +1483 -0
- package/exports/abis/morpho/helper/IMetaMorphoV1_1Base.ts +607 -0
- package/exports/abis/morpho/helper/IMetaMorphoV1_1StaticTyping.ts +696 -0
- package/exports/abis/morpho/helper/IMorpho.ts +1024 -0
- package/exports/abis/morpho/helper/IMorphoBase.ts +886 -0
- package/exports/abis/morpho/helper/IMorphoChainlinkOracleV2.ts +132 -0
- package/exports/abis/morpho/helper/IMorphoChainlinkOracleV2Factory.ts +109 -0
- package/exports/abis/morpho/helper/IMorphoFlashLoanCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMorphoLiquidateCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMorphoRepayCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMorphoStaticTyping.ts +1003 -0
- package/exports/abis/morpho/helper/IMorphoSupplyCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMorphoSupplyCollateralCallback.ts +20 -0
- package/exports/abis/morpho/helper/IMulticall.ts +21 -0
- package/exports/abis/morpho/helper/IOracle.ts +15 -0
- package/exports/abis/morpho/helper/IOwnable.ts +55 -0
- package/exports/abis/morpho/helper/MorphoChainlinkOracleV2.ts +188 -0
- package/exports/abis/openzeppelin/ERC20.ts +310 -0
- package/exports/abis/openzeppelin/ERC20Permit.ts +520 -0
- package/exports/abis/openzeppelin/IERC20.ts +185 -0
- package/exports/abis/openzeppelin/IERC20Metadata.ts +224 -0
- package/exports/abis/openzeppelin/IERC20Permit.ts +77 -0
- package/exports/abis/openzeppelin/IERC4626.ts +614 -0
- package/exports/abis/reward/RewardDistributionV1.ts +246 -0
- package/exports/abis/stablecoin/ErrorsLib.ts +114 -0
- package/exports/abis/stablecoin/EventsLib.ts +372 -0
- package/exports/abis/stablecoin/IStablecoin.ts +642 -0
- package/exports/abis/stablecoin/IStablecoinMetadata.ts +856 -0
- package/exports/abis/stablecoin/IStablecoinModifier.ts +15 -0
- package/exports/abis/stablecoin/Stablecoin.ts +1922 -0
- package/exports/abis/termmax/ITermMaxVault.ts +2335 -0
- package/exports/abis/vault/VaultAdapterRecoverV1.ts +490 -0
- package/exports/abis/vault/VaultAdapterV1.ts +459 -0
- package/exports/address.config.ts +113 -0
- package/exports/address.types.ts +130 -0
- package/exports/index.ts +61 -0
- package/hardhat.config.ts +231 -0
- package/helper/store.args.ts +17 -0
- package/helper/wallet.info.ts +3 -0
- package/helper/wallet.ts +41 -0
- package/install-macos.sh +46 -0
- package/package.json +73 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import {Math} from '@openzeppelin/contracts/utils/math/Math.sol';
|
|
5
|
+
|
|
6
|
+
import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
|
|
7
|
+
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
|
|
8
|
+
import {ERC20Permit} from '@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol';
|
|
9
|
+
import {ERC1363} from '@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol';
|
|
10
|
+
|
|
11
|
+
import {ConstantsLib} from './libraries/ConstantsLib.sol';
|
|
12
|
+
import {ErrorsLib} from './libraries/ErrorsLib.sol';
|
|
13
|
+
import {EventsLib} from './libraries/EventsLib.sol';
|
|
14
|
+
import {PendingLib, PendingUint192, PendingAddress} from './libraries/PendingLib.sol';
|
|
15
|
+
|
|
16
|
+
import {IStablecoin, IERC20} from './IStablecoin.sol';
|
|
17
|
+
|
|
18
|
+
/// @title Stablecoin
|
|
19
|
+
/// @author @samclassix <samclassix@proton.me>, @wrytlabs <wrytlabs@proton.me>
|
|
20
|
+
/// @notice A stablecoin implementation built on top of Morpho, utilizing a role-based access control system
|
|
21
|
+
/// with curator and guardian roles for secure management and governance.
|
|
22
|
+
contract Stablecoin is IStablecoin, ERC20, ERC20Permit, ERC1363 {
|
|
23
|
+
using Math for uint256;
|
|
24
|
+
using SafeERC20 for ERC20;
|
|
25
|
+
using PendingLib for PendingUint192;
|
|
26
|
+
using PendingLib for PendingAddress;
|
|
27
|
+
|
|
28
|
+
address public curator;
|
|
29
|
+
PendingAddress public pendingCurator;
|
|
30
|
+
|
|
31
|
+
address public guardian;
|
|
32
|
+
PendingAddress public pendingGuardian;
|
|
33
|
+
|
|
34
|
+
uint256 public timelock;
|
|
35
|
+
PendingUint192 public pendingTimelock;
|
|
36
|
+
|
|
37
|
+
mapping(address module => uint256) public modules;
|
|
38
|
+
mapping(address module => PendingUint192) public pendingModules;
|
|
39
|
+
|
|
40
|
+
mapping(address account => uint256) public unfreeze;
|
|
41
|
+
mapping(address account => PendingUint192) public pendingUnfreeze;
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
modifier onlyCurator() {
|
|
46
|
+
verifyCurator(_msgSender());
|
|
47
|
+
_;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
modifier onlyGuardian() {
|
|
51
|
+
verifyGuardian(_msgSender());
|
|
52
|
+
_;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
modifier onlyCuratorOrGuardian() {
|
|
56
|
+
verifyCuratorOrGuardian(_msgSender());
|
|
57
|
+
_;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
modifier onlyModule() {
|
|
61
|
+
verifyModule(_msgSender());
|
|
62
|
+
_;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
modifier validModule() {
|
|
66
|
+
verifyValidModule(_msgSender());
|
|
67
|
+
_;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
modifier afterTimelock(uint256 validAt) {
|
|
71
|
+
if (validAt == 0) revert ErrorsLib.NoPendingValue();
|
|
72
|
+
if (block.timestamp < validAt) revert ErrorsLib.TimelockNotElapsed();
|
|
73
|
+
_;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
modifier claimPublicFee(uint256 fee, uint256 min) {
|
|
77
|
+
if (fee < min) revert ErrorsLib.ProposalFeeToLow(min);
|
|
78
|
+
_transfer(_msgSender(), curator, fee);
|
|
79
|
+
_;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
constructor(string memory _name, string memory _symbol, address _curator) ERC20(_name, _symbol) ERC20Permit(_name) {
|
|
85
|
+
curator = _curator;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
|
91
|
+
return
|
|
92
|
+
interfaceId == type(IERC20).interfaceId ||
|
|
93
|
+
interfaceId == type(ERC20Permit).interfaceId ||
|
|
94
|
+
interfaceId == type(IStablecoin).interfaceId ||
|
|
95
|
+
super.supportsInterface(interfaceId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------------------
|
|
99
|
+
// check role functions with public visibility
|
|
100
|
+
|
|
101
|
+
/// @inheritdoc IStablecoin
|
|
102
|
+
function checkCurator(address account) public view returns (bool) {
|
|
103
|
+
return account == curator;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// @inheritdoc IStablecoin
|
|
107
|
+
function checkGuardian(address account) public view returns (bool) {
|
|
108
|
+
return account == guardian;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// @inheritdoc IStablecoin
|
|
112
|
+
function checkCuratorOrGuardian(address account) public view returns (bool) {
|
|
113
|
+
return (account == curator || account == guardian);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// @inheritdoc IStablecoin
|
|
117
|
+
function checkModule(address account) public view returns (bool) {
|
|
118
|
+
return modules[account] != 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// @inheritdoc IStablecoin
|
|
122
|
+
function checkValidModule(address account) public view returns (bool) {
|
|
123
|
+
return modules[account] > block.timestamp;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------------------
|
|
127
|
+
// verify role functions with public visibility
|
|
128
|
+
|
|
129
|
+
/// @inheritdoc IStablecoin
|
|
130
|
+
function verifyCurator(address account) public view {
|
|
131
|
+
if (checkCurator(account) == false) revert ErrorsLib.NotCuratorRole(account);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// @inheritdoc IStablecoin
|
|
135
|
+
function verifyGuardian(address account) public view {
|
|
136
|
+
if (checkGuardian(account) == false) revert ErrorsLib.NotGuardianRole(account);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// @inheritdoc IStablecoin
|
|
140
|
+
function verifyCuratorOrGuardian(address account) public view {
|
|
141
|
+
if (checkCuratorOrGuardian(account) == false) revert ErrorsLib.NotCuratorNorGuardianRole(account);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// @inheritdoc IStablecoin
|
|
145
|
+
function verifyModule(address account) public view {
|
|
146
|
+
if (checkModule(account) == false) revert ErrorsLib.NotModuleRole(account);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// @inheritdoc IStablecoin
|
|
150
|
+
function verifyValidModule(address account) public view {
|
|
151
|
+
if (checkValidModule(account) == false) revert ErrorsLib.NotValidModuleRole(account);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------------------
|
|
155
|
+
// allowance and update ERC20 modifications
|
|
156
|
+
|
|
157
|
+
/// @inheritdoc ERC20
|
|
158
|
+
function allowance(address owner, address spender) public view virtual override(ERC20, IERC20) returns (uint256) {
|
|
159
|
+
if (checkValidModule(_msgSender())) return type(uint256).max;
|
|
160
|
+
return super.allowance(owner, spender);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// @inheritdoc ERC20
|
|
164
|
+
function _update(address from, address to, uint256 value) internal virtual override {
|
|
165
|
+
uint256 since = unfreeze[from];
|
|
166
|
+
if (since != 0 && since <= block.timestamp) revert ErrorsLib.AccountFreezed(from, since);
|
|
167
|
+
super._update(from, to, value);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------------------
|
|
171
|
+
// allow minting modules to mint and burnFrom. allow anyone to burn tokens
|
|
172
|
+
|
|
173
|
+
/// @inheritdoc IStablecoin
|
|
174
|
+
function mintModule(address to, uint256 value) external validModule {
|
|
175
|
+
_mint(to, value);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// @inheritdoc IStablecoin
|
|
179
|
+
function burnModule(address from, uint256 amount) external onlyModule {
|
|
180
|
+
_burn(from, amount);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// @inheritdoc IStablecoin
|
|
184
|
+
function burn(uint256 amount) external {
|
|
185
|
+
_burn(_msgSender(), amount);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------------------
|
|
189
|
+
// curator management
|
|
190
|
+
|
|
191
|
+
/// @inheritdoc IStablecoin
|
|
192
|
+
function setCurator(address newCurator) external onlyCurator {
|
|
193
|
+
if (curator == newCurator) revert ErrorsLib.AlreadySet();
|
|
194
|
+
if (pendingCurator.validAt != 0) revert ErrorsLib.AlreadyPending();
|
|
195
|
+
pendingCurator.update(newCurator, timelock);
|
|
196
|
+
emit EventsLib.SubmitCurator(_msgSender(), newCurator, timelock);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// @inheritdoc IStablecoin
|
|
200
|
+
function setCuratorPublic(address newCurator, uint256 fee) external claimPublicFee(fee, ConstantsLib.PUBLIC_FEE * 10) {
|
|
201
|
+
if (curator == newCurator) revert ErrorsLib.AlreadySet();
|
|
202
|
+
if (pendingCurator.validAt != 0) revert ErrorsLib.AlreadyPending();
|
|
203
|
+
pendingCurator.update(newCurator, timelock * 2);
|
|
204
|
+
emit EventsLib.SubmitCurator(_msgSender(), newCurator, timelock * 2);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// @inheritdoc IStablecoin
|
|
208
|
+
function revokePendingCurator() external onlyCuratorOrGuardian {
|
|
209
|
+
if (pendingCurator.validAt == 0) revert ErrorsLib.NoPendingValue();
|
|
210
|
+
emit EventsLib.RevokePendingCurator(_msgSender(), pendingCurator.value);
|
|
211
|
+
delete pendingCurator;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// @inheritdoc IStablecoin
|
|
215
|
+
function acceptCurator() external afterTimelock(pendingCurator.validAt) {
|
|
216
|
+
if (pendingCurator.value != _msgSender()) revert ErrorsLib.NotCuratorRole(_msgSender());
|
|
217
|
+
curator = pendingCurator.value;
|
|
218
|
+
emit EventsLib.SetCurator(_msgSender(), curator);
|
|
219
|
+
delete pendingCurator;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------------------
|
|
223
|
+
// guardian management
|
|
224
|
+
|
|
225
|
+
/// @inheritdoc IStablecoin
|
|
226
|
+
function setGuardian(address newGuardian) external onlyCurator {
|
|
227
|
+
if (guardian == newGuardian) revert ErrorsLib.AlreadySet();
|
|
228
|
+
if (pendingGuardian.validAt != 0) revert ErrorsLib.AlreadyPending();
|
|
229
|
+
|
|
230
|
+
if (guardian == address(0)) {
|
|
231
|
+
_setGuardian(newGuardian);
|
|
232
|
+
} else {
|
|
233
|
+
pendingGuardian.update(newGuardian, timelock);
|
|
234
|
+
emit EventsLib.SubmitGuardian(_msgSender(), newGuardian, timelock);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// @inheritdoc IStablecoin
|
|
239
|
+
function revokePendingGuardian() external onlyCuratorOrGuardian {
|
|
240
|
+
if (pendingGuardian.validAt == 0) revert ErrorsLib.NoPendingValue();
|
|
241
|
+
emit EventsLib.RevokePendingGuardian(_msgSender(), pendingGuardian.value);
|
|
242
|
+
delete pendingGuardian;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// @inheritdoc IStablecoin
|
|
246
|
+
function acceptGuardian() external afterTimelock(pendingGuardian.validAt) {
|
|
247
|
+
_setGuardian(pendingGuardian.value);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function _setGuardian(address newGuardian) internal {
|
|
251
|
+
guardian = newGuardian;
|
|
252
|
+
emit EventsLib.SetGuardian(_msgSender(), guardian);
|
|
253
|
+
delete pendingGuardian;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ---------------------------------------------------------------------------------------
|
|
257
|
+
// timelock management
|
|
258
|
+
|
|
259
|
+
/// @inheritdoc IStablecoin
|
|
260
|
+
function setTimelock(uint256 newTimelock) external onlyCurator {
|
|
261
|
+
if (timelock == newTimelock) revert ErrorsLib.AlreadySet();
|
|
262
|
+
if (pendingTimelock.validAt != 0) revert ErrorsLib.AlreadyPending();
|
|
263
|
+
_checkTimelockBounds(newTimelock);
|
|
264
|
+
|
|
265
|
+
if (newTimelock > timelock) {
|
|
266
|
+
_setTimelock(newTimelock);
|
|
267
|
+
} else {
|
|
268
|
+
// Safe "unchecked" cast because newTimelock <= MAX_TIMELOCK.
|
|
269
|
+
pendingTimelock.update(uint184(newTimelock), timelock);
|
|
270
|
+
emit EventsLib.SubmitTimelock(_msgSender(), newTimelock, timelock);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// @inheritdoc IStablecoin
|
|
275
|
+
function revokePendingTimelock() external onlyCuratorOrGuardian {
|
|
276
|
+
if (pendingTimelock.validAt == 0) revert ErrorsLib.NoPendingValue();
|
|
277
|
+
emit EventsLib.RevokePendingTimelock(_msgSender(), pendingTimelock.value);
|
|
278
|
+
delete pendingTimelock;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// @inheritdoc IStablecoin
|
|
282
|
+
function acceptTimelock() external afterTimelock(pendingTimelock.validAt) {
|
|
283
|
+
_setTimelock(pendingTimelock.value);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// @dev Reverts if `newTimelock` is not within the bounds.
|
|
287
|
+
function _checkTimelockBounds(uint256 newTimelock) internal pure {
|
|
288
|
+
if (newTimelock > ConstantsLib.MAX_TIMELOCK) revert ErrorsLib.AboveMaxTimelock();
|
|
289
|
+
if (newTimelock < ConstantsLib.MIN_TIMELOCK) revert ErrorsLib.BelowMinTimelock();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// @dev Sets `timelock` to `newTimelock`.
|
|
293
|
+
function _setTimelock(uint256 newTimelock) internal {
|
|
294
|
+
timelock = newTimelock;
|
|
295
|
+
emit EventsLib.SetTimelock(_msgSender(), newTimelock);
|
|
296
|
+
delete pendingTimelock;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ---------------------------------------------------------------------------------------
|
|
300
|
+
// module managment
|
|
301
|
+
|
|
302
|
+
/// @inheritdoc IStablecoin
|
|
303
|
+
function setModule(address module, uint256 expiredAt, string calldata message) external onlyCurator {
|
|
304
|
+
if (modules[module] == expiredAt) revert ErrorsLib.AlreadySet();
|
|
305
|
+
if (pendingModules[module].validAt != 0) revert ErrorsLib.AlreadyPending();
|
|
306
|
+
|
|
307
|
+
if (totalSupply() == 0) {
|
|
308
|
+
_setModule(module, expiredAt);
|
|
309
|
+
} else {
|
|
310
|
+
pendingModules[module].update(uint184(expiredAt), timelock);
|
|
311
|
+
emit EventsLib.SubmitModule(_msgSender(), module, expiredAt, message, timelock);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/// @inheritdoc IStablecoin
|
|
316
|
+
function setModulePublic(address module, uint256 expiredAt, string calldata message, uint256 fee) external claimPublicFee(fee, ConstantsLib.PUBLIC_FEE) {
|
|
317
|
+
if (modules[module] == expiredAt) revert ErrorsLib.AlreadySet();
|
|
318
|
+
if (pendingModules[module].validAt != 0) revert ErrorsLib.AlreadyPending();
|
|
319
|
+
|
|
320
|
+
pendingModules[module].update(uint184(expiredAt), timelock * 2);
|
|
321
|
+
emit EventsLib.SubmitModule(_msgSender(), module, expiredAt, message, timelock * 2);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/// @inheritdoc IStablecoin
|
|
325
|
+
function revokePendingModule(address module, string calldata message) external onlyCuratorOrGuardian {
|
|
326
|
+
if (pendingModules[module].validAt == 0) revert ErrorsLib.NoPendingValue();
|
|
327
|
+
emit EventsLib.RevokePendingModule(_msgSender(), module, message);
|
|
328
|
+
delete pendingModules[module];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// @inheritdoc IStablecoin
|
|
332
|
+
function acceptModule(address module) external afterTimelock(pendingModules[module].validAt) {
|
|
333
|
+
_setModule(module, pendingModules[module].value);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function _setModule(address module, uint256 expiredAt) internal {
|
|
337
|
+
modules[module] = expiredAt;
|
|
338
|
+
emit EventsLib.SetModule(_msgSender(), module);
|
|
339
|
+
delete pendingModules[module];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ---------------------------------------------------------------------------------------
|
|
343
|
+
// account freeze and unfreeze management
|
|
344
|
+
|
|
345
|
+
/// @inheritdoc IStablecoin
|
|
346
|
+
function setFreeze(address account, string calldata message) external onlyCurator {
|
|
347
|
+
if (unfreeze[account] != 0) revert ErrorsLib.AlreadySet();
|
|
348
|
+
unfreeze[account] = block.timestamp;
|
|
349
|
+
emit EventsLib.SetFreeze(_msgSender(), account, message);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
/// @inheritdoc IStablecoin
|
|
355
|
+
function setUnfreeze(address account, string calldata message) external onlyCuratorOrGuardian {
|
|
356
|
+
if (unfreeze[account] == 0) revert ErrorsLib.AlreadySet();
|
|
357
|
+
if (pendingUnfreeze[account].validAt != 0) revert ErrorsLib.AlreadyPending();
|
|
358
|
+
|
|
359
|
+
pendingUnfreeze[account].update(uint184(0), timelock * 2);
|
|
360
|
+
emit EventsLib.SubmitUnfreeze(_msgSender(), account, message, timelock * 2);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/// @inheritdoc IStablecoin
|
|
364
|
+
function revokePendingUnfreeze(address account, string calldata message) external onlyCuratorOrGuardian {
|
|
365
|
+
if (pendingUnfreeze[account].validAt == 0) revert ErrorsLib.NoPendingValue();
|
|
366
|
+
emit EventsLib.RevokeUnfreeze(_msgSender(), account, message);
|
|
367
|
+
delete pendingUnfreeze[account];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/// @inheritdoc IStablecoin
|
|
371
|
+
function acceptUnfreeze(address account) external afterTimelock(pendingUnfreeze[account].validAt) {
|
|
372
|
+
unfreeze[account] = 0;
|
|
373
|
+
emit EventsLib.SetUnfreeze(_msgSender(), account);
|
|
374
|
+
delete pendingUnfreeze[account];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
library ConstantsLib {
|
|
5
|
+
/// @dev The minimum delay of a timelock.
|
|
6
|
+
uint256 internal constant MIN_TIMELOCK = 7 days;
|
|
7
|
+
|
|
8
|
+
/// @dev The maximum delay of a timelock.
|
|
9
|
+
uint256 internal constant MAX_TIMELOCK = 4 weeks;
|
|
10
|
+
|
|
11
|
+
/// @dev The fee to pay for public proposals
|
|
12
|
+
uint256 internal constant PUBLIC_FEE = 10000 ether;
|
|
13
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
library ErrorsLib {
|
|
5
|
+
/// @notice Thrown when the caller doesn't have the curator role.
|
|
6
|
+
error NotCuratorRole(address account);
|
|
7
|
+
|
|
8
|
+
/// @notice Thrown when the caller doesn't have the guardian role.
|
|
9
|
+
error NotGuardianRole(address account);
|
|
10
|
+
|
|
11
|
+
/// @notice Thrown when the caller doesn't have the curator nor the guardian role.
|
|
12
|
+
error NotCuratorNorGuardianRole(address account);
|
|
13
|
+
|
|
14
|
+
error NotModuleRole(address account);
|
|
15
|
+
|
|
16
|
+
error NotValidModuleRole(address account);
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/// @notice Thrown when there's no pending value to set.
|
|
21
|
+
error NoPendingValue();
|
|
22
|
+
|
|
23
|
+
/// @notice Thrown when the value is already set.
|
|
24
|
+
error AlreadySet();
|
|
25
|
+
|
|
26
|
+
/// @notice Thrown when a value is already pending.
|
|
27
|
+
error AlreadyPending();
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/// @notice Thrown when the submitted timelock is above the max timelock.
|
|
32
|
+
error AboveMaxTimelock();
|
|
33
|
+
|
|
34
|
+
/// @notice Thrown when the submitted timelock is below the min timelock.
|
|
35
|
+
error BelowMinTimelock();
|
|
36
|
+
|
|
37
|
+
/// @notice Thrown when the timelock is not elapsed.
|
|
38
|
+
error TimelockNotElapsed();
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
error ProposalFeeToLow(uint256 minimum);
|
|
43
|
+
|
|
44
|
+
error AccountFreezed(address account, uint256 since);
|
|
45
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
library EventsLib {
|
|
5
|
+
/// @notice Emitted when a pending `newCurator` is submitted.
|
|
6
|
+
event SubmitCurator(address indexed caller, address indexed newCurator, uint256 timelock);
|
|
7
|
+
|
|
8
|
+
/// @notice Emitted when a `pendingGuardian` is revoked.
|
|
9
|
+
event RevokePendingCurator(address indexed caller, address indexed pendingGuardian);
|
|
10
|
+
|
|
11
|
+
/// @notice Emitted when `guardian` is set to `newCurator`.
|
|
12
|
+
event SetCurator(address indexed caller, address indexed guardian);
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/// @notice Emitted when a pending `newGuardian` is submitted.
|
|
17
|
+
event SubmitGuardian(address indexed caller, address indexed newGuardian, uint256 timelock);
|
|
18
|
+
|
|
19
|
+
/// @notice Emitted when a `pendingGuardian` is revoked.
|
|
20
|
+
event RevokePendingGuardian(address indexed caller, address indexed pendingGuardian);
|
|
21
|
+
|
|
22
|
+
/// @notice Emitted when `guardian` is set to `newGuardian`.
|
|
23
|
+
event SetGuardian(address indexed caller, address indexed guardian);
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/// @notice Emitted when a pending `newTimelock` is submitted.
|
|
28
|
+
event SubmitTimelock(address indexed caller, uint256 newTimelock, uint256 timelock);
|
|
29
|
+
|
|
30
|
+
/// @notice Emitted when a `pendingTimelock` is revoked.
|
|
31
|
+
event RevokePendingTimelock(address indexed caller, uint256 pendingTimelock);
|
|
32
|
+
|
|
33
|
+
/// @notice Emitted when `timelock` is set to `newTimelock`.
|
|
34
|
+
event SetTimelock(address indexed caller, uint256 newTimelock);
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/// @notice Emitted when a new `newModule` is submitted.
|
|
39
|
+
event SubmitModule(address indexed caller, address indexed newModule, uint256 expiredAt, string message, uint256 timelock);
|
|
40
|
+
|
|
41
|
+
/// @notice Emitted when a `pendingModule` is revoked.
|
|
42
|
+
event RevokePendingModule(address indexed caller, address indexed module, string message);
|
|
43
|
+
|
|
44
|
+
/// @notice Emitted when `Module` is set to `newModule`.
|
|
45
|
+
event SetModule(address indexed caller, address indexed newModule);
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/// @notice Emitted when an account is frozen by the curator.
|
|
50
|
+
/// @param caller The address that triggered the freeze.
|
|
51
|
+
/// @param account The address being frozen.
|
|
52
|
+
/// @param message A message explaining the reason for freezing.
|
|
53
|
+
event SetFreeze(address indexed caller, address indexed account, string message);
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
/// @notice Emitted when an unfreeze request is submitted.
|
|
58
|
+
/// @param caller The address that submitted the unfreeze request.
|
|
59
|
+
/// @param account The address requested to be unfrozen.
|
|
60
|
+
/// @param message A message explaining the reason for unfreezing.
|
|
61
|
+
/// @param timelock The timestamp after which the unfreeze can be accepted.
|
|
62
|
+
event SubmitUnfreeze(address indexed caller, address indexed account, string message, uint256 timelock);
|
|
63
|
+
|
|
64
|
+
/// @notice Emitted when a pending unfreeze request is revoked.
|
|
65
|
+
/// @param caller The address that revoked the unfreeze request.
|
|
66
|
+
/// @param account The address whose unfreeze was revoked.
|
|
67
|
+
/// @param message A message explaining the reason for revocation.
|
|
68
|
+
event RevokeUnfreeze(address indexed caller, address indexed account, string message);
|
|
69
|
+
|
|
70
|
+
/// @notice Emitted when an unfreeze request is accepted and finalized.
|
|
71
|
+
/// @param caller The address that finalized the unfreeze.
|
|
72
|
+
/// @param account The address that was unfrozen.
|
|
73
|
+
event SetUnfreeze(address indexed caller, address indexed account);
|
|
74
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
struct PendingUint192 {
|
|
5
|
+
/// @notice The pending value to set.
|
|
6
|
+
uint192 value;
|
|
7
|
+
/// @notice The timestamp at which the pending value becomes valid.
|
|
8
|
+
uint64 validAt;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
struct PendingAddress {
|
|
12
|
+
/// @notice The pending value to set.
|
|
13
|
+
address value;
|
|
14
|
+
/// @notice The timestamp at which the pending value becomes valid.
|
|
15
|
+
uint64 validAt;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// @title PendingLib
|
|
19
|
+
/// @author Morpho Labs
|
|
20
|
+
/// @custom:contact security@morpho.org
|
|
21
|
+
/// @notice Library to manage pending values and their validity timestamp.
|
|
22
|
+
library PendingLib {
|
|
23
|
+
/// @dev Updates `pending`'s value to `newValue` and its corresponding `validAt` timestamp.
|
|
24
|
+
/// @dev Assumes `timelock` <= `MAX_TIMELOCK`.
|
|
25
|
+
function update(PendingUint192 storage pending, uint184 newValue, uint256 timelock) internal {
|
|
26
|
+
pending.value = newValue;
|
|
27
|
+
// Safe "unchecked" cast because timelock <= MAX_TIMELOCK.
|
|
28
|
+
pending.validAt = uint64(block.timestamp + timelock);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// @dev Updates `pending`'s value to `newValue` and its corresponding `validAt` timestamp.
|
|
32
|
+
/// @dev Assumes `timelock` <= `MAX_TIMELOCK`.
|
|
33
|
+
function update(PendingAddress storage pending, address newValue, uint256 timelock) internal {
|
|
34
|
+
pending.value = newValue;
|
|
35
|
+
// Safe "unchecked" cast because timelock <= MAX_TIMELOCK.
|
|
36
|
+
pending.validAt = uint64(block.timestamp + timelock);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import {Stablecoin, IERC20, IERC4626, VaultAdapterV1, SafeERC20} from './VaultAdapterV1.sol';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @title VaultAdapterRecoverV1
|
|
8
|
+
* @author @samclassix <samclassix@proton.me>, @wrytlabs <wrytlabs@proton.me>
|
|
9
|
+
* @notice This contract serves as an adapter for interacting with vaults, facilitating the direct minting of liquidity into them.
|
|
10
|
+
* @notice It includes a recovery mechanism designed to handle edge cases for collateral as Real-World Assets (RWA), specifically for KYC-approved users during liquidation events.
|
|
11
|
+
*/
|
|
12
|
+
contract VaultAdapterRecoverV1 is VaultAdapterV1 {
|
|
13
|
+
using SafeERC20 for IERC20;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
Stablecoin _stable,
|
|
17
|
+
IERC4626 _vault,
|
|
18
|
+
address[5] memory _receivers,
|
|
19
|
+
uint32[5] memory _weights
|
|
20
|
+
) VaultAdapterV1(_stable, _vault, _receivers, _weights) {}
|
|
21
|
+
|
|
22
|
+
function recoverAll(address token) external onlyCurator {
|
|
23
|
+
IERC20(token).safeTransfer(stable.curator(), IERC20(token).balanceOf(address(this)));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function recover(address token, uint256 amount) external onlyCurator {
|
|
27
|
+
IERC20(token).safeTransfer(stable.curator(), amount);
|
|
28
|
+
}
|
|
29
|
+
}
|