-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathAccountManager.sol
395 lines (359 loc) · 14.1 KB
/
AccountManager.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
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import {Errors} from "../utils/Errors.sol";
import {Helpers} from "../utils/Helpers.sol";
import {Pausable} from "../utils/Pausable.sol";
import {IERC20} from "../interface/tokens/IERC20.sol";
import {ILToken} from "../interface/tokens/ILToken.sol";
import {IAccount} from "../interface/core/IAccount.sol";
import {IRegistry} from "../interface/core/IRegistry.sol";
import {IRiskEngine} from "../interface/core/IRiskEngine.sol";
import {IAccountFactory} from "../interface/core/IAccountFactory.sol";
import {IAccountManager} from "../interface/core/IAccountManager.sol";
import {IControllerFacade} from "controller/core/IControllerFacade.sol";
/**
@title Account Manager
@notice Sentiment Account Manager,
All account interactions go via the account manager
*/
contract AccountManager is Pausable, IAccountManager {
using Helpers for address;
/* -------------------------------------------------------------------------- */
/* STATE_VARIABLES */
/* -------------------------------------------------------------------------- */
/// @notice Utility variable to indicate if contract is initialized
bool private initialized;
/// @notice Registry
IRegistry public registry;
/// @notice Risk Engine
IRiskEngine public riskEngine;
/// @notice Controller Facade
IControllerFacade public controller;
/// @notice Account Factory
IAccountFactory public accountFactory;
/// @notice List of inactive accounts per user
mapping(address => address[]) public inactiveAccountsOf;
/// @notice Mapping of collateral enabled tokens
mapping(address => bool) public isCollateralAllowed;
/* -------------------------------------------------------------------------- */
/* CUSTOM MODIFIERS */
/* -------------------------------------------------------------------------- */
modifier onlyOwner(address account) {
if (registry.ownerFor(account) != msg.sender)
revert Errors.AccountOwnerOnly();
_;
}
/* -------------------------------------------------------------------------- */
/* EXTERNAL FUNCTIONS */
/* -------------------------------------------------------------------------- */
/**
@notice Initializes contract
@dev Can only be invoked once
@param _registry Address of Registry
*/
function init(IRegistry _registry) external {
if (initialized) revert Errors.ContractAlreadyInitialized();
initialized = true;
initPausable(msg.sender);
registry = _registry;
}
/// @notice Initializes external dependencies
function initDep() external adminOnly {
riskEngine = IRiskEngine(registry.getAddress('RISK_ENGINE'));
controller = IControllerFacade(registry.getAddress('CONTROLLER'));
accountFactory =
IAccountFactory(registry.getAddress('ACCOUNT_FACTORY'));
}
/**
@notice Opens a new account for a user
@dev Creates a new account if there are no inactive accounts otherwise
reuses an already inactive account
Emits AccountAssigned(account, owner) event
@param owner Owner of the newly opened account
*/
function openAccount(address owner) external whenNotPaused {
if (owner == address(0)) revert Errors.ZeroAddress();
address account;
uint length = inactiveAccountsOf[owner].length;
if (length == 0) {
account = accountFactory.create(address(this));
IAccount(account).init(address(this));
registry.addAccount(account, owner);
} else {
account = inactiveAccountsOf[owner][length - 1];
inactiveAccountsOf[owner].pop();
registry.updateAccount(account, owner);
}
IAccount(account).activate();
emit AccountAssigned(account, owner);
}
/**
@notice Closes a specified account for a user
@dev Account can only be closed when the account has no debt
Emits AccountClosed(account, owner) event
@param _account Address of account to be closed
*/
function closeAccount(address _account) public onlyOwner(_account) {
IAccount account = IAccount(_account);
if (account.activationBlock() == block.number)
revert Errors.AccountDeactivationFailure();
if (!account.hasNoDebt()) revert Errors.OutstandingDebt();
account.deactivate();
registry.closeAccount(_account);
inactiveAccountsOf[msg.sender].push(_account);
account.sweepTo(msg.sender);
emit AccountClosed(_account, msg.sender);
}
/**
@notice Transfers Eth from owner to account
@param account Address of account
*/
function depositEth(address account)
external
payable
whenNotPaused
onlyOwner(account)
{
account.safeTransferEth(msg.value);
}
/**
@notice Transfers Eth from the account to owner
@dev Eth can only be withdrawn if the account remains healthy
after withdrawal
@param account Address of account
@param amt Amount of Eth to withdraw
*/
function withdrawEth(address account, uint amt)
external
onlyOwner(account)
{
if(!riskEngine.isWithdrawAllowed(account, address(0), amt))
revert Errors.RiskThresholdBreached();
account.withdrawEth(msg.sender, amt);
}
/**
@notice Transfers a specified amount of token from the owner
to the account
@dev Token must be accepted as collateral by the protocol
@param account Address of account
@param token Address of token
@param amt Amount of token to deposit
*/
function deposit(address account, address token, uint amt)
external
whenNotPaused
onlyOwner(account)
{
if (!isCollateralAllowed[token])
revert Errors.CollateralTypeRestricted();
if (IAccount(account).hasAsset(token) == false)
IAccount(account).addAsset(token);
token.safeTransferFrom(msg.sender, account, amt);
}
/**
@notice Transfers a specified amount of token from the account
to the owner of the account
@dev Amount of token can only be withdrawn if the account remains healthy
after withdrawal
@param account Address of account
@param token Address of token
@param amt Amount of token to withdraw
*/
function withdraw(address account, address token, uint amt)
external
onlyOwner(account)
{
if (!riskEngine.isWithdrawAllowed(account, token, amt))
revert Errors.RiskThresholdBreached();
account.withdraw(msg.sender, token, amt);
if (token.balanceOf(account) == 0)
IAccount(account).removeAsset(token);
}
/**
@notice Transfers a specified amount of token from the LP to the account
@dev Specified token must have a LP
Account must remain healthy after the borrow, otherwise tx is reverted
Emits Borrow(account, msg.sender, token, amount) event
@param account Address of account
@param token Address of token
@param amt Amount of token to borrow
*/
function borrow(address account, address token, uint amt)
external
whenNotPaused
onlyOwner(account)
{
if (registry.LTokenFor(token) == address(0))
revert Errors.LTokenUnavailable();
if (!riskEngine.isBorrowAllowed(account, token, amt))
revert Errors.RiskThresholdBreached();
if (IAccount(account).hasAsset(token) == false)
IAccount(account).addAsset(token);
if (ILToken(registry.LTokenFor(token)).lendTo(account, amt))
IAccount(account).addBorrow(token);
emit Borrow(account, msg.sender, token, amt);
}
/**
@notice Transfers a specified amount of token from the account to the LP
@dev Specified token must have a LP
Emits Repay(account, msg.sender, token, amount) event
@param account Address of account
@param token Address of token
@param amt Amount of token to borrow
*/
function repay(address account, address token, uint amt)
public
onlyOwner(account)
{
ILToken LToken = ILToken(registry.LTokenFor(token));
if (address(LToken) == address(0))
revert Errors.LTokenUnavailable();
LToken.updateState();
if (amt == type(uint256).max) amt = LToken.getBorrowBalance(account);
account.withdraw(address(LToken), token, amt);
if (LToken.collectFrom(account, amt))
IAccount(account).removeBorrow(token);
if (IERC20(token).balanceOf(account) == 0)
IAccount(account).removeAsset(token);
emit Repay(account, msg.sender, token, amt);
}
/**
@notice Liquidates an account
@dev Account can only be liquidated when it's unhealthy
Emits AccountLiquidated(account, owner) event
@param account Address of account
*/
function liquidate(address account) external {
if (riskEngine.isAccountHealthy(account))
revert Errors.AccountNotLiquidatable();
_liquidate(account);
emit AccountLiquidated(account, registry.ownerFor(account));
}
/**
@notice Gives a spender approval to spend a given amount of token from
the account
@dev Spender must have a controller in controller facade
@param account Address of account
@param token Address of token
@param spender Address of spender
@param amt Amount of token
*/
function approve(
address account,
address token,
address spender,
uint amt
)
external
onlyOwner(account)
{
if(address(controller.controllerFor(spender)) == address(0))
revert Errors.FunctionCallRestricted();
account.safeApprove(token, spender, amt);
}
/**
@notice A general function that allows the owner to perform specific interactions
with external protocols for their account
@dev Target must have a controller in controller facade
@param account Address of account
@param target Address of contract to transact with
@param amt Amount of Eth to send to the target contract
@param data Encoded sig + params of the function to transact with in the
target contract
*/
function exec(
address account,
address target,
uint amt,
bytes calldata data
)
external
onlyOwner(account)
{
bool isAllowed;
address[] memory tokensIn;
address[] memory tokensOut;
(isAllowed, tokensIn, tokensOut) =
controller.canCall(target, (amt > 0), data);
if (!isAllowed) revert Errors.FunctionCallRestricted();
_updateTokensIn(account, tokensIn);
(bool success,) = IAccount(account).exec(target, amt, data);
if (!success)
revert Errors.AccountInteractionFailure(account, target, amt, data);
_updateTokensOut(account, tokensOut);
if (!riskEngine.isAccountHealthy(account))
revert Errors.RiskThresholdBreached();
}
/**
@notice Settles an account by repaying all the loans
@param account Address of account
*/
function settle(address account) external onlyOwner(account) {
address[] memory borrows = IAccount(account).getBorrows();
for (uint i; i < borrows.length; i++) {
repay(account, borrows[i], type(uint).max);
}
}
/**
@notice Fetches inactive accounts of a user
@param user Address of user
@return address[] List of inactive accounts
*/
function getInactiveAccountsOf(
address user
)
external
view
returns (address[] memory)
{
return inactiveAccountsOf[user];
}
/* -------------------------------------------------------------------------- */
/* Internal Functions */
/* -------------------------------------------------------------------------- */
function _updateTokensIn(address account, address[] memory tokensIn)
internal
{
uint tokensInLen = tokensIn.length;
for(uint i; i < tokensInLen; ++i) {
if (IAccount(account).hasAsset(tokensIn[i]) == false)
IAccount(account).addAsset(tokensIn[i]);
}
}
function _updateTokensOut(address account, address[] memory tokensOut)
internal
{
uint tokensOutLen = tokensOut.length;
for(uint i; i < tokensOutLen; ++i) {
if (tokensOut[i].balanceOf(account) == 0)
IAccount(account).removeAsset(tokensOut[i]);
}
}
function _liquidate(address _account) internal {
IAccount account = IAccount(_account);
address[] memory accountBorrows = account.getBorrows();
uint borrowLen = accountBorrows.length;
ILToken LToken;
uint amt;
for(uint i; i < borrowLen; ++i) {
address token = accountBorrows[i];
LToken = ILToken(registry.LTokenFor(token));
LToken.updateState();
amt = LToken.getBorrowBalance(_account);
token.safeTransferFrom(msg.sender, address(LToken), amt);
LToken.collectFrom(_account, amt);
account.removeBorrow(token);
}
account.sweepTo(msg.sender);
}
/* -------------------------------------------------------------------------- */
/* ADMIN FUNCTIONS */
/* -------------------------------------------------------------------------- */
/**
@notice Toggle collateral status of a token
@param token Address of token
*/
function toggleCollateralStatus(address token) external adminOnly {
isCollateralAllowed[token] = !isCollateralAllowed[token];
}
}