-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathLToken.sol
252 lines (211 loc) · 8.93 KB
/
LToken.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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {Errors} from "../utils/Errors.sol";
import {Pausable} from "../utils/Pausable.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC4626} from "./utils/ERC4626.sol";
import {IRegistry} from "../interface/core/IRegistry.sol";
import {IRateModel} from "../interface/core/IRateModel.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {ILToken} from "../interface/tokens/ILToken.sol";
/**
@title Lending Token
@notice Lending token with ERC4626 implementation
*/
contract LToken is Pausable, ERC4626, ILToken {
using FixedPointMathLib for uint;
using SafeTransferLib for ERC20;
/* -------------------------------------------------------------------------- */
/* STATE VARIABLES */
/* -------------------------------------------------------------------------- */
/// @notice Utility variable to indicate if contract is initialized
bool private initialized;
/// @notice Registry
IRegistry public registry;
/// @notice Rate Model
IRateModel public rateModel;
/// @notice Account Manager
address public accountManager;
/// @notice Protocol treasury
address public treasury;
/// @notice Total amount of borrows
uint public borrows;
/// @notice Timestamp of when the state of the LToken was last updated
uint public lastUpdated;
/// @notice Protocol reserves
uint public reserves;
/// @notice Reserve factor
uint public reserveFactor;
/// @notice Total borrow shares minted
uint public totalBorrowShares;
/// @notice Mapping of account to borrow in terms of shares
mapping (address => uint) public borrowsOf;
/* -------------------------------------------------------------------------- */
/* EVENTS */
/* -------------------------------------------------------------------------- */
event ReservesRedeemed(address indexed treasury, uint amt);
/* -------------------------------------------------------------------------- */
/* CUSTOM MODIFIERS */
/* -------------------------------------------------------------------------- */
modifier accountManagerOnly() {
if (msg.sender != accountManager) revert Errors.AccountManagerOnly();
_;
}
/* -------------------------------------------------------------------------- */
/* EXTERNAL FUNCTIONS */
/* -------------------------------------------------------------------------- */
/**
@notice Contract initialization function
@dev Can only be invoked once
@param _asset Underlying ERC20 token
@param _name Name of LToken
@param _symbol Symbol of LToken
@param _registry Address of Registry
@param _reserveFactor Borrow Fee
@param _treasury Protocol treasury
*/
function init(
ERC20 _asset,
string calldata _name,
string calldata _symbol,
IRegistry _registry,
uint _reserveFactor,
address _treasury
) external {
if (initialized) revert Errors.ContractAlreadyInitialized();
if (
address(_asset) == address(0) ||
address(_registry) == address(0) ||
_treasury == address(0)
) revert Errors.ZeroAddress();
initialized = true;
initPausable(msg.sender);
initERC4626(_asset, _name, _symbol);
registry = _registry;
reserveFactor = _reserveFactor;
treasury = _treasury;
}
/**
@notice Initializes external dependencies
@param _rateModel Name of rate model contract
*/
function initDep(string calldata _rateModel) external adminOnly {
rateModel = IRateModel(registry.getAddress(_rateModel));
accountManager = registry.getAddress('ACCOUNT_MANAGER');
}
/**
@notice Lends a specified amount of underlying asset to an account
@param account Address of account
@param amt Amount of token to lend
@return isFirstBorrow Returns if the account is borrowing the asset for
the first time
*/
function lendTo(address account, uint amt)
external
whenNotPaused
accountManagerOnly
returns (bool isFirstBorrow)
{
updateState();
isFirstBorrow = (borrowsOf[account] == 0);
uint borrowShares;
require((borrowShares = convertAssetToBorrowShares(amt)) != 0, "ZERO_BORROW_SHARES");
totalBorrowShares += borrowShares;
borrowsOf[account] += borrowShares;
borrows += amt;
asset.safeTransfer(account, amt);
return isFirstBorrow;
}
/**
@notice Collects a specified amount of underlying asset from an account
@param account Address of account
@param amt Amount of token to collect
@return bool Returns true if account has no debt
*/
function collectFrom(address account, uint amt)
external
accountManagerOnly
returns (bool)
{
uint borrowShares;
require((borrowShares = convertAssetToBorrowShares(amt)) != 0, "ZERO_BORROW_SHARES");
borrowsOf[account] -= borrowShares;
totalBorrowShares -= borrowShares;
borrows -= amt;
return (borrowsOf[account] == 0);
}
/**
@notice Returns Borrow balance of given account
@param account Address of account
@return borrowBalance Amount of underlying tokens borrowed
*/
function getBorrowBalance(address account) external view returns (uint) {
return convertBorrowSharesToAsset(borrowsOf[account]);
}
function getReserves() public view returns (uint) {
return reserves + borrows.mulWadUp(getRateFactor())
.mulWadUp(reserveFactor);
}
/* -------------------------------------------------------------------------- */
/* PUBLIC FUNCTIONS */
/* -------------------------------------------------------------------------- */
/**
@notice Returns total amount of underlying assets
totalAssets = underlying balance + totalBorrows + delta
delta = totalBorrows * RateFactor
@return totalAssets Total amount of underlying assets
*/
function totalAssets() public view override returns (uint) {
return asset.balanceOf(address(this)) + getBorrows() - getReserves();
}
function getBorrows() public view returns (uint) {
return borrows + borrows.mulWadUp(getRateFactor());
}
/// @notice Updates state of the lending pool
function updateState() public {
if (lastUpdated == block.timestamp) return;
uint rateFactor = getRateFactor();
uint interestAccrued = borrows.mulWadUp(rateFactor);
borrows += interestAccrued;
reserves += interestAccrued.mulWadUp(reserveFactor);
lastUpdated = block.timestamp;
}
/* -------------------------------------------------------------------------- */
/* INTERNAL FUNCTIONS */
/* -------------------------------------------------------------------------- */
/**
@dev Rate Factor = Timestamp Delta * 1e18 (Scales timestamp delta to 18 decimals) * Interest Rate Per Block
Timestamp Delta = Number of seconds since last update
*/
function getRateFactor() internal view returns (uint) {
return (block.timestamp == lastUpdated) ?
0 :
((block.timestamp - lastUpdated)*1e18)
.mulWadUp(
rateModel.getBorrowRatePerSecond(
asset.balanceOf(address(this)),
borrows
)
);
}
function convertAssetToBorrowShares(uint amt) internal view returns (uint) {
uint256 supply = totalBorrowShares;
return supply == 0 ? amt : amt.mulDivUp(supply, getBorrows());
}
function convertBorrowSharesToAsset(uint debt) internal view returns (uint) {
uint256 supply = totalBorrowShares;
return supply == 0 ? debt : debt.mulDivDown(getBorrows(), supply);
}
function beforeDeposit(uint, uint) internal override { updateState(); }
function beforeWithdraw(uint, uint) internal override { updateState(); }
/* -------------------------------------------------------------------------- */
/* ADMIN FUNCTIONS */
/* -------------------------------------------------------------------------- */
function redeemReserves(uint amt) external adminOnly {
updateState();
reserves -= amt;
emit ReservesRedeemed(treasury, amt);
asset.safeTransfer(treasury, amt);
}
}