-
Notifications
You must be signed in to change notification settings - Fork 40
/
Voucher.sol
293 lines (246 loc) · 8.28 KB
/
Voucher.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
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.19;
import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
import { IERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/interfaces/IERC165Upgradeable.sol";
import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { IVersioned } from "../utility/interfaces/IVersioned.sol";
import { Upgradeable } from "../utility/Upgradeable.sol";
import { Utils, InvalidIndices } from "../utility/Utils.sol";
import { MAX_GAP } from "../utility/Constants.sol";
import { IVoucher } from "./interfaces/IVoucher.sol";
contract Voucher is IVoucher, Upgradeable, ERC721Upgradeable, Utils {
using Strings for uint256;
using EnumerableSet for EnumerableSet.UintSet;
error BatchNotSupported();
// a flag used to toggle between a unique URI per token / one global URI for all tokens
bool private _useGlobalURI;
// the prefix of a dynamic URI representing a single token
string private __baseURI;
// the suffix of a dynamic URI for e.g. `.json`
string private _baseExtension;
// a mapping between an owner to its tokenIds
mapping(address => EnumerableSet.UintSet) internal _ownedTokens;
// controller address - used to mint / burn
address private _controller;
// upgrade forward-compatibility storage gap
uint256[MAX_GAP - 5] private __gap;
/**
@dev triggered when updating useGlobalURI
*/
event UseGlobalURIUpdated(bool newUseGlobalURI);
/**
* @dev triggered when updating the baseURI
*/
event BaseURIUpdated(string newBaseURI);
/**
* @dev triggered when updating the baseExtension
*/
event BaseExtensionUpdated(string newBaseExtension);
/**
* @dev used to initialize the implementation
*/
constructor() {
initialize(true, "ipfs://QmUyDUzQtwAhMB1hrYaQAqmRTbgt9sUnwq11GeqyzzSuqn", "");
}
/**
* @dev fully initializes the contract and its parents
*/
function initialize(
bool newUseGlobalURI,
string memory newBaseURI,
string memory newBaseExtension
) public initializer {
__Voucher_init(newUseGlobalURI, newBaseURI, newBaseExtension);
}
// solhint-disable func-name-mixedcase
/**
* @dev initializes the contract and its parents
*/
function __Voucher_init(
bool newUseGlobalURI,
string memory newBaseURI,
string memory newBaseExtension
) internal onlyInitializing {
__Upgradeable_init();
__ERC721_init("Carbon Automated Trading Strategy", "CARBON-STRAT");
__Voucher_init_unchained(newUseGlobalURI, newBaseURI, newBaseExtension);
}
/**
* @dev performs contract-specific initialization
*/
function __Voucher_init_unchained(
bool newUseGlobalURI,
string memory newBaseURI,
string memory newBaseExtension
) internal onlyInitializing {
_useGlobalURI = newUseGlobalURI;
__baseURI = newBaseURI;
_baseExtension = newBaseExtension;
}
/**
* @inheritdoc IERC165Upgradeable
*/
function supportsInterface(
bytes4 interfaceId
) public view override(AccessControlEnumerableUpgradeable, ERC721Upgradeable, IERC165Upgradeable) returns (bool) {
return super.supportsInterface(interfaceId);
}
// solhint-enable func-name-mixedcase
/**
* @inheritdoc Upgradeable
*/
function version() public pure override(IVersioned, Upgradeable) returns (uint16) {
return 2;
}
/**
* @inheritdoc IVoucher
*/
function controller() external view returns (address) {
return _controller;
}
/**
* @inheritdoc IVoucher
*/
function mint(address owner, uint256 tokenId) external onlyController {
_safeMint(owner, tokenId);
}
/**
* @inheritdoc IVoucher
*/
function burn(uint256 tokenId) external onlyController {
_burn(tokenId);
}
/**
* @inheritdoc IVoucher
*/
function tokensByOwner(
address owner,
uint256 startIndex,
uint256 endIndex
) external view validAddress(owner) returns (uint256[] memory) {
EnumerableSet.UintSet storage tokenIds = _ownedTokens[owner];
uint256 allLength = tokenIds.length();
// when the endIndex is 0 or out of bound, set the endIndex to the last valid value
if (endIndex == 0 || endIndex > allLength) {
endIndex = allLength;
}
// revert when startIndex is out of bound
if (startIndex > endIndex) {
revert InvalidIndices();
}
// populate the result
uint256 resultLength = endIndex - startIndex;
uint256[] memory result = new uint256[](resultLength);
for (uint256 i = 0; i < resultLength; i++) {
result[i] = tokenIds.at(startIndex + i);
}
return result;
}
/**
* @dev depending on the useGlobalURI flag, returns a unique URI point to a json representing the voucher,
* or a URI of a global json used for all tokens
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);
string memory baseURI = _baseURI();
if (_useGlobalURI) {
return baseURI;
}
if (bytes(baseURI).length > 0) {
return string(abi.encodePacked(baseURI, tokenId.toString(), _baseExtension));
}
return "";
}
/**
* @dev sets the base URI
*
* requirements:
*
* - the caller must be the admin of this contract
*/
function setBaseURI(string memory newBaseURI) public onlyAdmin {
__baseURI = newBaseURI;
emit BaseURIUpdated(newBaseURI);
}
/**
* @dev sets the base extension
*
* requirements:
*
* - the caller must be the admin of this contract
*/
function setBaseExtension(string memory newBaseExtension) public onlyAdmin {
_baseExtension = newBaseExtension;
emit BaseExtensionUpdated(newBaseExtension);
}
/**
* @dev sets the useGlobalURI flag
*
* requirements:
*
* - the caller must be the admin of this contract
*/
function useGlobalURI(bool newUseGlobalURI) public onlyAdmin {
if (_useGlobalURI == newUseGlobalURI) {
return;
}
_useGlobalURI = newUseGlobalURI;
emit UseGlobalURIUpdated(newUseGlobalURI);
}
/**
* @dev sets the controller address
*
* requirements:
*
* - the caller must be the admin of this contract
* - controller address must not be set
*/
function setController(address controllerAddress) external onlyAdmin {
if (_controller != address(0)) {
revert ControllerAlreadySet();
}
_controller = controllerAddress;
}
modifier onlyController() {
_onlyController();
_;
}
function _onlyController() private view {
if (msg.sender != _controller) {
revert OnlyController();
}
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI`, `tokenId`
*/
function _baseURI() internal view virtual override returns (string memory) {
return __baseURI;
}
/**
* @dev See {ERC721-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
if (batchSize > 1) {
revert BatchNotSupported();
}
if (from == address(0)) {
_ownedTokens[to].add(firstTokenId);
} else if (from != to) {
_ownedTokens[from].remove(firstTokenId);
}
if (to == address(0)) {
_ownedTokens[from].remove(firstTokenId);
} else if (to != from) {
_ownedTokens[to].add(firstTokenId);
}
}
}