-
Notifications
You must be signed in to change notification settings - Fork 1
/
MasterAMO.sol
348 lines (311 loc) · 13.7 KB
/
MasterAMO.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import {IMinter} from "./interfaces/IMinter.sol";
import {IBoostStablecoin} from "./interfaces/IBoostStablecoin.sol";
import {IMasterAMO} from "./interfaces/IMasterAMO.sol";
/**
* the contracts are upgradable but behind a time lock. This is because we plan further improvements to the AMO logic ( we could for instance deploy an AMO cotract for concentrated liquidity).
* in future versions, upgrades could be strictly tied to a governance vote (where upgrade can only be passed with testified governance vote approval)
* the contracts are pausable — also governance-unpausable to ensure decentralisation
*/
abstract contract MasterAMO is
IMasterAMO,
Initializable,
AccessControlEnumerableUpgradeable,
PausableUpgradeable,
ReentrancyGuardUpgradeable
{
using SafeERC20Upgradeable for IERC20Upgradeable;
/* ========== ERRORS ========== */
error ZeroAddress();
error InvalidRatioValue();
error InsufficientOutputAmount(uint256 outputAmount, uint256 minRequired);
error InvalidRatioToAddLiquidity();
error InvalidRatioToRemoveLiquidity();
error PriceNotInRange(uint256 price);
/* ========== EVENTS ========== */
event MintSell(uint256 boostAmountIn, uint256 usdAmountOut);
event PublicMintSellFarmExecuted(uint256 liquidity, uint256 newBoostPrice);
event PublicUnfarmBuyBurnExecuted(uint256 liquidity, uint256 newBoostPrice);
/* ========= MODIFIERS ========= */
modifier validateSwap(bool boostForUsd) {
_validateSwap(boostForUsd);
_;
}
/* ========== ROLES ========== */
/// @inheritdoc IMasterAMO
bytes32 public constant override SETTER_ROLE = keccak256("SETTER_ROLE");
/// @inheritdoc IMasterAMO
bytes32 public constant override AMO_ROLE = keccak256("AMO_ROLE");
/// @inheritdoc IMasterAMO
bytes32 public constant override PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// @inheritdoc IMasterAMO
bytes32 public constant override UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE");
/// @inheritdoc IMasterAMO
bytes32 public constant override WITHDRAWER_ROLE = keccak256("WITHDRAWER_ROLE");
/* ========== VARIABLES ========== */
/// @inheritdoc IMasterAMO
address public override boost;
/// @inheritdoc IMasterAMO
address public override usd;
/// @inheritdoc IMasterAMO
address public override pool;
/// @inheritdoc IMasterAMO
uint8 public override boostDecimals;
/// @inheritdoc IMasterAMO
uint8 public override usdDecimals;
/// @inheritdoc IMasterAMO
address public override boostMinter;
/// @inheritdoc IMasterAMO
uint256 public override boostMultiplier;
/// @inheritdoc IMasterAMO
uint24 public override validRangeWidth;
/// @inheritdoc IMasterAMO
uint24 public override validRemovingRatio;
/// @inheritdoc IMasterAMO
uint256 public override boostLowerPriceSell;
/// @inheritdoc IMasterAMO
uint256 public override boostUpperPriceBuy;
/* ========== CONSTANTS ========== */
uint8 internal constant PRICE_DECIMALS = 6; // BOOST price decimals. For instance, if the actual BOOST price is 1.2$ the internal BOOST price is 1200000
uint8 internal constant PARAMS_DECIMALS = 6; // Decimals of all parameters for internal calculations
uint256 internal constant FACTOR = 10 ** PARAMS_DECIMALS; // Factor scales 1 with PARAMS_DECIMALS for internal calcs. It is the internal representation of the value 1
bool internal constant SELL_BOOST = true; // param for the validation modifier
bool internal constant BUY_BOOST = false; // param for the validation modifier
/* ========== FUNCTIONS ========== */
function initialize(
address admin, // // Address assigned the admin role (given exclusively to a multi-sig wallet)
address boost_, // The Boost stablecoin address
address usd_, // generic name for $1 collateral ( typically USDC or USDT )
address pool_, // The pool where AMO logic applies for Boost-USD pair
// On each chain where Boost is deployed, there will be a stable Boost-USD pool ensuring BOOST's peg.
// Multiple Boost-USD pools can exist across different DEXes on the same chain, each with its own AMO, maintaining independent peg guarantees.
address boostMinter_ // the minter contract
) public initializer {
__AccessControlEnumerable_init();
__Pausable_init();
// Ensure no zero addresses are passed to critical parameters
if (
admin == address(0) ||
boost_ == address(0) ||
usd_ == address(0) ||
pool_ == address(0) ||
boostMinter_ == address(0)
) revert ZeroAddress(); // zero-address error checks
_grantRole(DEFAULT_ADMIN_ROLE, admin);
boost = boost_;
usd = usd_;
pool = pool_;
boostDecimals = IERC20Metadata(boost).decimals();
usdDecimals = IERC20Metadata(usd).decimals();
boostMinter = boostMinter_;
}
////////////////////////// PAUSE ACTIONS //////////////////////////
/// @inheritdoc IMasterAMO
function pause() external override onlyRole(PAUSER_ROLE) {
_pause();
}
/// @inheritdoc IMasterAMO
function unpause() external override onlyRole(UNPAUSER_ROLE) {
_unpause();
}
////////////////////////// AMO_ROLE ACTIONS //////////////////////////
/**
* I) When Boost is over peg (in the AMOed pool)
* 1.a) Boost can minted and sold for USD — this is the mintAndSellBoost() function
* 1.b) When peg is restored, the USD 'Backing" (we received from (a) ) is paired with free-minted Boost — it is added farmed with the addLiquidity function
* II) When Boost is under peg
* 2.a) the algo
* 2.b)
* Implementation: the actions to maintain peg ( I and II ) can be triggered permissionlessly
*/
function _mintAndSellBoost(
uint256 boostAmount,
uint256 minUsdAmountOut, // boost is only sold over peg ( checked performed whatever minUsdAmountOut is inputted to this function —— this variable can sometimes be omited )
// the price check is always performed in the implementation contracts
// ( this gives more flexibility to the function actually used)
uint256 deadline
) internal virtual returns (uint256 boostAmountIn, uint256 usdAmountOut);
/// @inheritdoc IMasterAMO
function mintAndSellBoost(
uint256 boostAmount,
uint256 minUsdAmountOut,
uint256 deadline
)
external
override
onlyRole(AMO_ROLE)
whenNotPaused
nonReentrant
returns (uint256 boostAmountIn, uint256 usdAmountOut)
{
(boostAmountIn, usdAmountOut) = _mintAndSellBoost(boostAmount, minUsdAmountOut, deadline);
}
function _addLiquidity(
uint256 usdAmount,
uint256 minBoostSpend,
uint256 minUsdSpend,
uint256 deadline
) internal virtual returns (uint256 boostSpent, uint256 usdSpent, uint256 liquidity);
/// @inheritdoc IMasterAMO
function addLiquidity(
uint256 usdAmount,
uint256 minBoostSpend,
uint256 minUsdSpend,
uint256 deadline
)
external
override
onlyRole(AMO_ROLE)
whenNotPaused
nonReentrant
returns (uint256 boostSpent, uint256 usdSpent, uint256 liquidity)
{
(boostSpent, usdSpent, liquidity) = _addLiquidity(usdAmount, minBoostSpend, minUsdSpend, deadline);
}
/**
* combines mintAndSellBoost and addLiquidity
* addLiquidity only can be performed when price is close to peg (within range)
* mintAndSellBoost will be looped as long as price remains too far above peg
*/
function _mintSellFarm(
uint256 boostAmount,
uint256 minUsdAmountOut,
uint256 minBoostSpend,
uint256 minUsdSpend,
uint256 deadline
)
internal
returns (uint256 boostAmountIn, uint256 usdAmountOut, uint256 boostSpent, uint256 usdSpent, uint256 liquidity)
{
(boostAmountIn, usdAmountOut) = _mintAndSellBoost(boostAmount, minUsdAmountOut, deadline);
uint256 price = boostPrice();
if (price > FACTOR - validRangeWidth && price < FACTOR + validRangeWidth) {
uint256 usdBalance = IERC20Upgradeable(usd).balanceOf(address(this));
(boostSpent, usdSpent, liquidity) = _addLiquidity(usdBalance, minBoostSpend, minUsdSpend, deadline);
}
}
/// @inheritdoc IMasterAMO
function mintSellFarm(
uint256 boostAmount,
uint256 minUsdAmountOut,
uint256 minBoostSpend,
uint256 minUsdSpend,
uint256 deadline
)
external
override
onlyRole(AMO_ROLE)
whenNotPaused
nonReentrant
returns (uint256 boostAmountIn, uint256 usdAmountOut, uint256 boostSpent, uint256 usdSpent, uint256 liquidity)
{
(boostAmountIn, usdAmountOut, boostSpent, usdSpent, liquidity) = _mintSellFarm(
boostAmount,
minUsdAmountOut,
minBoostSpend,
minUsdSpend,
deadline
);
}
function _unfarmBuyBurn(
uint256 liquidity,
uint256 minBoostRemove,
uint256 minUsdRemove,
uint256 minBoostAmountOut,
uint256 deadline
) internal virtual returns (uint256 boostRemoved, uint256 usdRemoved, uint256 usdAmountIn, uint256 boostAmountOut);
/// @inheritdoc IMasterAMO
function unfarmBuyBurn(
uint256 liquidity,
uint256 minBoostRemove,
uint256 minUsdRemove,
uint256 minBoostAmountOut,
uint256 deadline
)
external
override
onlyRole(AMO_ROLE)
whenNotPaused
nonReentrant
returns (uint256 boostRemoved, uint256 usdRemoved, uint256 usdAmountIn, uint256 boostAmountOut)
{
(boostRemoved, usdRemoved, usdAmountIn, boostAmountOut) = _unfarmBuyBurn(
liquidity,
minBoostRemove,
minUsdRemove,
minBoostAmountOut,
deadline
);
}
////////////////////////// PUBLIC FUNCTIONS //////////////////////////
function _mintSellFarm() internal virtual returns (uint256 liquidity, uint256 newBoostPrice);
// This function handles the minting, selling, and farming of Boost when it's over the peg.
function mintSellFarm()
external
override
whenNotPaused
nonReentrant
validateSwap(SELL_BOOST)
returns (uint256 liquidity, uint256 newBoostPrice)
{
(liquidity, newBoostPrice) = _mintSellFarm(); // Perform the mint and sell, and return liquidity and the new Boost price
// Checks if the actual average price of boost when selling is greater than the boostLowerPriceSell
if (newBoostPrice < boostLowerPriceSell) revert PriceNotInRange(newBoostPrice);
emit PublicMintSellFarmExecuted(liquidity, newBoostPrice);
}
function _unfarmBuyBurn() internal virtual returns (uint256 liquidity, uint256 newBoostPrice);
// This function handles the un-farming, buying, and burning of Boost when it's under the peg.
function unfarmBuyBurn()
external
override
whenNotPaused // Ensures the contract is not paused
nonReentrant
validateSwap(BUY_BOOST)
returns (uint256 liquidity, uint256 newBoostPrice)
{
(liquidity, newBoostPrice) = _unfarmBuyBurn();
// Checks if the actual average price of boost when buying is less than the boostUpperPriceBuy
if (newBoostPrice > boostUpperPriceBuy) revert PriceNotInRange(newBoostPrice);
emit PublicUnfarmBuyBurnExecuted(liquidity, newBoostPrice);
}
////////////////////////// WITHDRAWAL FUNCTIONS //////////////////////////
/// @inheritdoc IMasterAMO
function withdrawERC20(
address token,
uint256 amount,
address recipient
) external override onlyRole(WITHDRAWER_ROLE) {
if (recipient == address(0)) revert ZeroAddress();
IERC20Upgradeable(token).safeTransfer(recipient, amount);
}
////////////////////////// INTERNAL FUNCTIONS //////////////////////////
function sortAmounts(uint256 amount0, uint256 amount1) internal view returns (uint256, uint256) {
if (boost < usd) return (amount0, amount1);
return (amount1, amount0);
}
function sortAmounts(int256 amount0, int256 amount1) internal view returns (int256, int256) {
if (boost < usd) return (amount0, amount1);
return (amount1, amount0);
}
function toBoostAmount(uint256 usdAmount) internal view returns (uint256) {
return usdAmount * 10 ** (boostDecimals - usdDecimals);
}
function toUsdAmount(uint256 boostAmount) internal view returns (uint256) {
return boostAmount / 10 ** (boostDecimals - usdDecimals);
}
function balanceOfToken(address token) internal view returns (uint256) {
return IERC20Upgradeable(token).balanceOf(address(this));
}
////////////////////////// VIEW FUNCTIONS //////////////////////////
function boostPrice() public view virtual returns (uint256 price);
function _validateSwap(bool boostForUsd) internal view virtual;
}