-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathFERC1155.sol
398 lines (358 loc) · 13.7 KB
/
FERC1155.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
396
397
398
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import {Clone} from "clones-with-immutable-args/src/Clone.sol";
import {ERC1155} from "@rari-capital/solmate/src/tokens/ERC1155.sol";
import {IFERC1155} from "./interfaces/IFERC1155.sol";
import {INFTReceiver} from "./interfaces/INFTReceiver.sol";
import "./constants/Permit.sol";
/// @title FERC1155
/// @author Fractional Art
/// @notice An ERC-1155 implementation for Fractions
contract FERC1155 is Clone, ERC1155, IFERC1155 {
/// @notice Name of the token contract
string public constant NAME = "FERC1155";
/// @notice Version number of the token contract
string public constant VERSION = "1";
/// @notice Address that can deploy new vaults for this collection, manage metadata, etc
address internal _controller;
/// @notice URI of contract metadata
string public contractURI;
/// @notice Mapping of token type approvals owner => operator => tokenId => approved
mapping(address => mapping(address => mapping(uint256 => bool)))
public isApproved;
/// @notice Mapping of metadata contracts for token ID types => metadata address
mapping(uint256 => address) public metadata;
/// @notice Mapping to track account nonces for metadata txs owner => nonces
mapping(address => uint256) public nonces;
/// @notice Mapping to track total supply for token ID types => totalSupply
mapping(uint256 => uint256) public totalSupply;
/// @notice Mapping to track royalty receivers for token ID types => royaltyAddress
mapping(uint256 => address) private royaltyAddress;
/// @notice Mapping to track the royalty percent for token ID types => royaltyPercent
mapping(uint256 => uint256) private royaltyPercent;
/// @notice Modifier for restricting function calls to the controller account
modifier onlyController() {
address controller_ = controller();
if (msg.sender != controller_)
revert InvalidSender(controller_, msg.sender);
_;
}
/// @notice Modifier for restricting function calls to the VaultRegistry
modifier onlyRegistry() {
address vaultRegistry = VAULT_REGISTRY();
if (msg.sender != vaultRegistry)
revert InvalidSender(vaultRegistry, msg.sender);
_;
}
/// @notice Burns fractions for an ID
/// @param _from Address to burn fraction tokens from
/// @param _id Token ID to burn
/// @param _amount Number of tokens to burn
function burn(
address _from,
uint256 _id,
uint256 _amount
) external onlyRegistry {
_burn(_from, _id, _amount);
totalSupply[_id] -= _amount;
}
/// @notice Hook to emit the URI update when setting the metadata or updating
/// @param _id Token ID metadata was updated for
/// @param _uri URI of metadata
function emitSetURI(uint256 _id, string memory _uri) external {
if (msg.sender != metadata[_id])
revert InvalidSender(metadata[_id], msg.sender);
emit URI(_uri, _id);
}
/// @notice Mints new fractions for an ID
/// @param _to Address to mint fraction tokens to
/// @param _id Token ID to mint
/// @param _amount Number of tokens to mint
/// @param _data Extra calldata to include in the mint
function mint(
address _to,
uint256 _id,
uint256 _amount,
bytes memory _data
) external onlyRegistry {
_mint(_to, _id, _amount, _data);
totalSupply[_id] += _amount;
}
/// @notice Permit function that approves an operator for token type with a valid signature
/// @param _owner Address of the owner of the token type
/// @param _operator Address of the spender of the token type
/// @param _id ID of the token type
/// @param _approved Approval status for the token type
/// @param _deadline Expiration of the signature
/// @param _v The recovery ID (129th byte and chain ID) of the signature used to recover the signer
/// @param _r The first 64 bytes of the signature
/// @param _s Bytes 64-128 of the signature
function permit(
address _owner,
address _operator,
uint256 _id,
bool _approved,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external {
if (block.timestamp > _deadline)
revert SignatureExpired(block.timestamp, _deadline);
// cannot realistically overflow on human timescales
unchecked {
bytes32 structHash = _computePermitStructHash(
_owner,
_operator,
_id,
_approved,
_deadline
);
bytes32 digest = _computeDigest(
_computeDomainSeparator(),
structHash
);
address signer = ecrecover(digest, _v, _r, _s);
if (signer == address(0) || signer != _owner)
revert InvalidSignature(signer, _owner);
}
isApproved[_owner][_operator][_id] = _approved;
emit SingleApproval(_owner, _operator, _id, _approved);
}
/// @notice Permit function that approves an operator for all token types with a valid signature
/// @param _owner Address of the owner of the token type
/// @param _operator Address of the spender of the token type
/// @param _approved Approval status for the token type
/// @param _deadline Expiration of the signature
/// @param _v The recovery ID (129th byte and chain ID) of the signature used to recover the signer
/// @param _r The first 64 bytes of the signature
/// @param _s Bytes 64-128 of the signature
function permitAll(
address _owner,
address _operator,
bool _approved,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external {
if (block.timestamp > _deadline)
revert SignatureExpired(block.timestamp, _deadline);
// cannot realistically overflow on human timescales
unchecked {
bytes32 structHash = _computePermitAllStructHash(
_owner,
_operator,
_approved,
_deadline
);
bytes32 digest = _computeDigest(
_computeDomainSeparator(),
structHash
);
address signer = ecrecover(digest, _v, _r, _s);
if (signer == address(0) || signer != _owner)
revert InvalidSignature(signer, _owner);
}
isApprovedForAll[_owner][_operator] = _approved;
emit ApprovalForAll(_owner, _operator, _approved);
}
/// @notice Scoped approvals allow us to eliminate some of the risks associated with setting the approval for an entire collection
/// @param _operator Address of spender account
/// @param _id ID of the token type
/// @param _approved Approval status for operator(spender) account
function setApprovalFor(
address _operator,
uint256 _id,
bool _approved
) external {
isApproved[msg.sender][_operator][_id] = _approved;
emit SingleApproval(msg.sender, _operator, _id, _approved);
}
/// @notice Sets the contract metadata
/// @param _uri URI of metadata
function setContractURI(string calldata _uri) external onlyController {
contractURI = _uri;
}
/// @notice Sets the token metadata contract
/// @param _metadata Address for metadata contract
/// @param _id Token ID to set the metadata for
function setMetadata(address _metadata, uint256 _id)
external
onlyController
{
metadata[_id] = _metadata;
emit SetMetadata(_metadata, _id);
}
/// @notice Sets the token royalties
/// @param _id Token ID royalties are being updated for
/// @param _receiver Address to receive royalties
/// @param _percentage Percentage of royalties on secondary sales
function setRoyalties(
uint256 _id,
address _receiver,
uint256 _percentage
) external onlyController {
royaltyAddress[_id] = _receiver;
royaltyPercent[_id] = _percentage;
emit SetRoyalty(_receiver, _id, _percentage);
}
/// @notice Updates the controller address for the FERC1155 token contract
/// @param _newController Address of new controlling entity
function transferController(address _newController)
external
onlyController
{
if (_newController == address(0)) revert ZeroAddress();
_controller = _newController;
emit ControllerTransferred(_newController);
}
/// @notice Sets the token royalties
/// @param _id Token ID royalties are being updated for
/// @param _salePrice Sale price to calculate the royalty for
function royaltyInfo(uint256 _id, uint256 _salePrice)
external
view
returns (address receiver, uint256 royaltyAmount)
{
receiver = royaltyAddress[_id];
royaltyAmount = (_salePrice * royaltyPercent[_id]) / 100;
}
/// @notice Transfer an amount of a token type between two accounts
/// @param _from Source address for an amount of tokens
/// @param _to Destination address for an amount of tokens
/// @param _id ID of the token type
/// @param _amount The amount of tokens being transferred
/// @param _data Additional calldata
function safeTransferFrom(
address _from,
address _to,
uint256 _id,
uint256 _amount,
bytes memory _data
) public override(ERC1155, IFERC1155) {
require(
msg.sender == _from ||
isApprovedForAll[_from][msg.sender] ||
isApproved[_from][msg.sender][_id],
"NOT_AUTHORIZED"
);
balanceOf[_from][_id] -= _amount;
balanceOf[_to][_id] += _amount;
emit TransferSingle(msg.sender, _from, _to, _id, _amount);
require(
_to.code.length == 0
? _to != address(0)
: INFTReceiver(_to).onERC1155Received(
msg.sender,
_from,
_id,
_amount,
_data
) == INFTReceiver.onERC1155Received.selector,
"UNSAFE_RECIPIENT"
);
}
/// @notice Getter for URI of a token type
/// @param _id ID of the token type
function uri(uint256 _id)
public
view
override(ERC1155, IFERC1155)
returns (string memory)
{
require(metadata[_id] != address(0), "NO METADATA");
return IFERC1155(metadata[_id]).uri(_id);
}
/// @notice Getter for controller account
function controller() public view returns (address controllerAddress) {
_controller == address(0)
? controllerAddress = INITIAL_CONTROLLER()
: controllerAddress = _controller;
}
/// @notice Getter for initial controller account immutable argument stored in calldata
function INITIAL_CONTROLLER() public pure returns (address) {
return _getArgAddress(0);
}
/// @notice VaultRegistry address that is allowed to call mint() and burn()
function VAULT_REGISTRY() public pure returns (address) {
return _getArgAddress(20);
}
/// @dev Computes hash of permit struct
/// @param _owner Address of the owner of the token type
/// @param _operator Address of the spender of the token type
/// @param _id ID of the token type
/// @param _approved Approval status for the token type
/// @param _deadline Expiration of the signature
function _computePermitStructHash(
address _owner,
address _operator,
uint256 _id,
bool _approved,
uint256 _deadline
) internal returns (bytes32) {
return
keccak256(
abi.encode(
PERMIT_TYPEHASH,
_owner,
_operator,
_id,
_approved,
nonces[_owner]++,
_deadline
)
);
}
/// @dev Computes hash of permit all struct
/// @param _owner Address of the owner of the token type
/// @param _operator Address of the spender of the token type
/// @param _approved Approval status for the token type
/// @param _deadline Expiration of the signature
function _computePermitAllStructHash(
address _owner,
address _operator,
bool _approved,
uint256 _deadline
) internal returns (bytes32) {
return
keccak256(
abi.encode(
PERMIT_ALL_TYPEHASH,
_owner,
_operator,
_approved,
nonces[_owner]++,
_deadline
)
);
}
/// @dev Computes domain separator to prevent signature collisions
/// @return Hash of the contract-specific fields
function _computeDomainSeparator() internal view returns (bytes32) {
return
keccak256(
abi.encode(
DOMAIN_TYPEHASH,
keccak256(bytes(NAME)),
keccak256(bytes(VERSION)),
block.chainid,
address(this)
)
);
}
/// @dev Computes digest of domain separator and struct hash
/// @param _domainSeparator Hash of contract-specific fields
/// @param _structHash Hash of signature fields struct
/// @return Hash of the signature digest
function _computeDigest(bytes32 _domainSeparator, bytes32 _structHash)
internal
pure
returns (bytes32)
{
return
keccak256(
abi.encodePacked("\x19\x01", _domainSeparator, _structHash)
);
}
}