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