@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.
Files changed (138) hide show
  1. package/README.md +30 -11
  2. package/artifacts/contracts/AdminACLV1.sol/AdminACLV1.json +2 -2
  3. package/artifacts/contracts/BasicPolyptychRandomizerV0.sol/BasicPolyptychRandomizerV0.json +229 -0
  4. package/artifacts/contracts/BasicRandomizerV2.sol/BasicRandomizerV2.json +3 -3
  5. package/artifacts/contracts/GenArt721CoreV3.sol/GenArt721CoreV3.json +2 -2
  6. package/artifacts/contracts/GenArt721CoreV3_Engine.sol/GenArt721CoreV3_Engine.json +2 -2
  7. package/artifacts/contracts/archive/GenArt721CoreV0.sol/Address.json +10 -0
  8. package/artifacts/contracts/archive/GenArt721CoreV0.sol/Counters.json +10 -0
  9. package/artifacts/contracts/archive/GenArt721CoreV0.sol/CustomERC721Metadata.json +425 -0
  10. package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC165.json +38 -0
  11. package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721.json +322 -0
  12. package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721Enumerable.json +384 -0
  13. package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721ReceiverMock.json +101 -0
  14. package/artifacts/contracts/archive/GenArt721CoreV0.sol/GenArt721.json +1528 -0
  15. package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC165.json +32 -0
  16. package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721.json +316 -0
  17. package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721Enumerable.json +378 -0
  18. package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721Receiver.json +47 -0
  19. package/artifacts/contracts/archive/GenArt721CoreV0.sol/SafeMath.json +10 -0
  20. package/artifacts/contracts/archive/GenArt721CoreV0.sol/Strings.json +10 -0
  21. package/artifacts/contracts/archive/GenArt721CoreV1.sol/Address.json +10 -0
  22. package/artifacts/contracts/archive/GenArt721CoreV1.sol/Counters.json +10 -0
  23. package/artifacts/contracts/archive/GenArt721CoreV1.sol/CustomERC721Metadata.json +425 -0
  24. package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC165.json +38 -0
  25. package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC721.json +322 -0
  26. package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC721Enumerable.json +384 -0
  27. package/artifacts/contracts/archive/GenArt721CoreV1.sol/GenArt721Core.json +1773 -0
  28. package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC165.json +32 -0
  29. package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721.json +316 -0
  30. package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721Enumerable.json +378 -0
  31. package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721Receiver.json +47 -0
  32. package/artifacts/contracts/archive/GenArt721CoreV1.sol/Randomizer.json +26 -0
  33. package/artifacts/contracts/archive/GenArt721CoreV1.sol/SafeMath.json +10 -0
  34. package/artifacts/contracts/archive/GenArt721CoreV1.sol/Strings.json +10 -0
  35. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV0.sol/MinterDAExpV0.json +575 -0
  36. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV1.sol/MinterDAExpV1.json +463 -0
  37. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV2.sol/MinterDAExpV2.json +3 -3
  38. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV3.sol/MinterDAExpV3.json +3 -3
  39. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol/MinterDAExpSettlementV0.json +15 -15
  40. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV1.sol/MinterDAExpSettlementV1.json +1066 -0
  41. package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV0.sol/MinterDALinV0.json +551 -0
  42. package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV1.sol/MinterDALinV1.json +439 -0
  43. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV2.sol/MinterDALinV2.json +3 -3
  44. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV3.sol/MinterDALinV3.json +3 -3
  45. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV0.sol/MinterHolderV0.json +622 -0
  46. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol/MinterHolderV1.json +719 -0
  47. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +794 -0
  48. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV3.sol/MinterHolderV3.json +3 -3
  49. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV0.sol/MinterMerkleV0.json +873 -0
  50. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol/MinterMerkleV1.json +932 -0
  51. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol/MinterMerkleV2.json +984 -0
  52. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV3.sol/MinterMerkleV3.json +3 -3
  53. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV4.sol/MinterMerkleV4.json +3 -3
  54. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV0.sol/MinterSetPriceV0.json +400 -0
  55. package/artifacts/contracts/{minter-suite/Minters/MinterSetPrice/deprecated → archive/minter-suite/Minters/MinterSetPrice}/MinterSetPriceV1.sol/MinterSetPriceV1.json +3 -3
  56. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV2.sol/MinterSetPriceV2.json +3 -3
  57. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV3.sol/MinterSetPriceV3.json +3 -3
  58. package/artifacts/contracts/{minter-suite/Minters/MinterSetPriceERC20/deprecated → archive/minter-suite/Minters/MinterSetPriceERC20}/MinterSetPriceERC20V0.sol/MinterSetPriceERC20V0.json +3 -3
  59. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V1.sol/MinterSetPriceERC20V1.json +349 -0
  60. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol/MinterSetPriceERC20V2.json +3 -3
  61. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol/MinterSetPriceERC20V3.json +3 -3
  62. package/artifacts/contracts/explorations/GenArt721CoreV3_Explorations.sol/GenArt721CoreV3_Explorations.json +2 -2
  63. package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV0.sol/IFilteredMinterDAExpSettlementV0.json +12 -12
  64. package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV1.sol/IFilteredMinterDAExpSettlementV1.json +767 -0
  65. package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlement_Mixin.sol/IFilteredMinterDAExpSettlement_Mixin.json +161 -0
  66. package/artifacts/contracts/interfaces/0.8.x/IMinterBaseV0.sol/IMinterBaseV0.json +24 -0
  67. package/artifacts/contracts/interfaces/0.8.x/IMinterFilterV0.sol/IMinterFilterV0.json +6 -0
  68. package/artifacts/contracts/interfaces/0.8.x/IMintingAPICoreV0.sol/IMintingAPICoreV0.json +185 -0
  69. package/artifacts/contracts/interfaces/0.8.x/IMintingAPIMinterV0.sol/IMintingAPIMinterV0.json +88 -0
  70. package/artifacts/contracts/interfaces/0.8.x/IRandomizerPolyptychV0.sol/IRandomizerPolyptychV0.json +87 -0
  71. package/artifacts/contracts/interfaces/0.8.x/IRandomizerV2.sol/IRandomizerV2.json +1 -1
  72. package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV0.sol/MinterFilterV0.json +8 -2
  73. package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV1.sol/MinterFilterV1.json +34 -2
  74. package/artifacts/contracts/minter-suite/Minters/MinterBase_v0_1_1.sol/MinterBase.json +24 -0
  75. package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol/MinterDAExpSettlementV2.json +1127 -0
  76. package/artifacts/contracts/minter-suite/Minters/MinterDAExpV4.sol/MinterDAExpV4.json +882 -0
  77. package/artifacts/contracts/minter-suite/Minters/MinterDALinV4.sol/MinterDALinV4.json +858 -0
  78. package/artifacts/contracts/minter-suite/Minters/MinterHolderV4.sol/MinterHolderV4.json +1126 -0
  79. package/artifacts/contracts/minter-suite/Minters/MinterMerkleV5.sol/MinterMerkleV5.json +1065 -0
  80. package/artifacts/contracts/minter-suite/Minters/MinterPolyptychV0.sol/IGenArt721CoreContractV3WithRandomizer.json +662 -0
  81. package/artifacts/contracts/minter-suite/Minters/MinterPolyptychV0.sol/MinterPolyptychV0.json +1268 -0
  82. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceERC20V4.sol/MinterSetPriceERC20V4.json +768 -0
  83. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceV4.sol/MinterSetPriceV4.json +697 -0
  84. package/artifacts/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol/GenArt721CoreV3_Engine_IncorrectCoreType.json +1817 -0
  85. package/artifacts/contracts/mock/RandomizerV2_NoAssignMock.sol/RandomizerV2_NoAssignMock.json +3 -3
  86. package/contracts/BasicPolyptychRandomizerV0.sol +126 -0
  87. package/contracts/BasicRandomizerV2.sol +3 -3
  88. package/contracts/archive/GenArt721CoreV0.sol +1621 -0
  89. package/contracts/archive/GenArt721CoreV1.sol +1577 -0
  90. package/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV0.sol +503 -0
  91. package/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV1.sol +442 -0
  92. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV2.sol +3 -3
  93. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV3.sol +3 -3
  94. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol +3 -3
  95. package/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV1.sol +1026 -0
  96. package/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV0.sol +450 -0
  97. package/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV1.sol +406 -0
  98. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV2.sol +3 -3
  99. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV3.sol +3 -3
  100. package/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV0.sol +540 -0
  101. package/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol +648 -0
  102. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV2.sol +6 -6
  103. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV3.sol +4 -4
  104. package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV0.sol +523 -0
  105. package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol +602 -0
  106. package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol +679 -0
  107. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV3.sol +4 -4
  108. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV4.sol +4 -4
  109. package/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV0.sol +333 -0
  110. package/contracts/{minter-suite/Minters/MinterSetPrice/deprecated → archive/minter-suite/Minters/MinterSetPrice}/MinterSetPriceV1.sol +0 -0
  111. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV2.sol +3 -3
  112. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV3.sol +3 -3
  113. package/contracts/{minter-suite/Minters/MinterSetPriceERC20/deprecated → archive/minter-suite/Minters/MinterSetPriceERC20}/MinterSetPriceERC20V0.sol +0 -0
  114. package/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V1.sol +411 -0
  115. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol +3 -3
  116. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol +3 -3
  117. package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV0.sol +4 -53
  118. package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV1.sol +21 -0
  119. package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlement_Mixin.sol +62 -0
  120. package/contracts/interfaces/0.8.x/IMinterBaseV0.sol +20 -0
  121. package/contracts/interfaces/0.8.x/IMinterFilterV0.sol +6 -0
  122. package/contracts/interfaces/0.8.x/IMintingAPICoreV0.sol +62 -0
  123. package/contracts/interfaces/0.8.x/IMintingAPIMinterV0.sol +31 -0
  124. package/contracts/interfaces/0.8.x/IRandomizerPolyptychV0.sol +31 -0
  125. package/contracts/interfaces/0.8.x/IRandomizerV2.sol +5 -2
  126. package/contracts/minter-suite/MinterFilter/MinterFilterV1.sol +18 -0
  127. package/contracts/minter-suite/Minters/MinterBase_v0_1_1.sol +293 -0
  128. package/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol +1312 -0
  129. package/contracts/minter-suite/Minters/MinterDAExpV4.sol +600 -0
  130. package/contracts/minter-suite/Minters/MinterDALinV4.sol +552 -0
  131. package/contracts/minter-suite/Minters/MinterHolderV4.sol +781 -0
  132. package/contracts/minter-suite/Minters/MinterMerkleV5.sol +717 -0
  133. package/contracts/minter-suite/Minters/MinterPolyptychV0.sol +998 -0
  134. package/contracts/minter-suite/Minters/MinterSetPriceERC20V4.sol +480 -0
  135. package/contracts/minter-suite/Minters/MinterSetPriceV4.sol +380 -0
  136. package/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol +1979 -0
  137. package/package.json +8 -2
  138. package/artifacts/contracts/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +0 -794
@@ -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
+ }