-
Notifications
You must be signed in to change notification settings - Fork 261
Expand file tree
/
Copy pathPoolNFTBase.sol
More file actions
272 lines (223 loc) · 10.6 KB
/
PoolNFTBase.sol
File metadata and controls
272 lines (223 loc) · 10.6 KB
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
// SPDX-License-Identifier: AGPLv3
pragma solidity ^0.8.23;
// Notes: We use reserved slots for upgradable contracts.
// solhint-disable max-states-count
// Notes: We use these interfaces in natspec documentation below, grep @inheritdoc
// solhint-disable-next-line no-unused-import
import { IERC165, IERC721, IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import { UUPSProxiable } from "../../upgradability/UUPSProxiable.sol";
import { IGeneralDistributionAgreementV1, ISuperfluid } from "../../interfaces/superfluid/ISuperfluid.sol";
import { ISuperTokenFactory } from "../../interfaces/superfluid/ISuperTokenFactory.sol";
import { IPoolNFTBase } from "../../interfaces/agreements/gdav1/IPoolNFTBase.sol";
abstract contract PoolNFTBase is UUPSProxiable, IPoolNFTBase {
string public constant DEFAULT_BASE_URI = "https://nft.superfluid.finance/pool/v2/getmeta";
function baseURI() public pure returns (string memory) { return DEFAULT_BASE_URI; }
/// @notice Superfluid host contract address
ISuperfluid public immutable HOST;
/// @notice Superfluid GDAv1 contract address
IGeneralDistributionAgreementV1 public immutable GENERAL_DISTRIBUTION_AGREEMENT_V1;
//// Storage Variables ////
/// NOTE: The storage variables in this contract MUST NOT:
/// - change the ordering of the existing variables
/// - change any of the variable types
/// - rename any of the existing variables
/// - remove any of the existing variables
string internal _name;
string internal _symbol;
/// @notice Mapping for token approvals
/// @dev tokenID => approved address mapping
mapping(uint256 => address) internal _tokenApprovals;
/// @notice Mapping for operator approvals
mapping(address => mapping(address => bool)) internal _operatorApprovals;
/// @notice This allows us to add new storage variables in the base contract
/// without having to worry about messing up the storage layout that exists in COFNFT or CIFNFT.
/// @dev This empty reserved space is put in place to allow future versions to add new
/// variables without shifting down storage in the inheritance chain.
/// Slots 5-21 are reserved for future use.
/// We use this pattern in SuperToken.sol and favor this over the OpenZeppelin pattern
/// as this prevents silly footgunning.
/// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
uint256 internal _reserve5;
uint256 private _reserve6;
uint256 private _reserve7;
uint256 private _reserve8;
uint256 private _reserve9;
uint256 private _reserve10;
uint256 private _reserve11;
uint256 private _reserve12;
uint256 private _reserve13;
uint256 private _reserve14;
uint256 private _reserve15;
uint256 private _reserve16;
uint256 private _reserve17;
uint256 private _reserve18;
uint256 private _reserve19;
uint256 private _reserve20;
uint256 internal _reserve21;
constructor(ISuperfluid host, IGeneralDistributionAgreementV1 gdaV1) {
HOST = host;
GENERAL_DISTRIBUTION_AGREEMENT_V1 = gdaV1;
}
function initialize(string memory nftName, string memory nftSymbol)
external
override
initializer // OpenZeppelin Initializable
{
_name = nftName;
_symbol = nftSymbol;
}
function updateCode(address newAddress) external override {
ISuperTokenFactory superTokenFactory = HOST.getSuperTokenFactory();
if (msg.sender != address(superTokenFactory)) {
revert POOL_NFT_ONLY_SUPER_TOKEN_FACTORY();
}
UUPSProxiable._updateCodeAddress(newAddress);
}
/// @notice Emits the MetadataUpdate event with `tokenId` as the argument.
/// @dev Callable by anyone.
/// @param tokenId the token id to trigger a metaupdate for
function triggerMetadataUpdate(uint256 tokenId) external {
_triggerMetadataUpdate(tokenId);
}
/// @notice This contract supports IERC165, IERC721 and IERC721Metadata
/// @dev This is part of the Standard Interface Detection EIP: https://eips.ethereum.org/EIPS/eip-165
/// @param interfaceId the XOR of all function selectors in the interface
/// @return boolean true if the interface is supported
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) external pure virtual override returns (bool) {
return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
|| interfaceId == 0x80ac58cd // ERC165 Interface ID for ERC721
|| interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}
/// @inheritdoc IERC721
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _ownerOf(tokenId);
if (owner == address(0)) {
revert POOL_NFT_INVALID_TOKEN_ID();
}
return owner;
}
/// @notice Returns a hardcoded balance of 1
/// @dev We always return 1 to avoid the need for additional mapping
/// @return balance = 1
function balanceOf(
address // owner
) external pure returns (uint256 balance) {
balance = 1;
}
/// @notice Returns the name of the NFT
/// @dev Should follow the naming convention: Pool Admin NFT
/// @return name of the NFT
function name() external view virtual override returns (string memory) {
return _name;
}
/// @notice Returns the symbol of the NFT
/// @dev Should follow the naming convention: PA
/// @return symbol of the NFT
function symbol() external view virtual override returns (string memory) {
return _symbol;
}
/// @notice This returns the Uniform Resource Identifier (URI), where the metadata for the NFT lives.
/// @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
/// @return the token URI
function tokenURI(uint256 tokenId) external view virtual returns (string memory);
function _tokenURI(uint256 /*tokenId*/) internal view virtual returns (string memory) {
return string(abi.encodePacked(baseURI()));
}
/// @inheritdoc IERC721
function approve(address to, uint256 tokenId) public virtual override {
address owner = PoolNFTBase.ownerOf(tokenId);
if (to == owner) {
revert POOL_NFT_APPROVE_TO_CURRENT_OWNER();
}
if (msg.sender != owner && !isApprovedForAll(owner, msg.sender)) {
revert POOL_NFT_APPROVE_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL();
}
_approve(to, tokenId);
}
/// @inheritdoc IERC721
function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId];
}
/// @inheritdoc IERC721
function setApprovalForAll(address operator, bool approved) external virtual override {
_setApprovalForAll(msg.sender, operator, approved);
}
/// @inheritdoc IERC721
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/// @inheritdoc IERC721
function transferFrom(address from, address to, uint256 tokenId) external virtual override {
if (!_isApprovedOrOwner(msg.sender, tokenId)) {
revert POOL_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL();
}
_transfer(from, to, tokenId);
}
/// @inheritdoc IERC721
function safeTransferFrom(address from, address to, uint256 tokenId) external virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/// @inheritdoc IERC721
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
if (!_isApprovedOrOwner(msg.sender, tokenId)) {
revert POOL_NFT_TRANSFER_CALLER_NOT_OWNER_OR_APPROVED_FOR_ALL();
}
_safeTransfer(from, to, tokenId, data);
}
/// @notice Returns whether `spender` is allowed to manage `tokenId`.
/// @dev Will revert if `tokenId` doesn't exist.
/// @param spender the spender of the token
/// @param tokenId the id of the token to be spent
/// @return whether `tokenId` can be spent by `spender`
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
address owner = PoolNFTBase.ownerOf(tokenId);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
/// @notice Reverts if `tokenId` doesn't exist
/// @param tokenId the token id whose existence we are checking
function _requireMinted(uint256 tokenId) internal view {
if (!_exists(tokenId)) revert POOL_NFT_INVALID_TOKEN_ID();
}
/// @notice Returns whether `tokenId` exists
/// @dev Tokens can be managed by their owner or approved accounts via `approve` or `setApprovalForAll`.
/// Tokens start existing when they are minted (`_mint`),
/// and stop existing when they are burned (`_burn`).
/// @param tokenId the token id we're interested in seeing if exists
/// @return bool whether ot not the token exists
function _exists(uint256 tokenId) internal view returns (bool) {
return _ownerOf(tokenId) != address(0);
}
function _triggerMetadataUpdate(uint256 tokenId) internal {
emit MetadataUpdate(tokenId);
}
function _approve(address to, uint256 tokenId) internal {
_tokenApprovals[tokenId] = to;
emit Approval(_ownerOf(tokenId), to, tokenId);
}
function _setApprovalForAll(address owner, address operator, bool approved) internal {
if (owner == operator) revert POOL_NFT_APPROVE_TO_CALLER();
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/// @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist.
/// @param tokenId the token id whose existence we're checking
/// @return address the address of the owner of `tokenId`
function _ownerOf(uint256 tokenId) internal view virtual returns (address);
function _transfer(address from, address to, uint256 tokenId) internal virtual;
function _safeTransfer(
address from,
address to,
uint256 tokenId,
bytes memory // data
) internal virtual {
_transfer(from, to, tokenId);
}
/// @dev Deletes the tokenApprovals for `tokenId`
/// @param tokenId the token id whose approvals we're clearing
function _burn(uint256 tokenId) internal virtual {
// clear approvals from the previous owner
delete _tokenApprovals[tokenId];
}
}