@artblocks/contracts 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -9
- package/artifacts/contracts/AdminACLV1.sol/AdminACLV1.json +2 -2
- package/artifacts/contracts/BasicPolyptychRandomizerV0.sol/BasicPolyptychRandomizerV0.json +229 -0
- package/artifacts/contracts/BasicRandomizerV2.sol/BasicRandomizerV2.json +3 -3
- package/artifacts/contracts/GenArt721CoreV3.sol/GenArt721CoreV3.json +2 -2
- package/artifacts/contracts/GenArt721CoreV3_Engine.sol/GenArt721CoreV3_Engine.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV0.sol/MinterDAExpV0.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV1.sol/MinterDAExpV1.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV2.sol/MinterDAExpV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV3.sol/MinterDAExpV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol/MinterDAExpSettlementV0.json +2 -2
- package/artifacts/contracts/{minter-suite/Minters → archive/minter-suite/Minters/MinterDAExpSettlement}/MinterDAExpSettlementV1.sol/MinterDAExpSettlementV1.json +3 -3
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV0.sol/MinterDALinV0.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV1.sol/MinterDALinV1.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV2.sol/MinterDALinV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV3.sol/MinterDALinV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV0.sol/MinterHolderV0.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol/MinterHolderV1.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV3.sol/MinterHolderV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV0.sol/MinterMerkleV0.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol/MinterMerkleV1.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol/MinterMerkleV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV3.sol/MinterMerkleV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV4.sol/MinterMerkleV4.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV0.sol/MinterSetPriceV0.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV1.sol/MinterSetPriceV1.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV2.sol/MinterSetPriceV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV3.sol/MinterSetPriceV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V0.sol/MinterSetPriceERC20V0.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V1.sol/MinterSetPriceERC20V1.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol/MinterSetPriceERC20V2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol/MinterSetPriceERC20V3.json +2 -2
- package/artifacts/contracts/explorations/GenArt721CoreV3_Explorations.sol/GenArt721CoreV3_Explorations.json +2 -2
- package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV1.sol/IFilteredMinterDAExpSettlementV1.json +767 -0
- package/artifacts/contracts/interfaces/0.8.x/IMinterFilterV0.sol/IMinterFilterV0.json +6 -0
- package/artifacts/contracts/interfaces/0.8.x/IRandomizerPolyptychV0.sol/IRandomizerPolyptychV0.json +87 -0
- package/artifacts/contracts/interfaces/0.8.x/IRandomizerV2.sol/IRandomizerV2.json +1 -1
- package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV0.sol/MinterFilterV0.json +8 -2
- package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV1.sol/MinterFilterV1.json +34 -2
- package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol/MinterDAExpSettlementV2.json +1127 -0
- package/artifacts/contracts/minter-suite/Minters/MinterDAExpV4.sol/MinterDAExpV4.json +15 -2
- package/artifacts/contracts/minter-suite/Minters/MinterDALinV4.sol/MinterDALinV4.json +15 -2
- package/artifacts/contracts/minter-suite/Minters/MinterHolderV4.sol/MinterHolderV4.json +15 -2
- package/artifacts/contracts/minter-suite/Minters/MinterMerkleV5.sol/MinterMerkleV5.json +15 -2
- package/artifacts/contracts/minter-suite/Minters/MinterPolyptychV0.sol/IGenArt721CoreContractV3WithRandomizer.json +662 -0
- package/artifacts/contracts/minter-suite/Minters/MinterPolyptychV0.sol/MinterPolyptychV0.json +1268 -0
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceERC20V4.sol/MinterSetPriceERC20V4.json +15 -2
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceV4.sol/MinterSetPriceV4.json +15 -2
- package/artifacts/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol/GenArt721CoreV3_Engine_IncorrectCoreType.json +2 -2
- package/artifacts/contracts/mock/RandomizerV2_NoAssignMock.sol/RandomizerV2_NoAssignMock.json +3 -3
- package/contracts/BasicPolyptychRandomizerV0.sol +126 -0
- package/contracts/BasicRandomizerV2.sol +3 -3
- package/contracts/{minter-suite/Minters → archive/minter-suite/Minters/MinterDAExpSettlement}/MinterDAExpSettlementV1.sol +4 -4
- package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV1.sol +21 -0
- package/contracts/interfaces/0.8.x/IMinterFilterV0.sol +6 -0
- package/contracts/interfaces/0.8.x/IRandomizerPolyptychV0.sol +31 -0
- package/contracts/interfaces/0.8.x/IRandomizerV2.sol +5 -2
- package/contracts/minter-suite/MinterFilter/MinterFilterV1.sol +18 -0
- package/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol +1312 -0
- package/contracts/minter-suite/Minters/MinterDAExpV4.sol +32 -4
- package/contracts/minter-suite/Minters/MinterDALinV4.sol +32 -5
- package/contracts/minter-suite/Minters/MinterHolderV4.sol +35 -6
- package/contracts/minter-suite/Minters/MinterMerkleV5.sol +35 -6
- package/contracts/minter-suite/Minters/MinterPolyptychV0.sol +998 -0
- package/contracts/minter-suite/Minters/MinterSetPriceERC20V4.sol +35 -6
- package/contracts/minter-suite/Minters/MinterSetPriceV4.sol +32 -4
- package/package.json +8 -2
|
@@ -0,0 +1,1312 @@
|
|
|
1
|
+
// SPDX-License-Identifier: LGPL-3.0-only
|
|
2
|
+
// Created By: Art Blocks Inc.
|
|
3
|
+
|
|
4
|
+
import "../../interfaces/0.8.x/IGenArt721CoreContractV3_Base.sol";
|
|
5
|
+
import "../../interfaces/0.8.x/IMinterFilterV0.sol";
|
|
6
|
+
import "../../interfaces/0.8.x/IFilteredMinterDAExpSettlementV1.sol";
|
|
7
|
+
import "./MinterBase_v0_1_1.sol";
|
|
8
|
+
|
|
9
|
+
import "@openzeppelin-4.7/contracts/security/ReentrancyGuard.sol";
|
|
10
|
+
import "@openzeppelin-4.7/contracts/utils/math/SafeCast.sol";
|
|
11
|
+
|
|
12
|
+
pragma solidity 0.8.17;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @title Filtered Minter contract that allows tokens to be minted with ETH.
|
|
16
|
+
* Pricing is achieved using an automated Dutch-auction mechanism, with a
|
|
17
|
+
* settlement mechanism for tokens purchased before the auction ends.
|
|
18
|
+
* This is designed to be used with GenArt721CoreContractV3 flagship or
|
|
19
|
+
* engine contracts.
|
|
20
|
+
* @author Art Blocks Inc.
|
|
21
|
+
* @notice Privileged Roles and Ownership:
|
|
22
|
+
* This contract is designed to be managed, with limited powers.
|
|
23
|
+
* Privileged roles and abilities are controlled by the core contract's Admin
|
|
24
|
+
* ACL contract and a project's artist. Both of these roles hold extensive
|
|
25
|
+
* power and can modify minter details.
|
|
26
|
+
* Care must be taken to ensure that the admin ACL contract and artist
|
|
27
|
+
* addresses are secure behind a multi-sig or other access control mechanism.
|
|
28
|
+
* Additionally, the purchaser of a token has some trust assumptions regarding
|
|
29
|
+
* settlement, beyond typical minter Art Blocks trust assumptions. In general,
|
|
30
|
+
* Artists and Admin are trusted to not abuse their powers in a way that
|
|
31
|
+
* would artifically inflate the sellout price of a project. They are
|
|
32
|
+
* incentivized to not do so, as it would diminish their reputation and
|
|
33
|
+
* ability to sell future projects. Agreements between Admin and Artist
|
|
34
|
+
* may or may not be in place to further dissuade artificial inflation of an
|
|
35
|
+
* auction's sellout price.
|
|
36
|
+
* ----------------------------------------------------------------------------
|
|
37
|
+
* The following functions are restricted to the core contract's Admin ACL
|
|
38
|
+
* contract:
|
|
39
|
+
* - setAllowablePriceDecayHalfLifeRangeSeconds (note: this range is only
|
|
40
|
+
* enforced when creating new auctions)
|
|
41
|
+
* - resetAuctionDetails (note: this will prevent minting until a new auction
|
|
42
|
+
* is created)
|
|
43
|
+
* - adminEmergencyReduceSelloutPrice
|
|
44
|
+
* ----------------------------------------------------------------------------
|
|
45
|
+
* The following functions are restricted to a project's artist or the core
|
|
46
|
+
* contract's Admin ACL contract:
|
|
47
|
+
* - withdrawArtistAndAdminRevenues (note: this may only be called after an
|
|
48
|
+
* auction has sold out or has reached base price)
|
|
49
|
+
* ----------------------------------------------------------------------------
|
|
50
|
+
* The following functions are restricted to a project's artist:
|
|
51
|
+
* - setAuctionDetails (note: this may only be called when there is no active
|
|
52
|
+
* auction, and must start at a price less than or equal to any previously
|
|
53
|
+
* made purchases)
|
|
54
|
+
* - setProjectMaxInvocations
|
|
55
|
+
* - manuallyLimitProjectMaxInvocations
|
|
56
|
+
* ----------------------------------------------------------------------------
|
|
57
|
+
* Additional admin and artist privileged roles may be described on other
|
|
58
|
+
* contracts that this minter integrates with.
|
|
59
|
+
*
|
|
60
|
+
* @dev Note that while this minter makes use of `block.timestamp` and it is
|
|
61
|
+
* technically possible that this value is manipulated by block producers via
|
|
62
|
+
* denial of service (in PoS), such manipulation will not have material impact
|
|
63
|
+
* on the price values of this minter given the business practices for how
|
|
64
|
+
* pricing is congfigured for this minter and that variations on the order of
|
|
65
|
+
* less than a minute should not meaningfully impact price given the minimum
|
|
66
|
+
* allowable price decay rate that this minter intends to support.
|
|
67
|
+
*/
|
|
68
|
+
contract MinterDAExpSettlementV2 is
|
|
69
|
+
ReentrancyGuard,
|
|
70
|
+
MinterBase,
|
|
71
|
+
IFilteredMinterDAExpSettlementV1
|
|
72
|
+
{
|
|
73
|
+
using SafeCast for uint256;
|
|
74
|
+
|
|
75
|
+
/// Core contract address this minter interacts with
|
|
76
|
+
address public immutable genArt721CoreAddress;
|
|
77
|
+
/// The core contract integrates with V3 contracts
|
|
78
|
+
IGenArt721CoreContractV3_Base private immutable genArtCoreContract_Base;
|
|
79
|
+
|
|
80
|
+
/// Minter filter address this minter interacts with
|
|
81
|
+
address public immutable minterFilterAddress;
|
|
82
|
+
|
|
83
|
+
/// Minter filter this minter may interact with.
|
|
84
|
+
IMinterFilterV0 private immutable minterFilter;
|
|
85
|
+
|
|
86
|
+
/// minterType for this minter
|
|
87
|
+
string public constant minterType = "MinterDAExpSettlementV2";
|
|
88
|
+
|
|
89
|
+
uint256 constant ONE_MILLION = 1_000_000;
|
|
90
|
+
|
|
91
|
+
struct ProjectConfig {
|
|
92
|
+
// maxHasBeenInvoked is only true if invocations are equal to the
|
|
93
|
+
// locally limited max invocations value, `maxInvocations`. It may be
|
|
94
|
+
// stale (e.g. a different minter reachd max invocations), may return a
|
|
95
|
+
// false negative if stale, and must be accounted for in this minter's
|
|
96
|
+
// logic.
|
|
97
|
+
bool maxHasBeenInvoked;
|
|
98
|
+
// maxInvocations is the maximum number of tokens that may be minted
|
|
99
|
+
// for this project. The value here is cached on the minter, and may
|
|
100
|
+
// be out of sync with the core contract's value. It is guaranteed to
|
|
101
|
+
// be either manually populated or synced to the core contract value if
|
|
102
|
+
// an auction has been populated (i.e. no stale initial values). This
|
|
103
|
+
// behavior must be appropriately accounted for in this minter's logic.
|
|
104
|
+
uint24 maxInvocations;
|
|
105
|
+
// set to true only after artist + admin revenues have been collected
|
|
106
|
+
bool auctionRevenuesCollected;
|
|
107
|
+
// number of tokens minted that have potential of future settlement.
|
|
108
|
+
// max uint24 > 16.7 million tokens > 1 million tokens/project max
|
|
109
|
+
uint24 numSettleableInvocations;
|
|
110
|
+
// max uint64 ~= 1.8e19 sec ~= 570 billion years
|
|
111
|
+
uint64 timestampStart;
|
|
112
|
+
uint64 priceDecayHalfLifeSeconds;
|
|
113
|
+
// Prices are packed internally as uint128, resulting in a maximum
|
|
114
|
+
// allowed price of ~3.4e20 ETH. This is many orders of magnitude
|
|
115
|
+
// greater than current ETH supply.
|
|
116
|
+
uint128 startPrice;
|
|
117
|
+
// base price is non-zero for all configured auctions on this minter
|
|
118
|
+
uint128 basePrice;
|
|
119
|
+
// This value is only zero if no purchases have been made on this
|
|
120
|
+
// minter.
|
|
121
|
+
// When non-zero, this value is used as a reference when an auction is
|
|
122
|
+
// reset by admin, and then a new auction is configured by an artist.
|
|
123
|
+
// In that case, the new auction will be required to have a starting
|
|
124
|
+
// price less than or equal to this value, if one or more purchases
|
|
125
|
+
// have been made on this minter.
|
|
126
|
+
uint256 latestPurchasePrice;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
mapping(uint256 => ProjectConfig) public projectConfig;
|
|
130
|
+
|
|
131
|
+
/// Minimum price decay half life: price must decay with a half life of at
|
|
132
|
+
/// least this amount (must cut in half at least every N seconds).
|
|
133
|
+
uint256 public minimumPriceDecayHalfLifeSeconds = 300; // 5 minutes
|
|
134
|
+
/// Maximum price decay half life: price may decay with a half life of no
|
|
135
|
+
/// more than this amount (may cut in half at no more than every N seconds).
|
|
136
|
+
uint256 public maximumPriceDecayHalfLifeSeconds = 3600; // 60 minutes
|
|
137
|
+
|
|
138
|
+
struct Receipt {
|
|
139
|
+
// max uint232 allows for > 1e51 ETH (much more than max supply)
|
|
140
|
+
uint232 netPosted;
|
|
141
|
+
// max uint24 still allows for > max project supply of 1 million tokens
|
|
142
|
+
uint24 numPurchased;
|
|
143
|
+
}
|
|
144
|
+
/// user address => project ID => receipt
|
|
145
|
+
mapping(address => mapping(uint256 => Receipt)) receipts;
|
|
146
|
+
|
|
147
|
+
// modifier to restrict access to only AdminACL or the artist
|
|
148
|
+
modifier onlyCoreAdminACLOrArtist(uint256 _projectId, bytes4 _selector) {
|
|
149
|
+
require(
|
|
150
|
+
(msg.sender ==
|
|
151
|
+
genArtCoreContract_Base.projectIdToArtistAddress(_projectId)) ||
|
|
152
|
+
(
|
|
153
|
+
genArtCoreContract_Base.adminACLAllowed(
|
|
154
|
+
msg.sender,
|
|
155
|
+
address(this),
|
|
156
|
+
_selector
|
|
157
|
+
)
|
|
158
|
+
),
|
|
159
|
+
"Only Artist or Admin ACL"
|
|
160
|
+
);
|
|
161
|
+
_;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// modifier to restrict access to only AdminACL allowed calls
|
|
165
|
+
// @dev defers which ACL contract is used to the core contract
|
|
166
|
+
modifier onlyCoreAdminACL(bytes4 _selector) {
|
|
167
|
+
require(
|
|
168
|
+
genArtCoreContract_Base.adminACLAllowed(
|
|
169
|
+
msg.sender,
|
|
170
|
+
address(this),
|
|
171
|
+
_selector
|
|
172
|
+
),
|
|
173
|
+
"Only Core AdminACL allowed"
|
|
174
|
+
);
|
|
175
|
+
_;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
modifier onlyArtist(uint256 _projectId) {
|
|
179
|
+
require(
|
|
180
|
+
(msg.sender ==
|
|
181
|
+
genArtCoreContract_Base.projectIdToArtistAddress(_projectId)),
|
|
182
|
+
"Only Artist"
|
|
183
|
+
);
|
|
184
|
+
_;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @notice Initializes contract to be a Filtered Minter for
|
|
189
|
+
* `_minterFilter`, integrated with Art Blocks core contract
|
|
190
|
+
* at address `_genArt721Address`.
|
|
191
|
+
* @param _genArt721Address Art Blocks core contract address for
|
|
192
|
+
* which this contract will be a minter.
|
|
193
|
+
* @param _minterFilter Minter filter for which
|
|
194
|
+
* this will a filtered minter.
|
|
195
|
+
*/
|
|
196
|
+
constructor(
|
|
197
|
+
address _genArt721Address,
|
|
198
|
+
address _minterFilter
|
|
199
|
+
) ReentrancyGuard() MinterBase(_genArt721Address) {
|
|
200
|
+
genArt721CoreAddress = _genArt721Address;
|
|
201
|
+
// always populate immutable engine contracts, but only use appropriate
|
|
202
|
+
// interface based on isEngine in the rest of the contract
|
|
203
|
+
genArtCoreContract_Base = IGenArt721CoreContractV3_Base(
|
|
204
|
+
_genArt721Address
|
|
205
|
+
);
|
|
206
|
+
minterFilterAddress = _minterFilter;
|
|
207
|
+
minterFilter = IMinterFilterV0(_minterFilter);
|
|
208
|
+
require(
|
|
209
|
+
minterFilter.genArt721CoreAddress() == _genArt721Address,
|
|
210
|
+
"Illegal contract pairing"
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @notice This function is intentionally not implemented for this version
|
|
216
|
+
* of the minter. Due to potential for unintended consequences, the
|
|
217
|
+
* function `manuallyLimitProjectMaxInvocations` should be used to manually
|
|
218
|
+
* and explicitly limit the maximum invocations for a project to a value
|
|
219
|
+
* other than the core contract's maximum invocations for a project.
|
|
220
|
+
* @param _projectId Project ID to set the maximum invocations for.
|
|
221
|
+
* @dev This function is included for interface conformance purposes only.
|
|
222
|
+
*/
|
|
223
|
+
function setProjectMaxInvocations(
|
|
224
|
+
uint256 _projectId
|
|
225
|
+
) external view onlyArtist(_projectId) {
|
|
226
|
+
revert("Not implemented");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @notice Manually sets the local maximum invocations of project `_projectId`
|
|
231
|
+
* with the provided `_maxInvocations`, checking that `_maxInvocations` is less
|
|
232
|
+
* than or equal to the value of project `_project_id`'s maximum invocations that is
|
|
233
|
+
* set on the core contract.
|
|
234
|
+
* @dev Note that a `_maxInvocations` of 0 can only be set if the current `invocations`
|
|
235
|
+
* value is also 0 and this would also set `maxHasBeenInvoked` to true, correctly short-circuiting
|
|
236
|
+
* this minter's purchase function, avoiding extra gas costs from the core contract's maxInvocations check.
|
|
237
|
+
* @param _projectId Project ID to set the maximum invocations for.
|
|
238
|
+
* @param _maxInvocations Maximum invocations to set for the project.
|
|
239
|
+
*/
|
|
240
|
+
function manuallyLimitProjectMaxInvocations(
|
|
241
|
+
uint256 _projectId,
|
|
242
|
+
uint256 _maxInvocations
|
|
243
|
+
) external onlyArtist(_projectId) {
|
|
244
|
+
// CHECKS
|
|
245
|
+
// require that new maxInvocations is greater than 0 to prevent
|
|
246
|
+
// accidental premature closure of a project when artist is
|
|
247
|
+
// configuring, forever preventing any purchases on this minter
|
|
248
|
+
require(_maxInvocations > 0, "Only max invocations gt 0");
|
|
249
|
+
// do not allow changing maxInvocations if maxHasBeenInvoked is true
|
|
250
|
+
// @dev this is a guardrail to prevent accidental re-opening of a
|
|
251
|
+
// completed project that is waiting for revenues to be withdrawn
|
|
252
|
+
// @dev intentionally do not refresh maxHasBeenInvoked here via
|
|
253
|
+
// `_refreshMaxInvocations` because in the edge case of a stale
|
|
254
|
+
// hasMaxBeenInvoked, it is too difficult to determine what the artist
|
|
255
|
+
// may or may not want to do
|
|
256
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
257
|
+
require(
|
|
258
|
+
!_projectConfig.maxHasBeenInvoked,
|
|
259
|
+
"Max invocations already reached"
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// ensure that the manually set maxInvocations is not greater than what is set on the core contract
|
|
263
|
+
uint256 coreInvocations;
|
|
264
|
+
uint256 coreMaxInvocations;
|
|
265
|
+
(
|
|
266
|
+
coreInvocations,
|
|
267
|
+
coreMaxInvocations
|
|
268
|
+
) = _getProjectCoreInvocationsAndMaxInvocations(_projectId);
|
|
269
|
+
require(
|
|
270
|
+
_maxInvocations <= coreMaxInvocations,
|
|
271
|
+
"Cannot increase project max invocations above core contract set project max invocations"
|
|
272
|
+
);
|
|
273
|
+
require(
|
|
274
|
+
_maxInvocations >= coreInvocations,
|
|
275
|
+
"Cannot set project max invocations to less than current invocations"
|
|
276
|
+
);
|
|
277
|
+
// EFFECTS
|
|
278
|
+
// update storage with results
|
|
279
|
+
_projectConfig.maxInvocations = uint24(_maxInvocations);
|
|
280
|
+
// We need to ensure maxHasBeenInvoked is correctly set after manually setting the
|
|
281
|
+
// local maxInvocations value.
|
|
282
|
+
_projectConfig.maxHasBeenInvoked = coreInvocations == _maxInvocations;
|
|
283
|
+
|
|
284
|
+
emit ProjectMaxInvocationsLimitUpdated(_projectId, _maxInvocations);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @notice Warning: Disabling purchaseTo is not supported on this minter.
|
|
289
|
+
* This method exists purely for interface-conformance purposes.
|
|
290
|
+
*/
|
|
291
|
+
function togglePurchaseToDisabled(
|
|
292
|
+
uint256 _projectId
|
|
293
|
+
) external view onlyArtist(_projectId) {
|
|
294
|
+
revert("Action not supported");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @notice projectId => has project reached its maximum number of
|
|
299
|
+
* invocations on this minter?
|
|
300
|
+
* Note that this returns a local cached value on the minter, and may be
|
|
301
|
+
* out of sync with the core core contract's state, in which case it may
|
|
302
|
+
* return a false negative.
|
|
303
|
+
*/
|
|
304
|
+
function projectMaxHasBeenInvoked(
|
|
305
|
+
uint256 _projectId
|
|
306
|
+
) external view returns (bool) {
|
|
307
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
308
|
+
return _projectConfig.maxHasBeenInvoked;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @notice projectId => project's maximum number of invocations.
|
|
313
|
+
* Note that this returns a local cached value, and may be manually
|
|
314
|
+
* limited to be different than the core contract's maxInvocations,
|
|
315
|
+
* or may be out of sync with the core contract's maxInvocations state.
|
|
316
|
+
*/
|
|
317
|
+
function projectMaxInvocations(
|
|
318
|
+
uint256 _projectId
|
|
319
|
+
) external view returns (uint256) {
|
|
320
|
+
return projectConfig[_projectId].maxInvocations;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @notice projectId => auction parameters
|
|
325
|
+
*/
|
|
326
|
+
function projectAuctionParameters(
|
|
327
|
+
uint256 _projectId
|
|
328
|
+
)
|
|
329
|
+
external
|
|
330
|
+
view
|
|
331
|
+
returns (
|
|
332
|
+
uint256 timestampStart,
|
|
333
|
+
uint256 priceDecayHalfLifeSeconds,
|
|
334
|
+
uint256 startPrice,
|
|
335
|
+
uint256 basePrice
|
|
336
|
+
)
|
|
337
|
+
{
|
|
338
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
339
|
+
return (
|
|
340
|
+
_projectConfig.timestampStart,
|
|
341
|
+
_projectConfig.priceDecayHalfLifeSeconds,
|
|
342
|
+
_projectConfig.startPrice,
|
|
343
|
+
_projectConfig.basePrice
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @notice Sets the minimum and maximum values that are settable for
|
|
349
|
+
* `_priceDecayHalfLifeSeconds` across all projects.
|
|
350
|
+
* @param _minimumPriceDecayHalfLifeSeconds Minimum price decay half life
|
|
351
|
+
* (in seconds).
|
|
352
|
+
* @param _maximumPriceDecayHalfLifeSeconds Maximum price decay half life
|
|
353
|
+
* (in seconds).
|
|
354
|
+
*/
|
|
355
|
+
function setAllowablePriceDecayHalfLifeRangeSeconds(
|
|
356
|
+
uint256 _minimumPriceDecayHalfLifeSeconds,
|
|
357
|
+
uint256 _maximumPriceDecayHalfLifeSeconds
|
|
358
|
+
)
|
|
359
|
+
external
|
|
360
|
+
onlyCoreAdminACL(
|
|
361
|
+
this.setAllowablePriceDecayHalfLifeRangeSeconds.selector
|
|
362
|
+
)
|
|
363
|
+
{
|
|
364
|
+
require(
|
|
365
|
+
_maximumPriceDecayHalfLifeSeconds >
|
|
366
|
+
_minimumPriceDecayHalfLifeSeconds,
|
|
367
|
+
"Maximum half life must be greater than minimum"
|
|
368
|
+
);
|
|
369
|
+
require(
|
|
370
|
+
_minimumPriceDecayHalfLifeSeconds > 0,
|
|
371
|
+
"Half life of zero not allowed"
|
|
372
|
+
);
|
|
373
|
+
minimumPriceDecayHalfLifeSeconds = _minimumPriceDecayHalfLifeSeconds;
|
|
374
|
+
maximumPriceDecayHalfLifeSeconds = _maximumPriceDecayHalfLifeSeconds;
|
|
375
|
+
emit AuctionHalfLifeRangeSecondsUpdated(
|
|
376
|
+
_minimumPriceDecayHalfLifeSeconds,
|
|
377
|
+
_maximumPriceDecayHalfLifeSeconds
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
////// Auction Functions
|
|
382
|
+
/**
|
|
383
|
+
* @notice Sets auction details for project `_projectId`.
|
|
384
|
+
* @param _projectId Project ID to set auction details for.
|
|
385
|
+
* @param _auctionTimestampStart Timestamp at which to start the auction.
|
|
386
|
+
* @param _priceDecayHalfLifeSeconds The half life with which to decay the
|
|
387
|
+
* price (in seconds).
|
|
388
|
+
* @param _startPrice Price at which to start the auction, in Wei.
|
|
389
|
+
* If a previous auction existed on this minter and at least one settleable
|
|
390
|
+
* purchase has been made, this value must be less than or equal to the
|
|
391
|
+
* price when the previous auction was paused. This enforces an overall
|
|
392
|
+
* monatonically decreasing auction. Must be greater than or equal to
|
|
393
|
+
* max(uint128) for internal storage packing purposes.
|
|
394
|
+
* @param _basePrice Resting price of the auction, in Wei. Must be greater
|
|
395
|
+
* than or equal to max(uint128) for internal storage packing purposes.
|
|
396
|
+
* @dev Note that setting the auction price explicitly to `0` is
|
|
397
|
+
* intentionally not allowed. This allows the minter to use the assumption
|
|
398
|
+
* that a price of `0` indicates that the auction is not configured.
|
|
399
|
+
* @dev Note that prices must be <= max(128) for internal storage packing
|
|
400
|
+
* efficiency purposes only. This function's interface remains unchanged
|
|
401
|
+
* for interface conformance purposes.
|
|
402
|
+
* @dev Note that this function also populates the local minter max
|
|
403
|
+
* invocation values for the project. This is done to ensure that the
|
|
404
|
+
* minter's local max invocation values are guarenteed to be at least
|
|
405
|
+
* populated when an auction is configured.
|
|
406
|
+
*/
|
|
407
|
+
function setAuctionDetails(
|
|
408
|
+
uint256 _projectId,
|
|
409
|
+
uint256 _auctionTimestampStart,
|
|
410
|
+
uint256 _priceDecayHalfLifeSeconds,
|
|
411
|
+
uint256 _startPrice,
|
|
412
|
+
uint256 _basePrice
|
|
413
|
+
) external onlyArtist(_projectId) {
|
|
414
|
+
// CHECKS
|
|
415
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
416
|
+
require(
|
|
417
|
+
_projectConfig.timestampStart == 0 ||
|
|
418
|
+
block.timestamp < _projectConfig.timestampStart,
|
|
419
|
+
"No modifications mid-auction"
|
|
420
|
+
);
|
|
421
|
+
require(
|
|
422
|
+
block.timestamp < _auctionTimestampStart,
|
|
423
|
+
"Only future auctions"
|
|
424
|
+
);
|
|
425
|
+
require(
|
|
426
|
+
_startPrice > _basePrice,
|
|
427
|
+
"Auction start price must be greater than auction end price"
|
|
428
|
+
);
|
|
429
|
+
// require _basePrice is non-zero to simplify logic of this minter
|
|
430
|
+
require(_basePrice > 0, "Base price must be non-zero");
|
|
431
|
+
// If previous purchases have been made, require monotonically
|
|
432
|
+
// decreasing purchase prices to preserve settlement and revenue
|
|
433
|
+
// claiming logic. Since base price is always non-zero, if
|
|
434
|
+
// latestPurchasePrice is zero, then no previous purchases have been
|
|
435
|
+
// made, and startPrice may be set to any value.
|
|
436
|
+
require(
|
|
437
|
+
_projectConfig.latestPurchasePrice == 0 || // never purchased
|
|
438
|
+
_startPrice <= _projectConfig.latestPurchasePrice,
|
|
439
|
+
"Auction start price must be <= latest purchase price"
|
|
440
|
+
);
|
|
441
|
+
require(
|
|
442
|
+
(_priceDecayHalfLifeSeconds >= minimumPriceDecayHalfLifeSeconds) &&
|
|
443
|
+
(_priceDecayHalfLifeSeconds <=
|
|
444
|
+
maximumPriceDecayHalfLifeSeconds),
|
|
445
|
+
"Price decay half life must fall between min and max allowable values"
|
|
446
|
+
);
|
|
447
|
+
// EFFECTS
|
|
448
|
+
_projectConfig.timestampStart = _auctionTimestampStart.toUint64();
|
|
449
|
+
_projectConfig.priceDecayHalfLifeSeconds = _priceDecayHalfLifeSeconds
|
|
450
|
+
.toUint64();
|
|
451
|
+
_projectConfig.startPrice = _startPrice.toUint128();
|
|
452
|
+
_projectConfig.basePrice = _basePrice.toUint128();
|
|
453
|
+
|
|
454
|
+
emit SetAuctionDetails(
|
|
455
|
+
_projectId,
|
|
456
|
+
_auctionTimestampStart,
|
|
457
|
+
_priceDecayHalfLifeSeconds,
|
|
458
|
+
_startPrice,
|
|
459
|
+
_basePrice
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// refresh max invocations, ensuring the values are populated, and
|
|
463
|
+
// updating any local values that are illogical with respect to the
|
|
464
|
+
// current core contract state.
|
|
465
|
+
// @dev this refresh enables the guarantee that a project's max
|
|
466
|
+
// invocation state is always populated if an auction is configured.
|
|
467
|
+
_refreshMaxInvocations(_projectId);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @notice Resets auction details for project `_projectId`, zero-ing out all
|
|
472
|
+
* relevant auction fields. Not intended to be used in normal auction
|
|
473
|
+
* operation, but rather only in case of the need to reset an ongoing
|
|
474
|
+
* auction. An expected time this might occur would be when a frontend
|
|
475
|
+
* issue was occuring, and many typical users are actively being prevented
|
|
476
|
+
* from easily minting (even though minting would technically be possible
|
|
477
|
+
* directly from the contract).
|
|
478
|
+
* This function is only callable by the core admin during an active
|
|
479
|
+
* auction, before revenues have been collected.
|
|
480
|
+
* The price at the time of the reset will be the maximum starting price
|
|
481
|
+
* when re-configuring the next auction if one or more settleable purchases
|
|
482
|
+
* have been made.
|
|
483
|
+
* This is to ensure that purchases up through the block that this is
|
|
484
|
+
* called on will remain settleable, and that revenue claimed does not
|
|
485
|
+
* surpass (payments - excess_settlement_funds) for a given project.
|
|
486
|
+
* @param _projectId Project ID to set auction details for.
|
|
487
|
+
*/
|
|
488
|
+
function resetAuctionDetails(
|
|
489
|
+
uint256 _projectId
|
|
490
|
+
) external onlyCoreAdminACL(this.resetAuctionDetails.selector) {
|
|
491
|
+
// CHECKS
|
|
492
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
493
|
+
require(_projectConfig.startPrice != 0, "Auction must be configured");
|
|
494
|
+
// no reset after revenues collected, since that solidifies amount due
|
|
495
|
+
require(
|
|
496
|
+
!_projectConfig.auctionRevenuesCollected,
|
|
497
|
+
"Only before revenues collected"
|
|
498
|
+
);
|
|
499
|
+
// EFFECTS
|
|
500
|
+
// reset to initial values
|
|
501
|
+
_projectConfig.timestampStart = 0;
|
|
502
|
+
_projectConfig.priceDecayHalfLifeSeconds = 0;
|
|
503
|
+
_projectConfig.startPrice = 0;
|
|
504
|
+
_projectConfig.basePrice = 0;
|
|
505
|
+
// Since auction revenues have not been collected, we can safely assume
|
|
506
|
+
// that numSettleableInvocations is the number of purchases made on
|
|
507
|
+
// this minter. A dummy value of 0 is used for latest purchase price if
|
|
508
|
+
// no purchases have been made.
|
|
509
|
+
emit ResetAuctionDetails(
|
|
510
|
+
_projectId,
|
|
511
|
+
_projectConfig.numSettleableInvocations,
|
|
512
|
+
_projectConfig.latestPurchasePrice
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* @notice This represents an admin stepping in and reducing the sellout
|
|
518
|
+
* price of an auction. This is only callable by the core admin, only
|
|
519
|
+
* after the auction is complete, but before project revenues are
|
|
520
|
+
* withdrawn.
|
|
521
|
+
* This is only intended to be used in the case where for some reason, the
|
|
522
|
+
* sellout price was too high.
|
|
523
|
+
* @param _projectId Project ID to reduce auction sellout price for.
|
|
524
|
+
* @param _newSelloutPrice New sellout price to set for the auction. Must
|
|
525
|
+
* be less than the current sellout price.
|
|
526
|
+
*/
|
|
527
|
+
function adminEmergencyReduceSelloutPrice(
|
|
528
|
+
uint256 _projectId,
|
|
529
|
+
uint256 _newSelloutPrice
|
|
530
|
+
)
|
|
531
|
+
external
|
|
532
|
+
onlyCoreAdminACL(this.adminEmergencyReduceSelloutPrice.selector)
|
|
533
|
+
{
|
|
534
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
535
|
+
require(
|
|
536
|
+
!_projectConfig.auctionRevenuesCollected,
|
|
537
|
+
"Only before revenues collected"
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
// refresh max invocations, updating any local values that are
|
|
541
|
+
// illogical with respect to the current core contract state, and
|
|
542
|
+
// ensuring that local hasMaxBeenInvoked is accurate.
|
|
543
|
+
_refreshMaxInvocations(_projectId);
|
|
544
|
+
|
|
545
|
+
// require max invocations has been reached
|
|
546
|
+
require(_projectConfig.maxHasBeenInvoked, "Auction must be complete");
|
|
547
|
+
// @dev no need to check that auction max invocations has been reached,
|
|
548
|
+
// because if it was, the sellout price will be zero, and the following
|
|
549
|
+
// check will fail.
|
|
550
|
+
require(
|
|
551
|
+
_newSelloutPrice < _projectConfig.latestPurchasePrice,
|
|
552
|
+
"May only reduce sellout price"
|
|
553
|
+
);
|
|
554
|
+
require(
|
|
555
|
+
_newSelloutPrice >= _projectConfig.basePrice,
|
|
556
|
+
"May only reduce sellout price to base price or greater"
|
|
557
|
+
);
|
|
558
|
+
// ensure _newSelloutPrice is non-zero
|
|
559
|
+
require(_newSelloutPrice > 0, "Only sellout prices > 0");
|
|
560
|
+
_projectConfig.latestPurchasePrice = _newSelloutPrice;
|
|
561
|
+
emit SelloutPriceUpdated(_projectId, _newSelloutPrice);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* @notice This withdraws project revenues for the artist and admin.
|
|
566
|
+
* This function is only callable by the artist or admin, and only after
|
|
567
|
+
* one of the following is true:
|
|
568
|
+
* - the auction has sold out above base price
|
|
569
|
+
* - the auction has reached base price
|
|
570
|
+
* Note that revenues are not claimable if in a temporary state after
|
|
571
|
+
* an auction is reset.
|
|
572
|
+
* Revenues may only be collected a single time per project.
|
|
573
|
+
* After revenues are collected, auction parameters will never be allowed
|
|
574
|
+
* to be reset, and excess settlement funds will become immutable and fully
|
|
575
|
+
* deterministic.
|
|
576
|
+
*/
|
|
577
|
+
function withdrawArtistAndAdminRevenues(
|
|
578
|
+
uint256 _projectId
|
|
579
|
+
)
|
|
580
|
+
external
|
|
581
|
+
nonReentrant
|
|
582
|
+
onlyCoreAdminACLOrArtist(
|
|
583
|
+
_projectId,
|
|
584
|
+
this.withdrawArtistAndAdminRevenues.selector
|
|
585
|
+
)
|
|
586
|
+
{
|
|
587
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
588
|
+
// CHECKS
|
|
589
|
+
// require revenues to not have already been collected
|
|
590
|
+
require(
|
|
591
|
+
!_projectConfig.auctionRevenuesCollected,
|
|
592
|
+
"Revenues already collected"
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
// refresh max invocations, updating any local values that are
|
|
596
|
+
// illogical with respect to the current core contract state, and
|
|
597
|
+
// ensuring that local hasMaxBeenInvoked is accurate.
|
|
598
|
+
_refreshMaxInvocations(_projectId);
|
|
599
|
+
|
|
600
|
+
// get the current net price of the auction - reverts if no auction
|
|
601
|
+
// is configured.
|
|
602
|
+
// @dev we use _getPriceUnsafe here, since we just safely synced the
|
|
603
|
+
// project's max invocations and maxHasBeenInvoked, which guarantees
|
|
604
|
+
// an accurate price calculation from _getPriceUnsafe, while being
|
|
605
|
+
// more gas efficient than _getPriceSafe.
|
|
606
|
+
// @dev price is guaranteed <= _projectConfig.latestPurchasePrice,
|
|
607
|
+
// since this minter enforces monotonically decreasing purchase prices.
|
|
608
|
+
uint256 _price = _getPriceUnsafe(_projectId);
|
|
609
|
+
// if the price is not base price, require that the auction have
|
|
610
|
+
// reached max invocations. This prevents premature withdrawl
|
|
611
|
+
// before final auction price is possible to know.
|
|
612
|
+
if (_price != _projectConfig.basePrice) {
|
|
613
|
+
// @dev we can trust maxHasBeenInvoked, since we just
|
|
614
|
+
// refreshed it above with _refreshMaxInvocations, preventing any
|
|
615
|
+
// false negatives
|
|
616
|
+
require(
|
|
617
|
+
_projectConfig.maxHasBeenInvoked,
|
|
618
|
+
"Active auction not yet sold out"
|
|
619
|
+
);
|
|
620
|
+
} else {
|
|
621
|
+
uint256 basePrice = _projectConfig.basePrice;
|
|
622
|
+
// base price of zero indicates no sales, since base price of zero
|
|
623
|
+
// is not allowed when configuring an auction.
|
|
624
|
+
require(basePrice > 0, "Only latestPurchasePrice > 0");
|
|
625
|
+
// update the latest purchase price to the base price, to ensure
|
|
626
|
+
// the base price is used for all future settlement calculations
|
|
627
|
+
_projectConfig.latestPurchasePrice = basePrice;
|
|
628
|
+
}
|
|
629
|
+
// EFFECTS
|
|
630
|
+
_projectConfig.auctionRevenuesCollected = true;
|
|
631
|
+
// if the price is base price, the auction is valid and may be claimed
|
|
632
|
+
// calculate the artist and admin revenues
|
|
633
|
+
uint256 netRevenues = _projectConfig.numSettleableInvocations * _price;
|
|
634
|
+
// INTERACTIONS
|
|
635
|
+
splitRevenuesETH(_projectId, netRevenues, genArt721CoreAddress);
|
|
636
|
+
emit ArtistAndAdminRevenuesWithdrawn(_projectId);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* @notice Purchases a token from project `_projectId`.
|
|
641
|
+
* @param _projectId Project ID to mint a token on.
|
|
642
|
+
* @return tokenId Token ID of minted token
|
|
643
|
+
*/
|
|
644
|
+
function purchase(
|
|
645
|
+
uint256 _projectId
|
|
646
|
+
) external payable returns (uint256 tokenId) {
|
|
647
|
+
tokenId = purchaseTo_do6(msg.sender, _projectId);
|
|
648
|
+
return tokenId;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* @notice gas-optimized version of purchase(uint256).
|
|
653
|
+
*/
|
|
654
|
+
function purchase_H4M(
|
|
655
|
+
uint256 _projectId
|
|
656
|
+
) external payable returns (uint256 tokenId) {
|
|
657
|
+
tokenId = purchaseTo_do6(msg.sender, _projectId);
|
|
658
|
+
return tokenId;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* @notice Purchases a token from project `_projectId` and sets
|
|
663
|
+
* the token's owner to `_to`.
|
|
664
|
+
* @param _to Address to be the new token's owner.
|
|
665
|
+
* @param _projectId Project ID to mint a token on.
|
|
666
|
+
* @return tokenId Token ID of minted token
|
|
667
|
+
*/
|
|
668
|
+
function purchaseTo(
|
|
669
|
+
address _to,
|
|
670
|
+
uint256 _projectId
|
|
671
|
+
) external payable returns (uint256 tokenId) {
|
|
672
|
+
return purchaseTo_do6(_to, _projectId);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* @notice gas-optimized version of purchaseTo(address, uint256).
|
|
677
|
+
*/
|
|
678
|
+
function purchaseTo_do6(
|
|
679
|
+
address _to,
|
|
680
|
+
uint256 _projectId
|
|
681
|
+
) public payable nonReentrant returns (uint256 tokenId) {
|
|
682
|
+
// CHECKS
|
|
683
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
684
|
+
|
|
685
|
+
// Enforce the local limit of maxHasBeenInvoked, which is guaranteed
|
|
686
|
+
// to be populated for all configured auctions.
|
|
687
|
+
// @dev maxHasBeenInvoked can be sale and return a false negative.
|
|
688
|
+
// protect against that case by checking minted token's invocation
|
|
689
|
+
// against this minter's local max invocations immediately after
|
|
690
|
+
// receiving the newly minted tokenID.
|
|
691
|
+
require(
|
|
692
|
+
!_projectConfig.maxHasBeenInvoked,
|
|
693
|
+
"Maximum number of invocations reached"
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
// _getPriceUnsafe reverts if auction has not yet started or auction is
|
|
697
|
+
// unconfigured, and auction has not sold out or revenues have not been
|
|
698
|
+
// withdrawn.
|
|
699
|
+
// @dev _getPriceUnsafe is guaranteed to be accurate unless the core
|
|
700
|
+
// contract is limiting invocations and we have stale local state
|
|
701
|
+
// returning a false negative that max invocations have been reached.
|
|
702
|
+
// This is acceptable, because that case will revert this
|
|
703
|
+
// call later on in this function, when the core contract's max
|
|
704
|
+
// invocation check fails.
|
|
705
|
+
uint256 currentPriceInWei = _getPriceUnsafe(_projectId);
|
|
706
|
+
|
|
707
|
+
// EFFECTS
|
|
708
|
+
// update the purchaser's receipt and require sufficient net payment
|
|
709
|
+
Receipt storage receipt = receipts[msg.sender][_projectId];
|
|
710
|
+
|
|
711
|
+
// in memory copy + update
|
|
712
|
+
uint256 netPosted = receipt.netPosted + msg.value;
|
|
713
|
+
uint256 numPurchased = receipt.numPurchased + 1;
|
|
714
|
+
|
|
715
|
+
// require sufficient payment on project
|
|
716
|
+
require(
|
|
717
|
+
netPosted >= numPurchased * currentPriceInWei,
|
|
718
|
+
"Must send minimum value to mint"
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
// update Receipt in storage
|
|
722
|
+
// @dev overflow checks are not required since the added values cannot
|
|
723
|
+
// be enough to overflow due to maximum invocations or supply of ETH
|
|
724
|
+
receipt.netPosted = uint232(netPosted);
|
|
725
|
+
receipt.numPurchased = uint24(numPurchased);
|
|
726
|
+
|
|
727
|
+
// emit event indicating new receipt state
|
|
728
|
+
emit ReceiptUpdated(msg.sender, _projectId, numPurchased, netPosted);
|
|
729
|
+
|
|
730
|
+
// update latest purchase price (on this minter) in storage
|
|
731
|
+
// @dev this is used to enforce monotonically decreasing purchase price
|
|
732
|
+
// across multiple auctions
|
|
733
|
+
_projectConfig.latestPurchasePrice = currentPriceInWei;
|
|
734
|
+
|
|
735
|
+
tokenId = minterFilter.mint(_to, _projectId, msg.sender);
|
|
736
|
+
|
|
737
|
+
// invocation is token number plus one, and will never overflow due to
|
|
738
|
+
// limit of 1e6 invocations per project. block scope for gas efficiency
|
|
739
|
+
// (i.e. avoid an unnecessary var initialization to 0).
|
|
740
|
+
unchecked {
|
|
741
|
+
uint256 tokenInvocation = (tokenId % ONE_MILLION) + 1;
|
|
742
|
+
uint256 localMaxInvocations = _projectConfig.maxInvocations;
|
|
743
|
+
// handle the case where the token invocation == minter local max
|
|
744
|
+
// invocations occurred on a different minter, and we have a stale
|
|
745
|
+
// local maxHasBeenInvoked value returning a false negative.
|
|
746
|
+
// @dev this is a CHECK after EFFECTS, so security was considered
|
|
747
|
+
// in detail here.
|
|
748
|
+
require(
|
|
749
|
+
tokenInvocation <= localMaxInvocations,
|
|
750
|
+
"Maximum number of invocations reached"
|
|
751
|
+
);
|
|
752
|
+
// in typical case, update the local maxHasBeenInvoked value
|
|
753
|
+
// to true if the token invocation == minter local max invocations
|
|
754
|
+
// (enables gas efficient reverts after sellout)
|
|
755
|
+
if (tokenInvocation == localMaxInvocations) {
|
|
756
|
+
_projectConfig.maxHasBeenInvoked = true;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// INTERACTIONS
|
|
761
|
+
if (_projectConfig.auctionRevenuesCollected) {
|
|
762
|
+
// if revenues have been collected, split funds immediately.
|
|
763
|
+
// @dev note that we are guaranteed to be at auction base price,
|
|
764
|
+
// since we know we didn't sellout prior to this tx.
|
|
765
|
+
// note that we don't refund msg.sender here, since a separate
|
|
766
|
+
// settlement mechanism is provided on this minter, unrelated to
|
|
767
|
+
// msg.value
|
|
768
|
+
splitRevenuesETH(
|
|
769
|
+
_projectId,
|
|
770
|
+
currentPriceInWei,
|
|
771
|
+
genArt721CoreAddress
|
|
772
|
+
);
|
|
773
|
+
} else {
|
|
774
|
+
// increment the number of settleable invocations that will be
|
|
775
|
+
// claimable by the artist and admin once auction is validated.
|
|
776
|
+
// do not split revenue here since will be claimed at a later time.
|
|
777
|
+
_projectConfig.numSettleableInvocations++;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return tokenId;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* @notice Reclaims the sender's payment above current settled price for
|
|
785
|
+
* project `_projectId`. The current settled price is the the price paid
|
|
786
|
+
* for the most recently purchased token, or the base price if the artist
|
|
787
|
+
* has withdrawn revenues after the auction reached base price.
|
|
788
|
+
* This function is callable at any point, but is expected to typically be
|
|
789
|
+
* called after auction has sold out above base price or after the auction
|
|
790
|
+
* has been purchased at base price. This minimizes the amount of gas
|
|
791
|
+
* required to send all excess settlement funds to the sender.
|
|
792
|
+
* Sends excess settlement funds to msg.sender.
|
|
793
|
+
* @param _projectId Project ID to reclaim excess settlement funds on.
|
|
794
|
+
*/
|
|
795
|
+
function reclaimProjectExcessSettlementFunds(uint256 _projectId) external {
|
|
796
|
+
reclaimProjectExcessSettlementFundsTo(payable(msg.sender), _projectId);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* @notice Reclaims the sender's payment above current settled price for
|
|
801
|
+
* project `_projectId`. The current settled price is the the price paid
|
|
802
|
+
* for the most recently purchased token, or the base price if the artist
|
|
803
|
+
* has withdrawn revenues after the auction reached base price.
|
|
804
|
+
* This function is callable at any point, but is expected to typically be
|
|
805
|
+
* called after auction has sold out above base price or after the auction
|
|
806
|
+
* has been purchased at base price. This minimizes the amount of gas
|
|
807
|
+
* required to send all excess settlement funds.
|
|
808
|
+
* Sends excess settlement funds to address `_to`.
|
|
809
|
+
* @param _to Address to send excess settlement funds to.
|
|
810
|
+
* @param _projectId Project ID to reclaim excess settlement funds on.
|
|
811
|
+
*/
|
|
812
|
+
function reclaimProjectExcessSettlementFundsTo(
|
|
813
|
+
address payable _to,
|
|
814
|
+
uint256 _projectId
|
|
815
|
+
) public nonReentrant {
|
|
816
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
817
|
+
Receipt storage receipt = receipts[msg.sender][_projectId];
|
|
818
|
+
uint256 numPurchased = receipt.numPurchased;
|
|
819
|
+
// CHECKS
|
|
820
|
+
// input validation
|
|
821
|
+
require(_to != address(0), "No claiming to the zero address");
|
|
822
|
+
// require that a user has purchased at least one token on this project
|
|
823
|
+
require(numPurchased > 0, "No purchases made by this address");
|
|
824
|
+
// get the latestPurchasePrice, which returns the sellout price if the
|
|
825
|
+
// auction sold out before reaching base price, or returns the base
|
|
826
|
+
// price if auction has reached base price and artist has withdrawn
|
|
827
|
+
// revenues.
|
|
828
|
+
// @dev if user is eligible for a reclaiming, they have purchased a
|
|
829
|
+
// token, therefore we are guaranteed to have a populated
|
|
830
|
+
// latestPurchasePrice
|
|
831
|
+
uint256 currentSettledTokenPrice = _projectConfig.latestPurchasePrice;
|
|
832
|
+
|
|
833
|
+
// EFFECTS
|
|
834
|
+
// calculate the excess settlement funds amount
|
|
835
|
+
// implicit overflow/underflow checks in solidity ^0.8
|
|
836
|
+
uint256 requiredAmountPosted = numPurchased * currentSettledTokenPrice;
|
|
837
|
+
uint256 excessSettlementFunds = receipt.netPosted -
|
|
838
|
+
requiredAmountPosted;
|
|
839
|
+
// update Receipt in storage
|
|
840
|
+
receipt.netPosted = requiredAmountPosted.toUint232();
|
|
841
|
+
// emit event indicating new receipt state
|
|
842
|
+
emit ReceiptUpdated(
|
|
843
|
+
msg.sender,
|
|
844
|
+
_projectId,
|
|
845
|
+
numPurchased,
|
|
846
|
+
requiredAmountPosted
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
// INTERACTIONS
|
|
850
|
+
bool success_;
|
|
851
|
+
(success_, ) = _to.call{value: excessSettlementFunds}("");
|
|
852
|
+
require(success_, "Reclaiming failed");
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* @notice Reclaims the sender's payment above current settled price for
|
|
857
|
+
* projects in `_projectIds`. The current settled price is the the price
|
|
858
|
+
* paid for the most recently purchased token, or the base price if the
|
|
859
|
+
* artist has withdrawn revenues after the auction reached base price.
|
|
860
|
+
* This function is callable at any point, but is expected to typically be
|
|
861
|
+
* called after auction has sold out above base price or after the auction
|
|
862
|
+
* has been purchased at base price. This minimizes the amount of gas
|
|
863
|
+
* required to send all excess settlement funds to the sender.
|
|
864
|
+
* Sends total of all excess settlement funds to msg.sender in a single
|
|
865
|
+
* chunk. Entire transaction reverts if any excess settlement calculation
|
|
866
|
+
* fails.
|
|
867
|
+
* @param _projectIds Array of project IDs to reclaim excess settlement
|
|
868
|
+
* funds on.
|
|
869
|
+
*/
|
|
870
|
+
function reclaimProjectsExcessSettlementFunds(
|
|
871
|
+
uint256[] calldata _projectIds
|
|
872
|
+
) external {
|
|
873
|
+
reclaimProjectsExcessSettlementFundsTo(
|
|
874
|
+
payable(msg.sender),
|
|
875
|
+
_projectIds
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* @notice Reclaims the sender's payment above current settled price for
|
|
881
|
+
* projects in `_projectIds`. The current settled price is the the price
|
|
882
|
+
* paid for the most recently purchased token, or the base price if the
|
|
883
|
+
* artist has withdrawn revenues after the auction reached base price.
|
|
884
|
+
* This function is callable at any point, but is expected to typically be
|
|
885
|
+
* called after auction has sold out above base price or after the auction
|
|
886
|
+
* has been purchased at base price. This minimizes the amount of gas
|
|
887
|
+
* required to send all excess settlement funds to the sender.
|
|
888
|
+
* Sends total of all excess settlement funds to `_to` in a single
|
|
889
|
+
* chunk. Entire transaction reverts if any excess settlement calculation
|
|
890
|
+
* fails.
|
|
891
|
+
* @param _to Address to send excess settlement funds to.
|
|
892
|
+
* @param _projectIds Array of project IDs to reclaim excess settlement
|
|
893
|
+
* funds on.
|
|
894
|
+
*/
|
|
895
|
+
function reclaimProjectsExcessSettlementFundsTo(
|
|
896
|
+
address payable _to,
|
|
897
|
+
uint256[] memory _projectIds
|
|
898
|
+
) public nonReentrant {
|
|
899
|
+
// CHECKS
|
|
900
|
+
// input validation
|
|
901
|
+
require(_to != address(0), "No claiming to the zero address");
|
|
902
|
+
// EFFECTS
|
|
903
|
+
// for each project, tally up the excess settlement funds and update
|
|
904
|
+
// the receipt in storage
|
|
905
|
+
uint256 excessSettlementFunds;
|
|
906
|
+
uint256 projectIdsLength = _projectIds.length;
|
|
907
|
+
for (uint256 i; i < projectIdsLength; ) {
|
|
908
|
+
uint256 projectId = _projectIds[i];
|
|
909
|
+
ProjectConfig storage _projectConfig = projectConfig[projectId];
|
|
910
|
+
Receipt storage receipt = receipts[msg.sender][projectId];
|
|
911
|
+
uint256 numPurchased = receipt.numPurchased;
|
|
912
|
+
// input validation
|
|
913
|
+
// require that a user has purchased at least one token on this project
|
|
914
|
+
require(numPurchased > 0, "No purchases made by this address");
|
|
915
|
+
// get the latestPurchasePrice, which returns the sellout price if the
|
|
916
|
+
// auction sold out before reaching base price, or returns the base
|
|
917
|
+
// price if auction has reached base price and artist has withdrawn
|
|
918
|
+
// revenues.
|
|
919
|
+
// @dev if user is eligible for a claim, they have purchased a token,
|
|
920
|
+
// therefore we are guaranteed to have a populated
|
|
921
|
+
// latestPurchasePrice
|
|
922
|
+
uint256 currentSettledTokenPrice = _projectConfig
|
|
923
|
+
.latestPurchasePrice;
|
|
924
|
+
// calculate the excessSettlementFunds amount
|
|
925
|
+
// implicit overflow/underflow checks in solidity ^0.8
|
|
926
|
+
uint256 requiredAmountPosted = numPurchased *
|
|
927
|
+
currentSettledTokenPrice;
|
|
928
|
+
excessSettlementFunds += (receipt.netPosted - requiredAmountPosted);
|
|
929
|
+
// reduce the netPosted (in storage) to value after excess settlement
|
|
930
|
+
// funds deducted
|
|
931
|
+
receipt.netPosted = requiredAmountPosted.toUint232();
|
|
932
|
+
// emit event indicating new receipt state
|
|
933
|
+
emit ReceiptUpdated(
|
|
934
|
+
msg.sender,
|
|
935
|
+
projectId,
|
|
936
|
+
numPurchased,
|
|
937
|
+
requiredAmountPosted
|
|
938
|
+
);
|
|
939
|
+
// gas efficiently increment i
|
|
940
|
+
// won't overflow due to for loop, as well as gas limts
|
|
941
|
+
unchecked {
|
|
942
|
+
++i;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// INTERACTIONS
|
|
947
|
+
// send excess settlement funds in a single chunk for all
|
|
948
|
+
// projects
|
|
949
|
+
bool success_;
|
|
950
|
+
(success_, ) = _to.call{value: excessSettlementFunds}("");
|
|
951
|
+
require(success_, "Reclaiming failed");
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* @notice Gets price of minting a token on project `_projectId` given
|
|
956
|
+
* the project's AuctionParameters and current block timestamp.
|
|
957
|
+
* Reverts if auction has not yet started or auction is unconfigured, and
|
|
958
|
+
* auction has not sold out or revenues have not been withdrawn.
|
|
959
|
+
* Price is guaranteed to be accurate, regardless of the current state of
|
|
960
|
+
* the locally cached minter max invocations.
|
|
961
|
+
* @dev This method is less gas efficient than `_getPriceUnsafe`, but is
|
|
962
|
+
* guaranteed to be accurate.
|
|
963
|
+
* @param _projectId Project ID to get price of token for.
|
|
964
|
+
* @return tokenPriceInWei current price of token in Wei
|
|
965
|
+
* @dev This method calculates price decay using a linear interpolation
|
|
966
|
+
* of exponential decay based on the artist-provided half-life for price
|
|
967
|
+
* decay, `_priceDecayHalfLifeSeconds`.
|
|
968
|
+
*/
|
|
969
|
+
function _getPriceSafe(
|
|
970
|
+
uint256 _projectId
|
|
971
|
+
) private view returns (uint256 tokenPriceInWei) {
|
|
972
|
+
// accurately check if project has sold out
|
|
973
|
+
if (_projectMaxHasBeenInvokedSafe(_projectId)) {
|
|
974
|
+
// max invocations have been reached, return the latest purchased
|
|
975
|
+
// price
|
|
976
|
+
tokenPriceInWei = projectConfig[_projectId].latestPurchasePrice;
|
|
977
|
+
} else {
|
|
978
|
+
// if not sold out, return the current price
|
|
979
|
+
tokenPriceInWei = _getPriceUnsafe(_projectId);
|
|
980
|
+
}
|
|
981
|
+
return tokenPriceInWei;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* @notice Gets price of minting a token on project `_projectId` given
|
|
986
|
+
* the project's AuctionParameters and current block timestamp.
|
|
987
|
+
* Reverts if auction has not yet started or auction is unconfigured, and
|
|
988
|
+
* local hasMaxBeenInvoked is false and revenues have not been withdrawn.
|
|
989
|
+
* Price is guaranteed to be accurate unless the minter's local
|
|
990
|
+
* hasMaxBeenInvoked is stale and returning a false negative.
|
|
991
|
+
* @dev when an accurate price is required regardless of the current state
|
|
992
|
+
* state of the locally cached minter max invocations, use the less gas
|
|
993
|
+
* efficient function `_getPriceSafe`.
|
|
994
|
+
* @param _projectId Project ID to get price of token for.
|
|
995
|
+
* @return uint256 current price of token in Wei, accurate if minter max
|
|
996
|
+
* invocations are up to date
|
|
997
|
+
* @dev This method calculates price decay using a linear interpolation
|
|
998
|
+
* of exponential decay based on the artist-provided half-life for price
|
|
999
|
+
* decay, `_priceDecayHalfLifeSeconds`.
|
|
1000
|
+
*/
|
|
1001
|
+
function _getPriceUnsafe(
|
|
1002
|
+
uint256 _projectId
|
|
1003
|
+
) private view returns (uint256) {
|
|
1004
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
1005
|
+
// return latest purchase price if:
|
|
1006
|
+
// - minter is aware of a sold-out auction (without updating max
|
|
1007
|
+
// invocation value)
|
|
1008
|
+
// - auction revenues have been collected, at which point the
|
|
1009
|
+
// latest purchase price will never change again
|
|
1010
|
+
if (
|
|
1011
|
+
_projectConfig.maxHasBeenInvoked ||
|
|
1012
|
+
_projectConfig.auctionRevenuesCollected
|
|
1013
|
+
) {
|
|
1014
|
+
return _projectConfig.latestPurchasePrice;
|
|
1015
|
+
}
|
|
1016
|
+
// otherwise calculate price based on current block timestamp and
|
|
1017
|
+
// auction configuration (will revert if auction has not started)
|
|
1018
|
+
// move parameters to memory if used more than once
|
|
1019
|
+
uint256 _timestampStart = uint256(_projectConfig.timestampStart);
|
|
1020
|
+
uint256 _priceDecayHalfLifeSeconds = uint256(
|
|
1021
|
+
_projectConfig.priceDecayHalfLifeSeconds
|
|
1022
|
+
);
|
|
1023
|
+
uint256 _basePrice = _projectConfig.basePrice;
|
|
1024
|
+
|
|
1025
|
+
require(block.timestamp > _timestampStart, "Auction not yet started");
|
|
1026
|
+
require(_priceDecayHalfLifeSeconds > 0, "Only configured auctions");
|
|
1027
|
+
uint256 decayedPrice = _projectConfig.startPrice;
|
|
1028
|
+
uint256 elapsedTimeSeconds;
|
|
1029
|
+
unchecked {
|
|
1030
|
+
// already checked that block.timestamp > _timestampStart above
|
|
1031
|
+
elapsedTimeSeconds = block.timestamp - _timestampStart;
|
|
1032
|
+
}
|
|
1033
|
+
// Divide by two (via bit-shifting) for the number of entirely completed
|
|
1034
|
+
// half-lives that have elapsed since auction start time.
|
|
1035
|
+
unchecked {
|
|
1036
|
+
// already required _priceDecayHalfLifeSeconds > 0
|
|
1037
|
+
decayedPrice >>= elapsedTimeSeconds / _priceDecayHalfLifeSeconds;
|
|
1038
|
+
}
|
|
1039
|
+
// Perform a linear interpolation between partial half-life points, to
|
|
1040
|
+
// approximate the current place on a perfect exponential decay curve.
|
|
1041
|
+
unchecked {
|
|
1042
|
+
// value of expression is provably always less than decayedPrice,
|
|
1043
|
+
// so no underflow is possible when the subtraction assignment
|
|
1044
|
+
// operator is used on decayedPrice.
|
|
1045
|
+
decayedPrice -=
|
|
1046
|
+
(decayedPrice *
|
|
1047
|
+
(elapsedTimeSeconds % _priceDecayHalfLifeSeconds)) /
|
|
1048
|
+
_priceDecayHalfLifeSeconds /
|
|
1049
|
+
2;
|
|
1050
|
+
}
|
|
1051
|
+
if (decayedPrice < _basePrice) {
|
|
1052
|
+
// Price may not decay below stay `basePrice`.
|
|
1053
|
+
return _basePrice;
|
|
1054
|
+
}
|
|
1055
|
+
return decayedPrice;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* @notice Gets the current excess settlement funds on project `_projectId`
|
|
1060
|
+
* for address `_walletAddress`. The returned value is expected to change
|
|
1061
|
+
* throughtout an auction, since the latest purchase price is used when
|
|
1062
|
+
* determining excess settlement funds.
|
|
1063
|
+
* A user may claim excess settlement funds by calling the function
|
|
1064
|
+
* `reclaimProjectExcessSettlementFunds(_projectId)`.
|
|
1065
|
+
* @param _projectId Project ID to query.
|
|
1066
|
+
* @param _walletAddress Account address for which the excess posted funds
|
|
1067
|
+
* is being queried.
|
|
1068
|
+
* @return excessSettlementFundsInWei Amount of excess settlement funds, in
|
|
1069
|
+
* wei
|
|
1070
|
+
*/
|
|
1071
|
+
function getProjectExcessSettlementFunds(
|
|
1072
|
+
uint256 _projectId,
|
|
1073
|
+
address _walletAddress
|
|
1074
|
+
) external view returns (uint256 excessSettlementFundsInWei) {
|
|
1075
|
+
// input validation
|
|
1076
|
+
require(_walletAddress != address(0), "No zero address");
|
|
1077
|
+
// load struct from storage
|
|
1078
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
1079
|
+
Receipt storage receipt = receipts[_walletAddress][_projectId];
|
|
1080
|
+
// require that a user has purchased at least one token on this project
|
|
1081
|
+
require(receipt.numPurchased > 0, "No purchases made by this address");
|
|
1082
|
+
// get the latestPurchasePrice, which returns the sellout price if the
|
|
1083
|
+
// auction sold out before reaching base price, or returns the base
|
|
1084
|
+
// price if auction has reached base price and artist has withdrawn
|
|
1085
|
+
// revenues.
|
|
1086
|
+
// @dev if user is eligible for a reclaiming, they have purchased a
|
|
1087
|
+
// token, therefore we are guaranteed to have a populated
|
|
1088
|
+
// latestPurchasePrice
|
|
1089
|
+
uint256 currentSettledTokenPrice = _projectConfig.latestPurchasePrice;
|
|
1090
|
+
|
|
1091
|
+
// EFFECTS
|
|
1092
|
+
// calculate the excess settlement funds amount and return
|
|
1093
|
+
// implicit overflow/underflow checks in solidity ^0.8
|
|
1094
|
+
uint256 requiredAmountPosted = receipt.numPurchased *
|
|
1095
|
+
currentSettledTokenPrice;
|
|
1096
|
+
excessSettlementFundsInWei = receipt.netPosted - requiredAmountPosted;
|
|
1097
|
+
return excessSettlementFundsInWei;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* @notice Gets the latest purchase price for project `_projectId`, or 0 if
|
|
1102
|
+
* no purchases have been made.
|
|
1103
|
+
*/
|
|
1104
|
+
function getProjectLatestPurchasePrice(
|
|
1105
|
+
uint256 _projectId
|
|
1106
|
+
) external view returns (uint256 latestPurchasePrice) {
|
|
1107
|
+
return projectConfig[_projectId].latestPurchasePrice;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* @notice Gets the number of settleable invocations for project `_projectId`.
|
|
1112
|
+
*/
|
|
1113
|
+
function getNumSettleableInvocations(
|
|
1114
|
+
uint256 _projectId
|
|
1115
|
+
) external view returns (uint256 numSettleableInvocations) {
|
|
1116
|
+
return projectConfig[_projectId].numSettleableInvocations;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* @notice Gets if price of token is configured, price of minting a
|
|
1121
|
+
* token on project `_projectId`, and currency symbol and address to be
|
|
1122
|
+
* used as payment. Supersedes any core contract price information.
|
|
1123
|
+
* @param _projectId Project ID to get price information for.
|
|
1124
|
+
* @return isConfigured true only if project's auction parameters have been
|
|
1125
|
+
* configured on this minter
|
|
1126
|
+
* @return tokenPriceInWei current price of token on this minter - invalid
|
|
1127
|
+
* if auction has not yet been configured
|
|
1128
|
+
* @return currencySymbol currency symbol for purchases of project on this
|
|
1129
|
+
* minter. This minter always returns "ETH"
|
|
1130
|
+
* @return currencyAddress currency address for purchases of project on
|
|
1131
|
+
* this minter. This minter always returns null address, reserved for ether
|
|
1132
|
+
*/
|
|
1133
|
+
function getPriceInfo(
|
|
1134
|
+
uint256 _projectId
|
|
1135
|
+
)
|
|
1136
|
+
external
|
|
1137
|
+
view
|
|
1138
|
+
returns (
|
|
1139
|
+
bool isConfigured,
|
|
1140
|
+
uint256 tokenPriceInWei,
|
|
1141
|
+
string memory currencySymbol,
|
|
1142
|
+
address currencyAddress
|
|
1143
|
+
)
|
|
1144
|
+
{
|
|
1145
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
1146
|
+
|
|
1147
|
+
isConfigured = (_projectConfig.startPrice > 0);
|
|
1148
|
+
if (block.timestamp <= _projectConfig.timestampStart) {
|
|
1149
|
+
// Provide a reasonable value for `tokenPriceInWei` when it would
|
|
1150
|
+
// otherwise revert, using the starting price before auction starts.
|
|
1151
|
+
tokenPriceInWei = _projectConfig.startPrice;
|
|
1152
|
+
} else if (_projectConfig.startPrice == 0) {
|
|
1153
|
+
// In the case of unconfigured auction, return price of zero when
|
|
1154
|
+
// it would otherwise revert
|
|
1155
|
+
tokenPriceInWei = 0;
|
|
1156
|
+
} else {
|
|
1157
|
+
tokenPriceInWei = _getPriceSafe(_projectId);
|
|
1158
|
+
}
|
|
1159
|
+
currencySymbol = "ETH";
|
|
1160
|
+
currencyAddress = address(0);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* @notice Sets the local max invocation values of a project equal to the
|
|
1165
|
+
* values on the core contract.
|
|
1166
|
+
* @param _projectId Project ID to set the maximum invocations for.
|
|
1167
|
+
*/
|
|
1168
|
+
function _syncProjectMaxInvocationsCoreCached(uint256 _projectId) internal {
|
|
1169
|
+
uint256 coreMaxInvocations;
|
|
1170
|
+
uint256 coreInvocations;
|
|
1171
|
+
(
|
|
1172
|
+
coreInvocations,
|
|
1173
|
+
coreMaxInvocations
|
|
1174
|
+
) = _getProjectCoreInvocationsAndMaxInvocations(_projectId);
|
|
1175
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
1176
|
+
// update storage with results, emit event after change
|
|
1177
|
+
_projectConfig.maxInvocations = uint24(coreMaxInvocations);
|
|
1178
|
+
_projectConfig.maxHasBeenInvoked =
|
|
1179
|
+
coreMaxInvocations == coreInvocations;
|
|
1180
|
+
|
|
1181
|
+
emit ProjectMaxInvocationsLimitUpdated(_projectId, coreMaxInvocations);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* @notice Returns the current invocations and maximum invocations of
|
|
1186
|
+
* project `_projectId` from the core contract.
|
|
1187
|
+
* @param _projectId Project ID to get invocations and maximum invocations
|
|
1188
|
+
* for.
|
|
1189
|
+
* @return invocations current invocations of project.
|
|
1190
|
+
* @return maxInvocations maximum invocations of project.
|
|
1191
|
+
*/
|
|
1192
|
+
function _getProjectCoreInvocationsAndMaxInvocations(
|
|
1193
|
+
uint256 _projectId
|
|
1194
|
+
) internal view returns (uint256 invocations, uint256 maxInvocations) {
|
|
1195
|
+
(invocations, maxInvocations, , , , ) = genArtCoreContract_Base
|
|
1196
|
+
.projectStateData(_projectId);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* @notice Verifies the cached values of a project's maxInvocation state
|
|
1201
|
+
* are logically consistent with the core contract's maxInvocation state,
|
|
1202
|
+
* or populates them to equal the core contract's maxInvocation state if
|
|
1203
|
+
* they have never been populated.
|
|
1204
|
+
*/
|
|
1205
|
+
function _refreshMaxInvocations(uint256 _projectId) internal {
|
|
1206
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
1207
|
+
// project's max invocations and has max been invoked can only be
|
|
1208
|
+
// initial values if never populated, because setting a maxInvocations
|
|
1209
|
+
// of zero means maxHasBeenInvoked would be set to true
|
|
1210
|
+
bool notPopulated = (_projectConfig.maxInvocations == 0 &&
|
|
1211
|
+
_projectConfig.maxHasBeenInvoked == false);
|
|
1212
|
+
if (notPopulated) {
|
|
1213
|
+
// sync the minter max invocation state to equal the values on the
|
|
1214
|
+
// core contract (least restrictive state)
|
|
1215
|
+
_syncProjectMaxInvocationsCoreCached(_projectId);
|
|
1216
|
+
} else {
|
|
1217
|
+
// if using local max invocations, validate the local state
|
|
1218
|
+
// (i.e. ensure local max invocations not greater than core max
|
|
1219
|
+
// invocations)
|
|
1220
|
+
_validateProjectMaxInvocations(_projectId);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* @notice Checks and updates local project max invocations to determine if
|
|
1226
|
+
* if they are in an illogical state relative to the core contract's max
|
|
1227
|
+
* invocations.
|
|
1228
|
+
* This updates the project's local max invocations if the value is greater
|
|
1229
|
+
* than the core contract's max invocations, which is an illogical state
|
|
1230
|
+
* since V3 core contracts cannot increase max invocations. In that case,
|
|
1231
|
+
* the project's local max invocations are set to the core contract's max
|
|
1232
|
+
* invocations, and the project's `maxHasBeenInvoked` state is refreshed.
|
|
1233
|
+
* This also updates the project's `maxHasBeenInvoked` state if the core
|
|
1234
|
+
* contract's invocations are greater than or equal to the minter's local
|
|
1235
|
+
* max invocations. This handles the case where a different minter has been
|
|
1236
|
+
* used to mint above the local max invocations, which would cause
|
|
1237
|
+
* `maxHasBeenInvoked` to return a false negative.
|
|
1238
|
+
* @param _projectId Project ID to set the maximum invocations for.
|
|
1239
|
+
*/
|
|
1240
|
+
function _validateProjectMaxInvocations(uint256 _projectId) internal {
|
|
1241
|
+
uint256 coreMaxInvocations;
|
|
1242
|
+
uint256 coreInvocations;
|
|
1243
|
+
(
|
|
1244
|
+
coreInvocations,
|
|
1245
|
+
coreMaxInvocations
|
|
1246
|
+
) = _getProjectCoreInvocationsAndMaxInvocations(_projectId);
|
|
1247
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
1248
|
+
uint256 localMaxInvocations = _projectConfig.maxInvocations;
|
|
1249
|
+
// check if local max invocations is illogical relative to core
|
|
1250
|
+
// contract's max invocations
|
|
1251
|
+
if (localMaxInvocations > coreMaxInvocations) {
|
|
1252
|
+
// set local max invocations to core contract's max invocations
|
|
1253
|
+
_projectConfig.maxInvocations = uint24(coreMaxInvocations);
|
|
1254
|
+
// update the project's `maxHasBeenInvoked` state
|
|
1255
|
+
// @dev core values are equivalent to local values, use for gas
|
|
1256
|
+
// efficiency
|
|
1257
|
+
_projectConfig.maxHasBeenInvoked = (coreMaxInvocations ==
|
|
1258
|
+
coreInvocations);
|
|
1259
|
+
emit ProjectMaxInvocationsLimitUpdated(
|
|
1260
|
+
_projectId,
|
|
1261
|
+
coreMaxInvocations
|
|
1262
|
+
);
|
|
1263
|
+
} else if (coreInvocations >= localMaxInvocations) {
|
|
1264
|
+
// ensure the local `maxHasBeenInvoked` state is accurate to
|
|
1265
|
+
// prevent any false negatives due to minting on other minters
|
|
1266
|
+
_projectConfig.maxHasBeenInvoked = true;
|
|
1267
|
+
// emit event to ensure any indexers are aware of the change
|
|
1268
|
+
// @dev this is not strictly necessary, but is included for
|
|
1269
|
+
// convenience
|
|
1270
|
+
emit ProjectMaxInvocationsLimitUpdated(
|
|
1271
|
+
_projectId,
|
|
1272
|
+
coreMaxInvocations
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* @notice Returns true if the project `_projectId` is sold out, false
|
|
1279
|
+
* otherwise. This function returns an accurate value regardless of whether
|
|
1280
|
+
* the project's maximum invocations value cached locally on the minter is
|
|
1281
|
+
* up to date with the core contract's maximum invocations value.
|
|
1282
|
+
* @param _projectId Project ID to check if sold out.
|
|
1283
|
+
* @return bool true if the project is sold out, false otherwise.
|
|
1284
|
+
* @dev this is a view method, and will not update the minter's local
|
|
1285
|
+
* cached state.
|
|
1286
|
+
*/
|
|
1287
|
+
function _projectMaxHasBeenInvokedSafe(
|
|
1288
|
+
uint256 _projectId
|
|
1289
|
+
) internal view returns (bool) {
|
|
1290
|
+
// get max invocations from core contract
|
|
1291
|
+
uint256 coreInvocations;
|
|
1292
|
+
uint256 coreMaxInvocations;
|
|
1293
|
+
(
|
|
1294
|
+
coreInvocations,
|
|
1295
|
+
coreMaxInvocations
|
|
1296
|
+
) = _getProjectCoreInvocationsAndMaxInvocations(_projectId);
|
|
1297
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
1298
|
+
uint256 localMaxInvocations = _projectConfig.maxInvocations;
|
|
1299
|
+
// value is locally defined, and could be out of date.
|
|
1300
|
+
// only possible illogical state is if local max invocations is
|
|
1301
|
+
// greater than core contract's max invocations, in which case
|
|
1302
|
+
// we should use the core contract's max invocations
|
|
1303
|
+
if (localMaxInvocations > coreMaxInvocations) {
|
|
1304
|
+
// local max invocations is stale and illogical, defer to core
|
|
1305
|
+
// contract's max invocations since it is the limiting factor
|
|
1306
|
+
return (coreMaxInvocations == coreInvocations);
|
|
1307
|
+
}
|
|
1308
|
+
// local max invocations is limiting, so check core invocations against
|
|
1309
|
+
// local max invocations
|
|
1310
|
+
return (coreInvocations >= localMaxInvocations);
|
|
1311
|
+
}
|
|
1312
|
+
}
|