-
Notifications
You must be signed in to change notification settings - Fork 15
/
HToken.sol
226 lines (177 loc) · 7.46 KB
/
HToken.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
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity >=0.8.4;
import "@paulrberg/contracts/access/Ownable.sol";
import "@paulrberg/contracts/token/erc20/Erc20.sol";
import "@paulrberg/contracts/token/erc20/Erc20Permit.sol";
import "@paulrberg/contracts/token/erc20/Erc20Recover.sol";
import "@paulrberg/contracts/token/erc20/SafeErc20.sol";
import "./IHToken.sol";
import "../balanceSheet/IBalanceSheetV1.sol";
/// @notice Emitted when the bond did not mature.
error HToken__BondNotMatured(uint256 maturity);
/// @notice Emitted when burning hTokens and the caller is not the BalanceSheet contract.
error HToken__BurnNotAuthorized(address caller);
/// @notice Emitted when the maturity is in the past.
error HToken__MaturityPassed(uint256 maturity);
/// @notice Emitted when minting hTokens and the caller is not the BalanceSheet contract.
error HToken__MintNotAuthorized(address caller);
/// @notice Emitted when redeeming more underlying that there is in the reserve.
error HToken__RedeemInsufficientLiquidity(uint256 underlyingAmount, uint256 totalUnderlyingReserve);
/// @notice Emitted when redeeming a zero amount of underlying.
error HToken__RedeemUnderlyingZero();
/// @notice Emitted when redeeming a zero amount of hTokens.
error HToken__RedeemZero();
/// @notice Emitted when supplying a zero amount of underlying.
error HToken__SupplyUnderlyingZero();
/// @notice Emitted when constructing the contract and the underlying has more than 18 decimals.
error HToken__UnderlyingDecimalsOverflow(uint256 decimals);
/// @notice Emitted when constructing the contract and the underlying has zero decimals.
error HToken__UnderlyingDecimalsZero();
/// @title HToken
/// @author Hifi
contract HToken is
Ownable, // one dependency
Erc20, // one dependency
Erc20Permit, // four dependencies
IHToken, // five dependencies
Erc20Recover // five dependencies
{
using SafeErc20 for IErc20;
/// PUBLIC STORAGE ///
/// @inheritdoc IHToken
IBalanceSheetV1 public override balanceSheet;
/// @inheritdoc IHToken
uint256 public override maturity;
/// @inheritdoc IHToken
uint256 public override totalUnderlyingReserve;
/// @inheritdoc IHToken
IErc20 public override underlying;
/// @inheritdoc IHToken
uint256 public override underlyingPrecisionScalar;
/// CONSTRUCTOR ///
/// @notice The hToken always has 18 decimals.
/// @param name_ Erc20 name of this token.
/// @param symbol_ Erc20 symbol of this token.
/// @param maturity_ Unix timestamp in seconds for when this token matures.
/// @param balanceSheet_ The address of the BalanceSheet contract.
/// @param underlying_ The contract address of the underlying asset.
constructor(
string memory name_,
string memory symbol_,
uint256 maturity_,
IBalanceSheetV1 balanceSheet_,
IErc20 underlying_
) Erc20Permit(name_, symbol_, 18) Ownable() {
// Set the maturity.
if (maturity_ <= block.timestamp) {
revert HToken__MaturityPassed(maturity_);
}
maturity = maturity_;
// Set the BalanceSheet contract.
balanceSheet = balanceSheet_;
// Set the underlying contract and calculate the precision scalar.
uint256 underlyingDecimals = underlying_.decimals();
if (underlyingDecimals == 0) {
revert HToken__UnderlyingDecimalsZero();
}
if (underlyingDecimals > 18) {
revert HToken__UnderlyingDecimalsOverflow(underlyingDecimals);
}
underlyingPrecisionScalar = 10**(18 - underlyingDecimals);
underlying = underlying_;
// Set the list of non-recoverable tokens.
nonRecoverableTokens.push(underlying);
isRecoverInitialized = true;
}
/// PUBLIC CONSTANT FUNCTIONS ///
/// @inheritdoc IHToken
function isMatured() public view override returns (bool) {
return block.timestamp >= maturity;
}
/// PUBLIC NON-CONSTANT FUNCTIONS ///
/// @inheritdoc IHToken
function burn(address holder, uint256 burnAmount) external override {
// Checks: the caller is the BalanceSheet.
if (msg.sender != address(balanceSheet)) {
revert HToken__BurnNotAuthorized(msg.sender);
}
// Effects: burns the hTokens.
burnInternal(holder, burnAmount);
// Emit a Burn and a Transfer event.
emit Burn(holder, burnAmount);
}
/// @inheritdoc IHToken
function mint(address beneficiary, uint256 mintAmount) external override {
// Checks: the caller is the BalanceSheet.
if (msg.sender != address(balanceSheet)) {
revert HToken__MintNotAuthorized(msg.sender);
}
// Effects: print the new hTokens into existence.
mintInternal(beneficiary, mintAmount);
// Emit a Mint event.
emit Mint(beneficiary, mintAmount);
}
/// @inheritdoc IHToken
function redeem(uint256 hTokenAmount) external override {
// Checks: before maturation.
if (!isMatured()) {
revert HToken__BondNotMatured(maturity);
}
// Checks: the zero edge case.
if (hTokenAmount == 0) {
revert HToken__RedeemZero();
}
// Denormalize the hToken amount to the underlying decimals.
uint256 underlyingAmount;
if (underlyingPrecisionScalar != 1) {
unchecked {
underlyingAmount = hTokenAmount / underlyingPrecisionScalar;
}
// Checks: the zero edge case.
if (underlyingAmount == 0) {
revert HToken__RedeemUnderlyingZero();
}
} else {
underlyingAmount = hTokenAmount;
}
// Checks: there is enough liquidity.
if (underlyingAmount > totalUnderlyingReserve) {
revert HToken__RedeemInsufficientLiquidity(underlyingAmount, totalUnderlyingReserve);
}
// Effects: decrease the remaining supply of underlying.
totalUnderlyingReserve -= underlyingAmount;
// Interactions: burn the hTokens.
burnInternal(msg.sender, hTokenAmount);
// Interactions: perform the Erc20 transfer.
underlying.safeTransfer(msg.sender, underlyingAmount);
emit Redeem(msg.sender, hTokenAmount, underlyingAmount);
}
/// @inheritdoc IHToken
function supplyUnderlying(uint256 underlyingAmount) external override {
// Checks: the zero edge case.
if (underlyingAmount == 0) {
revert HToken__SupplyUnderlyingZero();
}
// Effects: update storage.
totalUnderlyingReserve += underlyingAmount;
// Normalize the underlying amount to 18 decimals.
uint256 hTokenAmount;
if (underlyingPrecisionScalar != 1) {
hTokenAmount = underlyingAmount * underlyingPrecisionScalar;
} else {
hTokenAmount = underlyingAmount;
}
// Effects: mint the hTokens.
mintInternal(msg.sender, hTokenAmount);
// Interactions: perform the Erc20 transfer.
underlying.safeTransferFrom(msg.sender, address(this), underlyingAmount);
emit SupplyUnderlying(msg.sender, underlyingAmount, hTokenAmount);
}
/// @inheritdoc IHToken
function _setBalanceSheet(IBalanceSheetV1 newBalanceSheet) external override onlyOwner {
// Effects: update storage.
IBalanceSheetV1 oldBalanceSheet = balanceSheet;
balanceSheet = newBalanceSheet;
emit SetBalanceSheet(owner, oldBalanceSheet, newBalanceSheet);
}
}