@artblocks/contracts 0.0.1 → 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 +30 -11
- 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/GenArt721CoreV0.sol/Address.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/Counters.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/CustomERC721Metadata.json +425 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC165.json +38 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721.json +322 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721Enumerable.json +384 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721ReceiverMock.json +101 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/GenArt721.json +1528 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC165.json +32 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721.json +316 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721Enumerable.json +378 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721Receiver.json +47 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/SafeMath.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/Strings.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/Address.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/Counters.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/CustomERC721Metadata.json +425 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC165.json +38 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC721.json +322 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC721Enumerable.json +384 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/GenArt721Core.json +1773 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC165.json +32 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721.json +316 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721Enumerable.json +378 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721Receiver.json +47 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/Randomizer.json +26 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/SafeMath.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/Strings.json +10 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV0.sol/MinterDAExpV0.json +575 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV1.sol/MinterDAExpV1.json +463 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV2.sol/MinterDAExpV2.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV3.sol/MinterDAExpV3.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol/MinterDAExpSettlementV0.json +15 -15
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV1.sol/MinterDAExpSettlementV1.json +1066 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV0.sol/MinterDALinV0.json +551 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV1.sol/MinterDALinV1.json +439 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV2.sol/MinterDALinV2.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV3.sol/MinterDALinV3.json +3 -3
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV0.sol/MinterHolderV0.json +622 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol/MinterHolderV1.json +719 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +794 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV3.sol/MinterHolderV3.json +3 -3
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV0.sol/MinterMerkleV0.json +873 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol/MinterMerkleV1.json +932 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol/MinterMerkleV2.json +984 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV3.sol/MinterMerkleV3.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV4.sol/MinterMerkleV4.json +3 -3
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV0.sol/MinterSetPriceV0.json +400 -0
- package/artifacts/contracts/{minter-suite/Minters/MinterSetPrice/deprecated → archive/minter-suite/Minters/MinterSetPrice}/MinterSetPriceV1.sol/MinterSetPriceV1.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV2.sol/MinterSetPriceV2.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV3.sol/MinterSetPriceV3.json +3 -3
- package/artifacts/contracts/{minter-suite/Minters/MinterSetPriceERC20/deprecated → archive/minter-suite/Minters/MinterSetPriceERC20}/MinterSetPriceERC20V0.sol/MinterSetPriceERC20V0.json +3 -3
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V1.sol/MinterSetPriceERC20V1.json +349 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol/MinterSetPriceERC20V2.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol/MinterSetPriceERC20V3.json +3 -3
- package/artifacts/contracts/explorations/GenArt721CoreV3_Explorations.sol/GenArt721CoreV3_Explorations.json +2 -2
- package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV0.sol/IFilteredMinterDAExpSettlementV0.json +12 -12
- package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV1.sol/IFilteredMinterDAExpSettlementV1.json +767 -0
- package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlement_Mixin.sol/IFilteredMinterDAExpSettlement_Mixin.json +161 -0
- package/artifacts/contracts/interfaces/0.8.x/IMinterBaseV0.sol/IMinterBaseV0.json +24 -0
- package/artifacts/contracts/interfaces/0.8.x/IMinterFilterV0.sol/IMinterFilterV0.json +6 -0
- package/artifacts/contracts/interfaces/0.8.x/IMintingAPICoreV0.sol/IMintingAPICoreV0.json +185 -0
- package/artifacts/contracts/interfaces/0.8.x/IMintingAPIMinterV0.sol/IMintingAPIMinterV0.json +88 -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/MinterBase_v0_1_1.sol/MinterBase.json +24 -0
- package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol/MinterDAExpSettlementV2.json +1127 -0
- package/artifacts/contracts/minter-suite/Minters/MinterDAExpV4.sol/MinterDAExpV4.json +882 -0
- package/artifacts/contracts/minter-suite/Minters/MinterDALinV4.sol/MinterDALinV4.json +858 -0
- package/artifacts/contracts/minter-suite/Minters/MinterHolderV4.sol/MinterHolderV4.json +1126 -0
- package/artifacts/contracts/minter-suite/Minters/MinterMerkleV5.sol/MinterMerkleV5.json +1065 -0
- 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 +768 -0
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceV4.sol/MinterSetPriceV4.json +697 -0
- package/artifacts/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol/GenArt721CoreV3_Engine_IncorrectCoreType.json +1817 -0
- 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/archive/GenArt721CoreV0.sol +1621 -0
- package/contracts/archive/GenArt721CoreV1.sol +1577 -0
- package/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV0.sol +503 -0
- package/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV1.sol +442 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV2.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV3.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol +3 -3
- package/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV1.sol +1026 -0
- package/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV0.sol +450 -0
- package/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV1.sol +406 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV2.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV3.sol +3 -3
- package/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV0.sol +540 -0
- package/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol +648 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV2.sol +6 -6
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV3.sol +4 -4
- package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV0.sol +523 -0
- package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol +602 -0
- package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol +679 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV3.sol +4 -4
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV4.sol +4 -4
- package/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV0.sol +333 -0
- package/contracts/{minter-suite/Minters/MinterSetPrice/deprecated → archive/minter-suite/Minters/MinterSetPrice}/MinterSetPriceV1.sol +0 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV2.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV3.sol +3 -3
- package/contracts/{minter-suite/Minters/MinterSetPriceERC20/deprecated → archive/minter-suite/Minters/MinterSetPriceERC20}/MinterSetPriceERC20V0.sol +0 -0
- package/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V1.sol +411 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol +3 -3
- package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV0.sol +4 -53
- package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV1.sol +21 -0
- package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlement_Mixin.sol +62 -0
- package/contracts/interfaces/0.8.x/IMinterBaseV0.sol +20 -0
- package/contracts/interfaces/0.8.x/IMinterFilterV0.sol +6 -0
- package/contracts/interfaces/0.8.x/IMintingAPICoreV0.sol +62 -0
- package/contracts/interfaces/0.8.x/IMintingAPIMinterV0.sol +31 -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/MinterBase_v0_1_1.sol +293 -0
- package/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol +1312 -0
- package/contracts/minter-suite/Minters/MinterDAExpV4.sol +600 -0
- package/contracts/minter-suite/Minters/MinterDALinV4.sol +552 -0
- package/contracts/minter-suite/Minters/MinterHolderV4.sol +781 -0
- package/contracts/minter-suite/Minters/MinterMerkleV5.sol +717 -0
- package/contracts/minter-suite/Minters/MinterPolyptychV0.sol +998 -0
- package/contracts/minter-suite/Minters/MinterSetPriceERC20V4.sol +480 -0
- package/contracts/minter-suite/Minters/MinterSetPriceV4.sol +380 -0
- package/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol +1979 -0
- package/package.json +8 -2
- package/artifacts/contracts/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +0 -794
package/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV1.sol
ADDED
|
@@ -0,0 +1,1026 @@
|
|
|
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/IFilteredMinterDAExpSettlementV0.sol";
|
|
7
|
+
import "../../../../minter-suite/Minters/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
|
+
* ----------------------------------------------------------------------------
|
|
55
|
+
* Additional admin and artist privileged roles may be described on other
|
|
56
|
+
* contracts that this minter integrates with.
|
|
57
|
+
*
|
|
58
|
+
* @dev Note that while this minter makes use of `block.timestamp` and it is
|
|
59
|
+
* technically possible that this value is manipulated by block producers via
|
|
60
|
+
* denial of service (in PoS), such manipulation will not have material impact
|
|
61
|
+
* on the price values of this minter given the business practices for how
|
|
62
|
+
* pricing is congfigured for this minter and that variations on the order of
|
|
63
|
+
* less than a minute should not meaningfully impact price given the minimum
|
|
64
|
+
* allowable price decay rate that this minter intends to support.
|
|
65
|
+
*/
|
|
66
|
+
contract MinterDAExpSettlementV1 is
|
|
67
|
+
ReentrancyGuard,
|
|
68
|
+
MinterBase,
|
|
69
|
+
IFilteredMinterDAExpSettlementV0
|
|
70
|
+
{
|
|
71
|
+
using SafeCast for uint256;
|
|
72
|
+
|
|
73
|
+
/// Core contract address this minter interacts with
|
|
74
|
+
address public immutable genArt721CoreAddress;
|
|
75
|
+
/// The core contract integrates with V3 contracts
|
|
76
|
+
IGenArt721CoreContractV3_Base private immutable genArtCoreContract_Base;
|
|
77
|
+
|
|
78
|
+
/// Minter filter address this minter interacts with
|
|
79
|
+
address public immutable minterFilterAddress;
|
|
80
|
+
|
|
81
|
+
/// Minter filter this minter may interact with.
|
|
82
|
+
IMinterFilterV0 private immutable minterFilter;
|
|
83
|
+
|
|
84
|
+
/// minterType for this minter
|
|
85
|
+
string public constant minterType = "MinterDAExpSettlementV1";
|
|
86
|
+
|
|
87
|
+
uint256 constant ONE_MILLION = 1_000_000;
|
|
88
|
+
|
|
89
|
+
struct ProjectConfig {
|
|
90
|
+
// on this minter, hasMaxBeenInvoked is updated only during every
|
|
91
|
+
// purchase, and is only true if this minter minted the final token.
|
|
92
|
+
// this enables the minter to know when a sellout price is greater than
|
|
93
|
+
// the auction's base price.
|
|
94
|
+
bool maxHasBeenInvoked;
|
|
95
|
+
// set to true only after artist + admin revenues have been collected
|
|
96
|
+
bool auctionRevenuesCollected;
|
|
97
|
+
// number of tokens minted that have potential of future settlement.
|
|
98
|
+
// max uint24 > 16.7 million tokens > 1 million tokens/project max
|
|
99
|
+
uint24 numSettleableInvocations;
|
|
100
|
+
// max uint64 ~= 1.8e19 sec ~= 570 billion years
|
|
101
|
+
uint64 timestampStart;
|
|
102
|
+
uint64 priceDecayHalfLifeSeconds;
|
|
103
|
+
// Prices are packed internally as uint128, resulting in a maximum
|
|
104
|
+
// allowed price of ~3.4e20 ETH. This is many orders of magnitude
|
|
105
|
+
// greater than current ETH supply.
|
|
106
|
+
uint128 startPrice;
|
|
107
|
+
// base price is non-zero for all configured auctions on this minter
|
|
108
|
+
uint128 basePrice;
|
|
109
|
+
// This value is only zero if no purchases have been made on this
|
|
110
|
+
// minter.
|
|
111
|
+
// When non-zero, this value is used as a reference when an auction is
|
|
112
|
+
// reset by admin, and then a new auction is configured by an artist.
|
|
113
|
+
// In that case, the new auction will be required to have a starting
|
|
114
|
+
// price less than or equal to this value, if one or more purchases
|
|
115
|
+
// have been made on this minter.
|
|
116
|
+
uint256 latestPurchasePrice;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
mapping(uint256 => ProjectConfig) public projectConfig;
|
|
120
|
+
|
|
121
|
+
/// Minimum price decay half life: price must decay with a half life of at
|
|
122
|
+
/// least this amount (must cut in half at least every N seconds).
|
|
123
|
+
uint256 public minimumPriceDecayHalfLifeSeconds = 300; // 5 minutes
|
|
124
|
+
/// Maximum price decay half life: price may decay with a half life of no
|
|
125
|
+
/// more than this amount (may cut in half at no more than every N seconds).
|
|
126
|
+
uint256 public maximumPriceDecayHalfLifeSeconds = 3600; // 60 minutes
|
|
127
|
+
|
|
128
|
+
struct Receipt {
|
|
129
|
+
// max uint232 allows for > 1e51 ETH (much more than max supply)
|
|
130
|
+
uint232 netPosted;
|
|
131
|
+
// max uint24 still allows for > max project supply of 1 million tokens
|
|
132
|
+
uint24 numPurchased;
|
|
133
|
+
}
|
|
134
|
+
/// user address => project ID => receipt
|
|
135
|
+
mapping(address => mapping(uint256 => Receipt)) receipts;
|
|
136
|
+
|
|
137
|
+
// modifier to restrict access to only AdminACL or the artist
|
|
138
|
+
modifier onlyCoreAdminACLOrArtist(uint256 _projectId, bytes4 _selector) {
|
|
139
|
+
require(
|
|
140
|
+
(msg.sender ==
|
|
141
|
+
genArtCoreContract_Base.projectIdToArtistAddress(_projectId)) ||
|
|
142
|
+
(
|
|
143
|
+
genArtCoreContract_Base.adminACLAllowed(
|
|
144
|
+
msg.sender,
|
|
145
|
+
address(this),
|
|
146
|
+
_selector
|
|
147
|
+
)
|
|
148
|
+
),
|
|
149
|
+
"Only Artist or Admin ACL"
|
|
150
|
+
);
|
|
151
|
+
_;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// modifier to restrict access to only AdminACL allowed calls
|
|
155
|
+
// @dev defers which ACL contract is used to the core contract
|
|
156
|
+
modifier onlyCoreAdminACL(bytes4 _selector) {
|
|
157
|
+
require(
|
|
158
|
+
genArtCoreContract_Base.adminACLAllowed(
|
|
159
|
+
msg.sender,
|
|
160
|
+
address(this),
|
|
161
|
+
_selector
|
|
162
|
+
),
|
|
163
|
+
"Only Core AdminACL allowed"
|
|
164
|
+
);
|
|
165
|
+
_;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
modifier onlyArtist(uint256 _projectId) {
|
|
169
|
+
require(
|
|
170
|
+
(msg.sender ==
|
|
171
|
+
genArtCoreContract_Base.projectIdToArtistAddress(_projectId)),
|
|
172
|
+
"Only Artist"
|
|
173
|
+
);
|
|
174
|
+
_;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @notice Initializes contract to be a Filtered Minter for
|
|
179
|
+
* `_minterFilter`, integrated with Art Blocks core contract
|
|
180
|
+
* at address `_genArt721Address`.
|
|
181
|
+
* @param _genArt721Address Art Blocks core contract address for
|
|
182
|
+
* which this contract will be a minter.
|
|
183
|
+
* @param _minterFilter Minter filter for which
|
|
184
|
+
* this will a filtered minter.
|
|
185
|
+
*/
|
|
186
|
+
constructor(
|
|
187
|
+
address _genArt721Address,
|
|
188
|
+
address _minterFilter
|
|
189
|
+
) ReentrancyGuard() MinterBase(_genArt721Address) {
|
|
190
|
+
genArt721CoreAddress = _genArt721Address;
|
|
191
|
+
// always populate immutable engine contracts, but only use appropriate
|
|
192
|
+
// interface based on isEngine in the rest of the contract
|
|
193
|
+
genArtCoreContract_Base = IGenArt721CoreContractV3_Base(
|
|
194
|
+
_genArt721Address
|
|
195
|
+
);
|
|
196
|
+
minterFilterAddress = _minterFilter;
|
|
197
|
+
minterFilter = IMinterFilterV0(_minterFilter);
|
|
198
|
+
require(
|
|
199
|
+
minterFilter.genArt721CoreAddress() == _genArt721Address,
|
|
200
|
+
"Illegal contract pairing"
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @notice This function is not implemented on this minter, and exists only
|
|
206
|
+
* for interface conformance reasons. This minter checks if max invocations
|
|
207
|
+
* have been reached during every purchase to determine if a sellout has
|
|
208
|
+
* occurred. Therefore, the local caching of max invocations is not
|
|
209
|
+
* beneficial or necessary.
|
|
210
|
+
*/
|
|
211
|
+
function setProjectMaxInvocations(uint256 /*_projectId*/) external pure {
|
|
212
|
+
// not implemented because maxInvocations must be checked during every mint
|
|
213
|
+
// to know if final price should be set
|
|
214
|
+
revert(
|
|
215
|
+
"setProjectMaxInvocations not implemented - updated during every mint"
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @notice Warning: Disabling purchaseTo is not supported on this minter.
|
|
221
|
+
* This method exists purely for interface-conformance purposes.
|
|
222
|
+
*/
|
|
223
|
+
function togglePurchaseToDisabled(
|
|
224
|
+
uint256 _projectId
|
|
225
|
+
) external view onlyArtist(_projectId) {
|
|
226
|
+
revert("Action not supported");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @notice projectId => has project reached its maximum number of
|
|
231
|
+
* invocations while being minted with this minter?
|
|
232
|
+
* Note that this returns a local cache of the core contract's
|
|
233
|
+
* state, and may be out of sync with the core contract. This is
|
|
234
|
+
* intentional. A false negative will only result in a gas cost increase,
|
|
235
|
+
* since the core contract will still enforce max invocations during during
|
|
236
|
+
* minting. A false negative will also only occur if the max invocations
|
|
237
|
+
* was either reduced on the core contract to equal current invocations, or
|
|
238
|
+
* if the max invocations was reached by minting on a different minter.
|
|
239
|
+
* In both of these cases, we expect the net purchase price (after
|
|
240
|
+
* settlement) shall be the base price of the project's auction. This
|
|
241
|
+
* prevents an artist from benefiting by reducing max invocations on the
|
|
242
|
+
* core mid-auction, or by minting on a different minter.
|
|
243
|
+
* Note that if an artist wishes to reduce the max invocations on the core
|
|
244
|
+
* to something less than the current invocations, but more than max
|
|
245
|
+
* invocations (with the hope of increasing the sellout price), an admin
|
|
246
|
+
* function is provided to manually reduce the sellout price to a lower
|
|
247
|
+
* value, if desired, in the `adminEmergencyReduceSelloutPrice`
|
|
248
|
+
* function.
|
|
249
|
+
* @param _projectId projectId to be queried
|
|
250
|
+
*
|
|
251
|
+
*/
|
|
252
|
+
function projectMaxHasBeenInvoked(
|
|
253
|
+
uint256 _projectId
|
|
254
|
+
) external view returns (bool) {
|
|
255
|
+
return projectConfig[_projectId].maxHasBeenInvoked;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @notice projectId => auction parameters
|
|
260
|
+
*/
|
|
261
|
+
function projectAuctionParameters(
|
|
262
|
+
uint256 _projectId
|
|
263
|
+
)
|
|
264
|
+
external
|
|
265
|
+
view
|
|
266
|
+
returns (
|
|
267
|
+
uint256 timestampStart,
|
|
268
|
+
uint256 priceDecayHalfLifeSeconds,
|
|
269
|
+
uint256 startPrice,
|
|
270
|
+
uint256 basePrice
|
|
271
|
+
)
|
|
272
|
+
{
|
|
273
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
274
|
+
return (
|
|
275
|
+
_projectConfig.timestampStart,
|
|
276
|
+
_projectConfig.priceDecayHalfLifeSeconds,
|
|
277
|
+
_projectConfig.startPrice,
|
|
278
|
+
_projectConfig.basePrice
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @notice Sets the minimum and maximum values that are settable for
|
|
284
|
+
* `_priceDecayHalfLifeSeconds` across all projects.
|
|
285
|
+
* @param _minimumPriceDecayHalfLifeSeconds Minimum price decay half life
|
|
286
|
+
* (in seconds).
|
|
287
|
+
* @param _maximumPriceDecayHalfLifeSeconds Maximum price decay half life
|
|
288
|
+
* (in seconds).
|
|
289
|
+
*/
|
|
290
|
+
function setAllowablePriceDecayHalfLifeRangeSeconds(
|
|
291
|
+
uint256 _minimumPriceDecayHalfLifeSeconds,
|
|
292
|
+
uint256 _maximumPriceDecayHalfLifeSeconds
|
|
293
|
+
)
|
|
294
|
+
external
|
|
295
|
+
onlyCoreAdminACL(
|
|
296
|
+
this.setAllowablePriceDecayHalfLifeRangeSeconds.selector
|
|
297
|
+
)
|
|
298
|
+
{
|
|
299
|
+
require(
|
|
300
|
+
_maximumPriceDecayHalfLifeSeconds >
|
|
301
|
+
_minimumPriceDecayHalfLifeSeconds,
|
|
302
|
+
"Maximum half life must be greater than minimum"
|
|
303
|
+
);
|
|
304
|
+
require(
|
|
305
|
+
_minimumPriceDecayHalfLifeSeconds > 0,
|
|
306
|
+
"Half life of zero not allowed"
|
|
307
|
+
);
|
|
308
|
+
minimumPriceDecayHalfLifeSeconds = _minimumPriceDecayHalfLifeSeconds;
|
|
309
|
+
maximumPriceDecayHalfLifeSeconds = _maximumPriceDecayHalfLifeSeconds;
|
|
310
|
+
emit AuctionHalfLifeRangeSecondsUpdated(
|
|
311
|
+
_minimumPriceDecayHalfLifeSeconds,
|
|
312
|
+
_maximumPriceDecayHalfLifeSeconds
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
////// Auction Functions
|
|
317
|
+
/**
|
|
318
|
+
* @notice Sets auction details for project `_projectId`.
|
|
319
|
+
* @param _projectId Project ID to set auction details for.
|
|
320
|
+
* @param _auctionTimestampStart Timestamp at which to start the auction.
|
|
321
|
+
* @param _priceDecayHalfLifeSeconds The half life with which to decay the
|
|
322
|
+
* price (in seconds).
|
|
323
|
+
* @param _startPrice Price at which to start the auction, in Wei.
|
|
324
|
+
* If a previous auction existed on this minter and at least one settleable
|
|
325
|
+
* purchase has been made, this value must be less than or equal to the
|
|
326
|
+
* price when the previous auction was paused. This enforces an overall
|
|
327
|
+
* monatonically decreasing auction. Must be greater than or equal to
|
|
328
|
+
* max(uint128) for internal storage packing purposes.
|
|
329
|
+
* @param _basePrice Resting price of the auction, in Wei. Must be greater
|
|
330
|
+
* than or equal to max(uint128) for internal storage packing purposes.
|
|
331
|
+
* @dev Note that setting the auction price explicitly to `0` is
|
|
332
|
+
* intentionally not allowed. This allows the minter to use the assumption
|
|
333
|
+
* that a price of `0` indicates that the auction is not configured.
|
|
334
|
+
* @dev Note that prices must be <= max(128) for internal storage packing
|
|
335
|
+
* efficiency purposes only. This function's interface remains unchanged
|
|
336
|
+
* for interface conformance purposes.
|
|
337
|
+
*/
|
|
338
|
+
function setAuctionDetails(
|
|
339
|
+
uint256 _projectId,
|
|
340
|
+
uint256 _auctionTimestampStart,
|
|
341
|
+
uint256 _priceDecayHalfLifeSeconds,
|
|
342
|
+
uint256 _startPrice,
|
|
343
|
+
uint256 _basePrice
|
|
344
|
+
) external onlyArtist(_projectId) {
|
|
345
|
+
// CHECKS
|
|
346
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
347
|
+
require(
|
|
348
|
+
_projectConfig.timestampStart == 0 ||
|
|
349
|
+
block.timestamp < _projectConfig.timestampStart,
|
|
350
|
+
"No modifications mid-auction"
|
|
351
|
+
);
|
|
352
|
+
require(
|
|
353
|
+
block.timestamp < _auctionTimestampStart,
|
|
354
|
+
"Only future auctions"
|
|
355
|
+
);
|
|
356
|
+
require(
|
|
357
|
+
_startPrice > _basePrice,
|
|
358
|
+
"Auction start price must be greater than auction end price"
|
|
359
|
+
);
|
|
360
|
+
// require _basePrice is non-zero to simplify logic of this minter
|
|
361
|
+
require(_basePrice > 0, "Base price must be non-zero");
|
|
362
|
+
// If previous purchases have been made, require monotonically
|
|
363
|
+
// decreasing purchase prices to preserve settlement and revenue
|
|
364
|
+
// claiming logic. Since base price is always non-zero, if
|
|
365
|
+
// latestPurchasePrice is zero, then no previous purchases have been
|
|
366
|
+
// made, and startPrice may be set to any value.
|
|
367
|
+
require(
|
|
368
|
+
_projectConfig.latestPurchasePrice == 0 || // never purchased
|
|
369
|
+
_startPrice <= _projectConfig.latestPurchasePrice,
|
|
370
|
+
"Auction start price must be <= latest purchase price"
|
|
371
|
+
);
|
|
372
|
+
require(
|
|
373
|
+
(_priceDecayHalfLifeSeconds >= minimumPriceDecayHalfLifeSeconds) &&
|
|
374
|
+
(_priceDecayHalfLifeSeconds <=
|
|
375
|
+
maximumPriceDecayHalfLifeSeconds),
|
|
376
|
+
"Price decay half life must fall between min and max allowable values"
|
|
377
|
+
);
|
|
378
|
+
// EFFECTS
|
|
379
|
+
_projectConfig.timestampStart = _auctionTimestampStart.toUint64();
|
|
380
|
+
_projectConfig.priceDecayHalfLifeSeconds = _priceDecayHalfLifeSeconds
|
|
381
|
+
.toUint64();
|
|
382
|
+
_projectConfig.startPrice = _startPrice.toUint128();
|
|
383
|
+
_projectConfig.basePrice = _basePrice.toUint128();
|
|
384
|
+
|
|
385
|
+
emit SetAuctionDetails(
|
|
386
|
+
_projectId,
|
|
387
|
+
_auctionTimestampStart,
|
|
388
|
+
_priceDecayHalfLifeSeconds,
|
|
389
|
+
_startPrice,
|
|
390
|
+
_basePrice
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @notice Resets auction details for project `_projectId`, zero-ing out all
|
|
396
|
+
* relevant auction fields. Not intended to be used in normal auction
|
|
397
|
+
* operation, but rather only in case of the need to reset an ongoing
|
|
398
|
+
* auction. An expected time this might occur would be when a frontend
|
|
399
|
+
* issue was occuring, and many typical users are actively being prevented
|
|
400
|
+
* from easily minting (even though minting would technically be possible
|
|
401
|
+
* directly from the contract).
|
|
402
|
+
* This function is only callable by the core admin during an active
|
|
403
|
+
* auction, before revenues have been collected.
|
|
404
|
+
* The price at the time of the reset will be the maximum starting price
|
|
405
|
+
* when re-configuring the next auction if one or more settleable purchases
|
|
406
|
+
* have been made.
|
|
407
|
+
* This is to ensure that purchases up through the block that this is
|
|
408
|
+
* called on will remain settleable, and that revenue claimed does not
|
|
409
|
+
* surpass (payments - excess_settlement_funds) for a given project.
|
|
410
|
+
* @param _projectId Project ID to set auction details for.
|
|
411
|
+
*/
|
|
412
|
+
function resetAuctionDetails(
|
|
413
|
+
uint256 _projectId
|
|
414
|
+
) external onlyCoreAdminACL(this.resetAuctionDetails.selector) {
|
|
415
|
+
// CHECKS
|
|
416
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
417
|
+
require(_projectConfig.startPrice != 0, "Auction must be configured");
|
|
418
|
+
// no reset after revenues collected, since that solidifies amount due
|
|
419
|
+
require(
|
|
420
|
+
!_projectConfig.auctionRevenuesCollected,
|
|
421
|
+
"Only before revenues collected"
|
|
422
|
+
);
|
|
423
|
+
// EFFECTS
|
|
424
|
+
// reset to initial values
|
|
425
|
+
_projectConfig.timestampStart = 0;
|
|
426
|
+
_projectConfig.priceDecayHalfLifeSeconds = 0;
|
|
427
|
+
_projectConfig.startPrice = 0;
|
|
428
|
+
_projectConfig.basePrice = 0;
|
|
429
|
+
// Since auction revenues have not been collected, we can safely assume
|
|
430
|
+
// that numSettleableInvocations is the number of purchases made on
|
|
431
|
+
// this minter. A dummy value of 0 is used for latest purchase price if
|
|
432
|
+
// no purchases have been made.
|
|
433
|
+
emit ResetAuctionDetails(
|
|
434
|
+
_projectId,
|
|
435
|
+
_projectConfig.numSettleableInvocations,
|
|
436
|
+
_projectConfig.latestPurchasePrice
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* @notice This represents an admin stepping in and reducing the sellout
|
|
442
|
+
* price of an auction. This is only callable by the core admin, only
|
|
443
|
+
* after the auction is complete, but before project revenues are
|
|
444
|
+
* withdrawn.
|
|
445
|
+
* This is only intended to be used in the case where for some reason,
|
|
446
|
+
* whether malicious or accidental, the sellout price was too high.
|
|
447
|
+
* Examples of this include:
|
|
448
|
+
* - The artist reducing a project's maxInvocations on the core contract
|
|
449
|
+
* after an auction has started, but before it ends, eliminating the
|
|
450
|
+
* ability of purchasers to fairly determine market price under the
|
|
451
|
+
* original, expected auction parameters.
|
|
452
|
+
* - Any other reason the admin deems to be a valid reason to reduce the
|
|
453
|
+
* sellout price of an auction, prior to marking it as valid.
|
|
454
|
+
* @param _projectId Project ID to reduce auction sellout price for.
|
|
455
|
+
* @param _newSelloutPrice New sellout price to set for the auction. Must
|
|
456
|
+
* be less than the current sellout price.
|
|
457
|
+
*/
|
|
458
|
+
function adminEmergencyReduceSelloutPrice(
|
|
459
|
+
uint256 _projectId,
|
|
460
|
+
uint256 _newSelloutPrice
|
|
461
|
+
)
|
|
462
|
+
external
|
|
463
|
+
onlyCoreAdminACL(this.adminEmergencyReduceSelloutPrice.selector)
|
|
464
|
+
{
|
|
465
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
466
|
+
require(_projectConfig.maxHasBeenInvoked, "Auction must be complete");
|
|
467
|
+
// @dev no need to check that auction max invocations has been reached,
|
|
468
|
+
// because if it was, the sellout price will be zero, and the following
|
|
469
|
+
// check will fail.
|
|
470
|
+
require(
|
|
471
|
+
_newSelloutPrice < _projectConfig.latestPurchasePrice,
|
|
472
|
+
"May only reduce sellout price"
|
|
473
|
+
);
|
|
474
|
+
require(
|
|
475
|
+
_newSelloutPrice >= _projectConfig.basePrice,
|
|
476
|
+
"May only reduce sellout price to base price or greater"
|
|
477
|
+
);
|
|
478
|
+
// ensure latestPurchasePrice is non-zero if any purchases on minter
|
|
479
|
+
// @dev only possible to fail this if auction is in a reset state
|
|
480
|
+
require(_newSelloutPrice > 0, "Only sellout prices > 0");
|
|
481
|
+
require(
|
|
482
|
+
!_projectConfig.auctionRevenuesCollected,
|
|
483
|
+
"Only before revenues collected"
|
|
484
|
+
);
|
|
485
|
+
_projectConfig.latestPurchasePrice = _newSelloutPrice;
|
|
486
|
+
emit SelloutPriceUpdated(_projectId, _newSelloutPrice);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* @notice This withdraws project revenues for the artist and admin.
|
|
491
|
+
* This function is only callable by the artist or admin, and only after
|
|
492
|
+
* one of the following is true:
|
|
493
|
+
* - the auction has sold out above base price
|
|
494
|
+
* - the auction has reached base price
|
|
495
|
+
* Note that revenues are not claimable if in a temporary state after
|
|
496
|
+
* an auction is reset.
|
|
497
|
+
* Revenues may only be collected a single time per project.
|
|
498
|
+
* After revenues are collected, auction parameters will never be allowed
|
|
499
|
+
* to be reset, and excess settlement funds will become immutable and fully
|
|
500
|
+
* deterministic.
|
|
501
|
+
*/
|
|
502
|
+
function withdrawArtistAndAdminRevenues(
|
|
503
|
+
uint256 _projectId
|
|
504
|
+
)
|
|
505
|
+
external
|
|
506
|
+
nonReentrant
|
|
507
|
+
onlyCoreAdminACLOrArtist(
|
|
508
|
+
_projectId,
|
|
509
|
+
this.withdrawArtistAndAdminRevenues.selector
|
|
510
|
+
)
|
|
511
|
+
{
|
|
512
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
513
|
+
// CHECKS
|
|
514
|
+
// require revenues to not have already been collected
|
|
515
|
+
require(
|
|
516
|
+
!_projectConfig.auctionRevenuesCollected,
|
|
517
|
+
"Revenues already collected"
|
|
518
|
+
);
|
|
519
|
+
// get the current net price of the auction - reverts if no auction
|
|
520
|
+
// is configured.
|
|
521
|
+
// @dev _getPrice is guaranteed <= _projectConfig.latestPurchasePrice,
|
|
522
|
+
// since this minter enforces monotonically decreasing purchase prices.
|
|
523
|
+
uint256 _price = _getPrice(_projectId);
|
|
524
|
+
// if the price is not base price, require that the auction have
|
|
525
|
+
// reached max invocations. This prevents premature withdrawl
|
|
526
|
+
// before final auction price is possible to know.
|
|
527
|
+
if (_price != _projectConfig.basePrice) {
|
|
528
|
+
// prefer to use locally cached value of maxHasBeenInvoked, which
|
|
529
|
+
// is only updated when a purchase is made. This is to handle the
|
|
530
|
+
// case where an artist reduced max invocations to current
|
|
531
|
+
// invocations on the core contract mid-auction. In that case, the
|
|
532
|
+
// the following _projectConfig.maxHasBeenInvoked check will fail
|
|
533
|
+
// (only a local cache is used). This is a valid state, and in that
|
|
534
|
+
// somewhat suspicious case, the artist must wait until the auction
|
|
535
|
+
// reaches base price before withdrawing funds, at which point the
|
|
536
|
+
// latestPurchasePrice will be set to base price, maximizing
|
|
537
|
+
// purchaser excess settlement amounts, and minimizing artist/admin
|
|
538
|
+
// revenue.
|
|
539
|
+
require(
|
|
540
|
+
_projectConfig.maxHasBeenInvoked,
|
|
541
|
+
"Active auction not yet sold out"
|
|
542
|
+
);
|
|
543
|
+
} else {
|
|
544
|
+
// update the latest purchase price to the base price, to ensure
|
|
545
|
+
// the base price is used for all future settlement calculations
|
|
546
|
+
_projectConfig.latestPurchasePrice = _projectConfig.basePrice;
|
|
547
|
+
}
|
|
548
|
+
// EFFECTS
|
|
549
|
+
_projectConfig.auctionRevenuesCollected = true;
|
|
550
|
+
// if the price is base price, the auction is valid and may be claimed
|
|
551
|
+
// calculate the artist and admin revenues (no check requuired)
|
|
552
|
+
uint256 netRevenues = _projectConfig.numSettleableInvocations * _price;
|
|
553
|
+
// INTERACTIONS
|
|
554
|
+
splitRevenuesETH(_projectId, netRevenues, genArt721CoreAddress);
|
|
555
|
+
emit ArtistAndAdminRevenuesWithdrawn(_projectId);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* @notice Purchases a token from project `_projectId`.
|
|
560
|
+
* @param _projectId Project ID to mint a token on.
|
|
561
|
+
* @return tokenId Token ID of minted token
|
|
562
|
+
*/
|
|
563
|
+
function purchase(
|
|
564
|
+
uint256 _projectId
|
|
565
|
+
) external payable returns (uint256 tokenId) {
|
|
566
|
+
tokenId = purchaseTo_do6(msg.sender, _projectId);
|
|
567
|
+
return tokenId;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* @notice gas-optimized version of purchase(uint256).
|
|
572
|
+
*/
|
|
573
|
+
function purchase_H4M(
|
|
574
|
+
uint256 _projectId
|
|
575
|
+
) external payable returns (uint256 tokenId) {
|
|
576
|
+
tokenId = purchaseTo_do6(msg.sender, _projectId);
|
|
577
|
+
return tokenId;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* @notice Purchases a token from project `_projectId` and sets
|
|
582
|
+
* the token's owner to `_to`.
|
|
583
|
+
* @param _to Address to be the new token's owner.
|
|
584
|
+
* @param _projectId Project ID to mint a token on.
|
|
585
|
+
* @return tokenId Token ID of minted token
|
|
586
|
+
*/
|
|
587
|
+
function purchaseTo(
|
|
588
|
+
address _to,
|
|
589
|
+
uint256 _projectId
|
|
590
|
+
) external payable returns (uint256 tokenId) {
|
|
591
|
+
return purchaseTo_do6(_to, _projectId);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* @notice gas-optimized version of purchaseTo(address, uint256).
|
|
596
|
+
*/
|
|
597
|
+
function purchaseTo_do6(
|
|
598
|
+
address _to,
|
|
599
|
+
uint256 _projectId
|
|
600
|
+
) public payable nonReentrant returns (uint256 tokenId) {
|
|
601
|
+
// CHECKS
|
|
602
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
603
|
+
|
|
604
|
+
// Note that `maxHasBeenInvoked` is only checked here to reduce gas
|
|
605
|
+
// consumption after a project has been fully minted.
|
|
606
|
+
// `_projectConfig.maxHasBeenInvoked` is locally cached during every
|
|
607
|
+
// purchase to reduce gas consumption and enable recording of sellout
|
|
608
|
+
// price, but if not in sync with the core contract's value,
|
|
609
|
+
// the core contract also enforces its own max invocation check during
|
|
610
|
+
// minting.
|
|
611
|
+
require(
|
|
612
|
+
!_projectConfig.maxHasBeenInvoked,
|
|
613
|
+
"Maximum number of invocations reached"
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
// _getPrice reverts if auction is unconfigured or has not started
|
|
617
|
+
uint256 currentPriceInWei = _getPrice(_projectId);
|
|
618
|
+
|
|
619
|
+
// EFFECTS
|
|
620
|
+
// update the purchaser's receipt and require sufficient net payment
|
|
621
|
+
Receipt storage receipt = receipts[msg.sender][_projectId];
|
|
622
|
+
|
|
623
|
+
// in memory copy + update
|
|
624
|
+
uint256 netPosted = receipt.netPosted + msg.value;
|
|
625
|
+
uint256 numPurchased = receipt.numPurchased + 1;
|
|
626
|
+
|
|
627
|
+
// require sufficient payment on project
|
|
628
|
+
require(
|
|
629
|
+
netPosted >= numPurchased * currentPriceInWei,
|
|
630
|
+
"Must send minimum value to mint"
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
// update Receipt in storage
|
|
634
|
+
// @dev overflow checks are not required since the added values cannot
|
|
635
|
+
// be enough to overflow due to maximum invocations or supply of ETH
|
|
636
|
+
receipt.netPosted = uint232(netPosted);
|
|
637
|
+
receipt.numPurchased = uint24(numPurchased);
|
|
638
|
+
|
|
639
|
+
// emit event indicating new receipt state
|
|
640
|
+
emit ReceiptUpdated(msg.sender, _projectId, numPurchased, netPosted);
|
|
641
|
+
|
|
642
|
+
// update latest purchase price (on this minter) in storage
|
|
643
|
+
// @dev this is used to enforce monotonically decreasing purchase price
|
|
644
|
+
// across multiple auctions
|
|
645
|
+
_projectConfig.latestPurchasePrice = currentPriceInWei;
|
|
646
|
+
|
|
647
|
+
tokenId = minterFilter.mint(_to, _projectId, msg.sender);
|
|
648
|
+
|
|
649
|
+
// Note that this requires that the core contract's maxInvocations
|
|
650
|
+
// be accurate to ensure that the minters maxHasBeenInvoked is
|
|
651
|
+
// accurate, so we get the value from the core contract directly.
|
|
652
|
+
uint256 maxInvocations;
|
|
653
|
+
(, maxInvocations, , , , ) = genArtCoreContract_Base.projectStateData(
|
|
654
|
+
_projectId
|
|
655
|
+
);
|
|
656
|
+
// okay if this underflows because if statement will always eval false.
|
|
657
|
+
// this is only for gas optimization and recording sellout price in
|
|
658
|
+
// an event (core enforces maxInvocations).
|
|
659
|
+
unchecked {
|
|
660
|
+
if (tokenId % ONE_MILLION == maxInvocations - 1) {
|
|
661
|
+
_projectConfig.maxHasBeenInvoked = true;
|
|
662
|
+
emit SelloutPriceUpdated(_projectId, currentPriceInWei);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// INTERACTIONS
|
|
667
|
+
if (_projectConfig.auctionRevenuesCollected) {
|
|
668
|
+
// if revenues have been collected, split funds immediately.
|
|
669
|
+
// @dev note that we are guaranteed to be at auction base price,
|
|
670
|
+
// since we know we didn't sellout prior to this tx.
|
|
671
|
+
// note that we don't refund msg.sender here, since a separate
|
|
672
|
+
// settlement mechanism is provided on this minter, unrelated to
|
|
673
|
+
// msg.value
|
|
674
|
+
splitRevenuesETH(
|
|
675
|
+
_projectId,
|
|
676
|
+
currentPriceInWei,
|
|
677
|
+
genArt721CoreAddress
|
|
678
|
+
);
|
|
679
|
+
} else {
|
|
680
|
+
// increment the number of settleable invocations that will be
|
|
681
|
+
// claimable by the artist and admin once auction is validated.
|
|
682
|
+
// do not split revenue here since will be claimed at a later time.
|
|
683
|
+
_projectConfig.numSettleableInvocations++;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return tokenId;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* @notice Reclaims the sender's payment above current settled price for
|
|
691
|
+
* project `_projectId`. The current settled price is the the price paid
|
|
692
|
+
* for the most recently purchased token, or the base price if the artist
|
|
693
|
+
* has withdrawn revenues after the auction reached base price.
|
|
694
|
+
* This function is callable at any point, but is expected to typically be
|
|
695
|
+
* called after auction has sold out above base price or after the auction
|
|
696
|
+
* has been purchased at base price. This minimizes the amount of gas
|
|
697
|
+
* required to send all excess settlement funds to the sender.
|
|
698
|
+
* Sends excess settlement funds to msg.sender.
|
|
699
|
+
* @param _projectId Project ID to reclaim excess settlement funds on.
|
|
700
|
+
*/
|
|
701
|
+
function reclaimProjectExcessSettlementFunds(uint256 _projectId) external {
|
|
702
|
+
reclaimProjectExcessSettlementFundsTo(payable(msg.sender), _projectId);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* @notice Reclaims the sender's payment above current settled price for
|
|
707
|
+
* project `_projectId`. The current settled price is the the price paid
|
|
708
|
+
* for the most recently purchased token, or the base price if the artist
|
|
709
|
+
* has withdrawn revenues after the auction reached base price.
|
|
710
|
+
* This function is callable at any point, but is expected to typically be
|
|
711
|
+
* called after auction has sold out above base price or after the auction
|
|
712
|
+
* has been purchased at base price. This minimizes the amount of gas
|
|
713
|
+
* required to send all excess settlement funds.
|
|
714
|
+
* Sends excess settlement funds to address `_to`.
|
|
715
|
+
* @param _to Address to send excess settlement funds to.
|
|
716
|
+
* @param _projectId Project ID to reclaim excess settlement funds on.
|
|
717
|
+
*/
|
|
718
|
+
function reclaimProjectExcessSettlementFundsTo(
|
|
719
|
+
address payable _to,
|
|
720
|
+
uint256 _projectId
|
|
721
|
+
) public nonReentrant {
|
|
722
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
723
|
+
Receipt storage receipt = receipts[msg.sender][_projectId];
|
|
724
|
+
uint256 numPurchased = receipt.numPurchased;
|
|
725
|
+
// CHECKS
|
|
726
|
+
// input validation
|
|
727
|
+
require(_to != address(0), "No claiming to the zero address");
|
|
728
|
+
// require that a user has purchased at least one token on this project
|
|
729
|
+
require(numPurchased > 0, "No purchases made by this address");
|
|
730
|
+
// get the latestPurchasePrice, which returns the sellout price if the
|
|
731
|
+
// auction sold out before reaching base price, or returns the base
|
|
732
|
+
// price if auction has reached base price and artist has withdrawn
|
|
733
|
+
// revenues.
|
|
734
|
+
// @dev if user is eligible for a reclaiming, they have purchased a
|
|
735
|
+
// token, therefore we are guaranteed to have a populated
|
|
736
|
+
// latestPurchasePrice
|
|
737
|
+
uint256 currentSettledTokenPrice = _projectConfig.latestPurchasePrice;
|
|
738
|
+
|
|
739
|
+
// EFFECTS
|
|
740
|
+
// calculate the excess settlement funds amount
|
|
741
|
+
// implicit overflow/underflow checks in solidity ^0.8
|
|
742
|
+
uint256 requiredAmountPosted = numPurchased * currentSettledTokenPrice;
|
|
743
|
+
uint256 excessSettlementFunds = receipt.netPosted -
|
|
744
|
+
requiredAmountPosted;
|
|
745
|
+
// update Receipt in storage
|
|
746
|
+
receipt.netPosted = requiredAmountPosted.toUint232();
|
|
747
|
+
// emit event indicating new receipt state
|
|
748
|
+
emit ReceiptUpdated(
|
|
749
|
+
msg.sender,
|
|
750
|
+
_projectId,
|
|
751
|
+
numPurchased,
|
|
752
|
+
requiredAmountPosted
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
// INTERACTIONS
|
|
756
|
+
bool success_;
|
|
757
|
+
(success_, ) = _to.call{value: excessSettlementFunds}("");
|
|
758
|
+
require(success_, "Reclaiming failed");
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* @notice Reclaims the sender's payment above current settled price for
|
|
763
|
+
* projects in `_projectIds`. The current settled price is the the price
|
|
764
|
+
* paid for the most recently purchased token, or the base price if the
|
|
765
|
+
* artist has withdrawn revenues after the auction reached base price.
|
|
766
|
+
* This function is callable at any point, but is expected to typically be
|
|
767
|
+
* called after auction has sold out above base price or after the auction
|
|
768
|
+
* has been purchased at base price. This minimizes the amount of gas
|
|
769
|
+
* required to send all excess settlement funds to the sender.
|
|
770
|
+
* Sends total of all excess settlement funds to msg.sender in a single
|
|
771
|
+
* chunk. Entire transaction reverts if any excess settlement calculation
|
|
772
|
+
* fails.
|
|
773
|
+
* @param _projectIds Array of project IDs to reclaim excess settlement
|
|
774
|
+
* funds on.
|
|
775
|
+
*/
|
|
776
|
+
function reclaimProjectsExcessSettlementFunds(
|
|
777
|
+
uint256[] calldata _projectIds
|
|
778
|
+
) external {
|
|
779
|
+
reclaimProjectsExcessSettlementFundsTo(
|
|
780
|
+
payable(msg.sender),
|
|
781
|
+
_projectIds
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* @notice Reclaims the sender's payment above current settled price for
|
|
787
|
+
* projects in `_projectIds`. The current settled price is the the price
|
|
788
|
+
* paid for the most recently purchased token, or the base price if the
|
|
789
|
+
* artist has withdrawn revenues after the auction reached base price.
|
|
790
|
+
* This function is callable at any point, but is expected to typically be
|
|
791
|
+
* called after auction has sold out above base price or after the auction
|
|
792
|
+
* has been purchased at base price. This minimizes the amount of gas
|
|
793
|
+
* required to send all excess settlement funds to the sender.
|
|
794
|
+
* Sends total of all excess settlement funds to `_to` in a single
|
|
795
|
+
* chunk. Entire transaction reverts if any excess settlement calculation
|
|
796
|
+
* fails.
|
|
797
|
+
* @param _to Address to send excess settlement funds to.
|
|
798
|
+
* @param _projectIds Array of project IDs to reclaim excess settlement
|
|
799
|
+
* funds on.
|
|
800
|
+
*/
|
|
801
|
+
function reclaimProjectsExcessSettlementFundsTo(
|
|
802
|
+
address payable _to,
|
|
803
|
+
uint256[] memory _projectIds
|
|
804
|
+
) public nonReentrant {
|
|
805
|
+
// CHECKS
|
|
806
|
+
// input validation
|
|
807
|
+
require(_to != address(0), "No claiming to the zero address");
|
|
808
|
+
// EFFECTS
|
|
809
|
+
// for each project, tally up the excess settlement funds and update
|
|
810
|
+
// the receipt in storage
|
|
811
|
+
uint256 excessSettlementFunds;
|
|
812
|
+
uint256 projectIdsLength = _projectIds.length;
|
|
813
|
+
for (uint256 i; i < projectIdsLength; ) {
|
|
814
|
+
uint256 projectId = _projectIds[i];
|
|
815
|
+
ProjectConfig storage _projectConfig = projectConfig[projectId];
|
|
816
|
+
Receipt storage receipt = receipts[msg.sender][projectId];
|
|
817
|
+
uint256 numPurchased = receipt.numPurchased;
|
|
818
|
+
// input validation
|
|
819
|
+
// require that a user has purchased at least one token on this project
|
|
820
|
+
require(numPurchased > 0, "No purchases made by this address");
|
|
821
|
+
// get the latestPurchasePrice, which returns the sellout price if the
|
|
822
|
+
// auction sold out before reaching base price, or returns the base
|
|
823
|
+
// price if auction has reached base price and artist has withdrawn
|
|
824
|
+
// revenues.
|
|
825
|
+
// @dev if user is eligible for a claim, they have purchased a token,
|
|
826
|
+
// therefore we are guaranteed to have a populated
|
|
827
|
+
// latestPurchasePrice
|
|
828
|
+
uint256 currentSettledTokenPrice = _projectConfig
|
|
829
|
+
.latestPurchasePrice;
|
|
830
|
+
// calculate the excessSettlementFunds amount
|
|
831
|
+
// implicit overflow/underflow checks in solidity ^0.8
|
|
832
|
+
uint256 requiredAmountPosted = numPurchased *
|
|
833
|
+
currentSettledTokenPrice;
|
|
834
|
+
excessSettlementFunds += (receipt.netPosted - requiredAmountPosted);
|
|
835
|
+
// reduce the netPosted (in storage) to value after excess settlement
|
|
836
|
+
// funds deducted
|
|
837
|
+
receipt.netPosted = requiredAmountPosted.toUint232();
|
|
838
|
+
// emit event indicating new receipt state
|
|
839
|
+
emit ReceiptUpdated(
|
|
840
|
+
msg.sender,
|
|
841
|
+
projectId,
|
|
842
|
+
numPurchased,
|
|
843
|
+
requiredAmountPosted
|
|
844
|
+
);
|
|
845
|
+
// gas efficiently increment i
|
|
846
|
+
// won't overflow due to for loop, as well as gas limts
|
|
847
|
+
unchecked {
|
|
848
|
+
++i;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// INTERACTIONS
|
|
853
|
+
// send excess settlement funds in a single chunk for all
|
|
854
|
+
// projects
|
|
855
|
+
bool success_;
|
|
856
|
+
(success_, ) = _to.call{value: excessSettlementFunds}("");
|
|
857
|
+
require(success_, "Reclaiming failed");
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* @notice Gets price of minting a token on project `_projectId` given
|
|
862
|
+
* the project's AuctionParameters and current block timestamp.
|
|
863
|
+
* Reverts if auction has not yet started or auction is unconfigured.
|
|
864
|
+
* Returns auction last purchase price if auction sold out before reaching
|
|
865
|
+
* base price.
|
|
866
|
+
* @param _projectId Project ID to get price of token for.
|
|
867
|
+
* @return current price of token in Wei
|
|
868
|
+
* @dev This method calculates price decay using a linear interpolation
|
|
869
|
+
* of exponential decay based on the artist-provided half-life for price
|
|
870
|
+
* decay, `_priceDecayHalfLifeSeconds`.
|
|
871
|
+
*/
|
|
872
|
+
function _getPrice(uint256 _projectId) private view returns (uint256) {
|
|
873
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
874
|
+
// if auction sold out on this minter, return the latest purchase
|
|
875
|
+
// price (which is the sellout price). This is the price that is due
|
|
876
|
+
// after an auction is complete.
|
|
877
|
+
if (_projectConfig.maxHasBeenInvoked) {
|
|
878
|
+
return _projectConfig.latestPurchasePrice;
|
|
879
|
+
}
|
|
880
|
+
// otherwise calculate price based on current block timestamp and
|
|
881
|
+
// auction configuration (will revert if auction has not started)
|
|
882
|
+
// move parameters to memory if used more than once
|
|
883
|
+
uint256 _timestampStart = uint256(_projectConfig.timestampStart);
|
|
884
|
+
uint256 _priceDecayHalfLifeSeconds = uint256(
|
|
885
|
+
_projectConfig.priceDecayHalfLifeSeconds
|
|
886
|
+
);
|
|
887
|
+
uint256 _basePrice = _projectConfig.basePrice;
|
|
888
|
+
|
|
889
|
+
require(block.timestamp > _timestampStart, "Auction not yet started");
|
|
890
|
+
require(_priceDecayHalfLifeSeconds > 0, "Only configured auctions");
|
|
891
|
+
uint256 decayedPrice = _projectConfig.startPrice;
|
|
892
|
+
uint256 elapsedTimeSeconds;
|
|
893
|
+
unchecked {
|
|
894
|
+
// already checked that block.timestamp > _timestampStart above
|
|
895
|
+
elapsedTimeSeconds = block.timestamp - _timestampStart;
|
|
896
|
+
}
|
|
897
|
+
// Divide by two (via bit-shifting) for the number of entirely completed
|
|
898
|
+
// half-lives that have elapsed since auction start time.
|
|
899
|
+
unchecked {
|
|
900
|
+
// already required _priceDecayHalfLifeSeconds > 0
|
|
901
|
+
decayedPrice >>= elapsedTimeSeconds / _priceDecayHalfLifeSeconds;
|
|
902
|
+
}
|
|
903
|
+
// Perform a linear interpolation between partial half-life points, to
|
|
904
|
+
// approximate the current place on a perfect exponential decay curve.
|
|
905
|
+
unchecked {
|
|
906
|
+
// value of expression is provably always less than decayedPrice,
|
|
907
|
+
// so no underflow is possible when the subtraction assignment
|
|
908
|
+
// operator is used on decayedPrice.
|
|
909
|
+
decayedPrice -=
|
|
910
|
+
(decayedPrice *
|
|
911
|
+
(elapsedTimeSeconds % _priceDecayHalfLifeSeconds)) /
|
|
912
|
+
_priceDecayHalfLifeSeconds /
|
|
913
|
+
2;
|
|
914
|
+
}
|
|
915
|
+
if (decayedPrice < _basePrice) {
|
|
916
|
+
// Price may not decay below stay `basePrice`.
|
|
917
|
+
return _basePrice;
|
|
918
|
+
}
|
|
919
|
+
return decayedPrice;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* @notice Gets the current excess settlement funds on project `_projectId`
|
|
924
|
+
* for address `_walletAddress`. The returned value is expected to change
|
|
925
|
+
* throughtout an auction, since the latest purchase price is used when
|
|
926
|
+
* determining excess settlement funds.
|
|
927
|
+
* A user may claim excess settlement funds by calling the function
|
|
928
|
+
* `reclaimProjectExcessSettlementFunds(_projectId)`.
|
|
929
|
+
* @param _projectId Project ID to query.
|
|
930
|
+
* @param _walletAddress Account address for which the excess posted funds
|
|
931
|
+
* is being queried.
|
|
932
|
+
* @return excessSettlementFundsInWei Amount of excess settlement funds, in
|
|
933
|
+
* wei
|
|
934
|
+
*/
|
|
935
|
+
function getProjectExcessSettlementFunds(
|
|
936
|
+
uint256 _projectId,
|
|
937
|
+
address _walletAddress
|
|
938
|
+
) external view returns (uint256 excessSettlementFundsInWei) {
|
|
939
|
+
// input validation
|
|
940
|
+
require(_walletAddress != address(0), "No zero address");
|
|
941
|
+
// load struct from storage
|
|
942
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
943
|
+
Receipt storage receipt = receipts[_walletAddress][_projectId];
|
|
944
|
+
// require that a user has purchased at least one token on this project
|
|
945
|
+
require(receipt.numPurchased > 0, "No purchases made by this address");
|
|
946
|
+
// get the latestPurchasePrice, which returns the sellout price if the
|
|
947
|
+
// auction sold out before reaching base price, or returns the base
|
|
948
|
+
// price if auction has reached base price and artist has withdrawn
|
|
949
|
+
// revenues.
|
|
950
|
+
// @dev if user is eligible for a reclaiming, they have purchased a
|
|
951
|
+
// token, therefore we are guaranteed to have a populated
|
|
952
|
+
// latestPurchasePrice
|
|
953
|
+
uint256 currentSettledTokenPrice = _projectConfig.latestPurchasePrice;
|
|
954
|
+
|
|
955
|
+
// EFFECTS
|
|
956
|
+
// calculate the excess settlement funds amount and return
|
|
957
|
+
// implicit overflow/underflow checks in solidity ^0.8
|
|
958
|
+
uint256 requiredAmountPosted = receipt.numPurchased *
|
|
959
|
+
currentSettledTokenPrice;
|
|
960
|
+
excessSettlementFundsInWei = receipt.netPosted - requiredAmountPosted;
|
|
961
|
+
return excessSettlementFundsInWei;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* @notice Gets the latest purchase price for project `_projectId`, or 0 if
|
|
966
|
+
* no purchases have been made.
|
|
967
|
+
*/
|
|
968
|
+
function getProjectLatestPurchasePrice(
|
|
969
|
+
uint256 _projectId
|
|
970
|
+
) external view returns (uint256 latestPurchasePrice) {
|
|
971
|
+
return projectConfig[_projectId].latestPurchasePrice;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* @notice Gets the number of settleable invocations for project `_projectId`.
|
|
976
|
+
*/
|
|
977
|
+
function getNumSettleableInvocations(
|
|
978
|
+
uint256 _projectId
|
|
979
|
+
) external view returns (uint256 numSettleableInvocations) {
|
|
980
|
+
return projectConfig[_projectId].numSettleableInvocations;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* @notice Gets if price of token is configured, price of minting a
|
|
985
|
+
* token on project `_projectId`, and currency symbol and address to be
|
|
986
|
+
* used as payment. Supersedes any core contract price information.
|
|
987
|
+
* @param _projectId Project ID to get price information for.
|
|
988
|
+
* @return isConfigured true only if project's auction parameters have been
|
|
989
|
+
* configured on this minter
|
|
990
|
+
* @return tokenPriceInWei current price of token on this minter - invalid
|
|
991
|
+
* if auction has not yet been configured
|
|
992
|
+
* @return currencySymbol currency symbol for purchases of project on this
|
|
993
|
+
* minter. This minter always returns "ETH"
|
|
994
|
+
* @return currencyAddress currency address for purchases of project on
|
|
995
|
+
* this minter. This minter always returns null address, reserved for ether
|
|
996
|
+
*/
|
|
997
|
+
function getPriceInfo(
|
|
998
|
+
uint256 _projectId
|
|
999
|
+
)
|
|
1000
|
+
external
|
|
1001
|
+
view
|
|
1002
|
+
returns (
|
|
1003
|
+
bool isConfigured,
|
|
1004
|
+
uint256 tokenPriceInWei,
|
|
1005
|
+
string memory currencySymbol,
|
|
1006
|
+
address currencyAddress
|
|
1007
|
+
)
|
|
1008
|
+
{
|
|
1009
|
+
ProjectConfig storage _projectConfig = projectConfig[_projectId];
|
|
1010
|
+
|
|
1011
|
+
isConfigured = (_projectConfig.startPrice > 0);
|
|
1012
|
+
if (block.timestamp <= _projectConfig.timestampStart) {
|
|
1013
|
+
// Provide a reasonable value for `tokenPriceInWei` when it would
|
|
1014
|
+
// otherwise revert, using the starting price before auction starts.
|
|
1015
|
+
tokenPriceInWei = _projectConfig.startPrice;
|
|
1016
|
+
} else if (_projectConfig.startPrice == 0) {
|
|
1017
|
+
// In the case of unconfigured auction, return price of zero when
|
|
1018
|
+
// it would otherwise revert
|
|
1019
|
+
tokenPriceInWei = 0;
|
|
1020
|
+
} else {
|
|
1021
|
+
tokenPriceInWei = _getPrice(_projectId);
|
|
1022
|
+
}
|
|
1023
|
+
currencySymbol = "ETH";
|
|
1024
|
+
currencyAddress = address(0);
|
|
1025
|
+
}
|
|
1026
|
+
}
|