-
Notifications
You must be signed in to change notification settings - Fork 4
/
SemiFungibleVault.sol
287 lines (252 loc) · 8.72 KB
/
SemiFungibleVault.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import {ERC20} from "@solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol";
import {
ERC1155Supply
} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
abstract contract SemiFungibleVault is ERC1155Supply {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/*///////////////////////////////////////////////////////////////
IMMUTABLES AND STORAGE
//////////////////////////////////////////////////////////////*/
ERC20 public immutable asset;
string public name;
string public symbol;
bytes internal constant EMPTY = "";
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/** @notice Deposit into vault when event is emitted
* @param caller Address of deposit caller
* @param owner Owner of assets
* @param id Vault id
* @param assets Amount of owner assets to deposit into vault
* @param shares Amount of shares to mint for owner
*/
event Deposit(
address caller,
address indexed owner,
uint256 indexed id,
uint256 assets,
uint256 shares
);
/** @notice Withdraw from vault when event is emitted
* @param caller Address of withdraw caller
* @param receiver Address of receiver of assets
* @param owner Owner of shares
* @param id Vault id
* @param assets Amount of owner assets to withdraw from vault
* @param shares Amount of owner shares to burn
*/
event Withdraw(
address caller,
address receiver,
address indexed owner,
uint256 indexed id,
uint256 assets,
uint256 shares
);
/** @notice Contract constructor
* @param _asset ERC20 token
* @param _name Token name
* @param _symbol Token symbol
*/
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC1155("") {
asset = _asset;
name = _name;
symbol = _symbol;
}
/*///////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
/** @notice Triggers deposit into vault and mints shares for receiver
* @param id Vault id
* @param assets Amount of tokens to deposit
* @param receiver Receiver of shares
* @return shares Amount of shares minted
*/
function deposit(
uint256 id,
uint256 assets,
address receiver
) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(id, assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, id, shares, EMPTY);
emit Deposit(msg.sender, receiver, id, assets, shares);
afterDeposit(id, assets, shares);
}
/** @notice Triggers withdraw from vault and burns receivers' shares
* @param id Vault id
* @param assets Amount of tokens to withdraw
* @param receiver Receiver of assets
* @param owner Owner of shares
* @return shares Amount of shares burned
*/
function withdraw(
uint256 id,
uint256 assets,
address receiver,
address owner
) external virtual returns (uint256 shares) {
require(
msg.sender == owner || isApprovedForAll(owner, receiver),
"Only owner can withdraw, or owner has approved receiver for all"
);
shares = previewWithdraw(id, assets); // No need to check for rounding error, previewWithdraw rounds up.
beforeWithdraw(id, assets, shares);
_burn(owner, id, shares);
emit Withdraw(msg.sender, receiver, owner, id, assets, shares);
asset.safeTransfer(receiver, assets);
}
/*///////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
/**@notice Returns total assets for token
* @param _id uint256 token id of token
*/
function totalAssets(uint256 _id) public view virtual returns (uint256);
/** @notice Converts assets to shares
@param id uint256 token id of token
@param assets Total number of assets
*/
function convertToShares(uint256 id, uint256 assets)
public
view
virtual
returns (uint256)
{
uint256 supply = totalSupply(id); // Saves an extra SLOAD if totalSupply is non-zero.
return
supply == 0 ? assets : assets.mulDivDown(supply, totalAssets(id));
}
/** @notice Converts shares to assets
@param id uint256 token id of token
@param shares Total number of shares
*/
function convertToAssets(uint256 id, uint256 shares)
public
view
virtual
returns (uint256)
{
uint256 supply = totalSupply(id); // Saves an extra SLOAD if totalSupply is non-zero.
return
supply == 0 ? shares : shares.mulDivDown(totalAssets(id), supply);
}
/**
@notice Shows shares conversion output from depositing assets
@param id uint256 token id of token
@param assets Total number of assets
*/
function previewDeposit(uint256 id, uint256 assets)
public
view
virtual
returns (uint256)
{
return convertToShares(id, assets);
}
/**
@notice Shows shares conversion output from minting shares
@param id uint256 token id of token
@param shares Total number of shares
*/
function previewMint(uint256 id, uint256 shares)
public
view
virtual
returns (uint256)
{
uint256 supply = totalSupply(id); // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(id), supply);
}
/**
@notice Shows assets conversion output from withdrawing assets
@param id uint256 token id of token
@param assets Total number of assets
*/
function previewWithdraw(uint256 id, uint256 assets)
public
view
virtual
returns (uint256)
{
uint256 supply = totalSupply(id); // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets(id));
}
/**
@notice Shows assets conversion output from burning shares
@param id uint256 token id of token
@param shares Total number of shares
*/
function previewRedeem(uint256 id, uint256 shares)
public
view
virtual
returns (uint256)
{
return convertToAssets(id, shares);
}
/*///////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
/**
@notice Shows max amount of assets depositable into vault
*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
/**
@notice Shows max amount of mintable shares
*/
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
/**
@notice Shows max amount of assets withdrawable from vault
*/
function maxWithdraw(uint256 id, address owner)
public
view
virtual
returns (uint256)
{
return convertToAssets(id, balanceOf(owner, id));
}
/**
@notice Shows max amount of redeemable assets
*/
function maxRedeem(uint256 id, address owner)
public
view
virtual
returns (uint256)
{
return balanceOf(owner, id);
}
/*///////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
// solhint-disable no-empty-blocks
function beforeWithdraw(
uint256 id,
uint256 assets,
uint256 shares
) internal virtual {}
function afterDeposit(
uint256 id,
uint256 assets,
uint256 shares
) internal virtual {}
}