@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,2001 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-only
2
+ pragma solidity 0.8.17;
3
+
4
+ // Created By: Art Blocks Inc.
5
+
6
+ import "./interfaces/0.8.x/IRandomizerV2.sol";
7
+ import "./interfaces/0.8.x/IAdminACLV0.sol";
8
+ import "./interfaces/0.8.x/IGenArt721CoreContractV3.sol";
9
+ import "./interfaces/0.8.x/IManifold.sol";
10
+
11
+ import "@openzeppelin-4.7/contracts/utils/Strings.sol";
12
+ import "@openzeppelin-4.7/contracts/access/Ownable.sol";
13
+ import "./libs/0.8.x/ERC721_PackedHashSeed.sol";
14
+ import "./libs/0.8.x/BytecodeStorage.sol";
15
+ import "./libs/0.8.x/Bytes32Strings.sol";
16
+
17
+ /**
18
+ * @title Art Blocks ERC-721 core contract, V3.
19
+ * @author Art Blocks Inc.
20
+ * @notice Privileged Roles and Ownership:
21
+ * This contract is designed to be managed, with progressively limited powers
22
+ * as a project progresses from active to locked.
23
+ * Privileged roles and abilities are controlled by the admin ACL contract and
24
+ * artists. Both of these roles hold extensive power and can arbitrarily
25
+ * control and modify portions of projects, dependent upon project state. After
26
+ * a project is locked, important project metadata fields are locked including
27
+ * the project name, artist name, and script and display details. Edition size
28
+ * can never be increased.
29
+ * Care must be taken to ensure that the admin ACL contract and artist
30
+ * addresses are secure behind a multi-sig or other access control mechanism.
31
+ * ----------------------------------------------------------------------------
32
+ * The following functions are restricted to the Admin ACL contract:
33
+ * - updateArtblocksCurationRegistryAddress
34
+ * - updateArtblocksDependencyRegistryAddress
35
+ * - updateArtblocksPrimarySalesAddress
36
+ * - updateArtblocksSecondarySalesAddress
37
+ * - updateArtblocksPrimarySalesPercentage (up to 25%)
38
+ * - updateArtblocksSecondarySalesBPS (up to 100%)
39
+ * - updateMinterContract
40
+ * - updateRandomizerAddress
41
+ * - toggleProjectIsActive
42
+ * - addProject
43
+ * - forbidNewProjects (forever forbidding new projects)
44
+ * - updateDefaultBaseURI (used to initialize new project base URIs)
45
+ * ----------------------------------------------------------------------------
46
+ * The following functions are restricted to either the Artist address or
47
+ * the Admin ACL contract, only when the project is not locked:
48
+ * - updateProjectName
49
+ * - updateProjectArtistName
50
+ * - updateProjectLicense
51
+ * - Change project script via addProjectScript, updateProjectScript,
52
+ * and removeProjectLastScript
53
+ * - updateProjectScriptType
54
+ * - updateProjectAspectRatio
55
+ * ----------------------------------------------------------------------------
56
+ * The following functions are restricted to only the Artist address:
57
+ * - proposeArtistPaymentAddressesAndSplits (Note that this has to be accepted
58
+ * by adminAcceptArtistAddressesAndSplits to take effect, which is restricted
59
+ * to the Admin ACL contract, or the artist if the core contract owner has
60
+ * renounced ownership. Also note that a proposal will be automatically
61
+ * accepted if the artist only proposes changed payee percentages without
62
+ * modifying any payee addresses, or is only removing payee addresses.)
63
+ * - toggleProjectIsPaused (note the artist can still mint while paused)
64
+ * - updateProjectSecondaryMarketRoyaltyPercentage (up to
65
+ ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent)
66
+ * - updateProjectWebsite
67
+ * - updateProjectMaxInvocations (to a number greater than or equal to the
68
+ * current number of invocations, and less than current project maximum
69
+ * invocations)
70
+ * - updateProjectBaseURI (controlling the base URI for tokens in the project)
71
+ * ----------------------------------------------------------------------------
72
+ * The following function is restricted to either the Admin ACL contract, or
73
+ * the Artist address if the core contract owner has renounced ownership:
74
+ * - adminAcceptArtistAddressesAndSplits
75
+ * - updateProjectArtistAddress (owner ultimately controlling the project and
76
+ * its and-on revenue, unless owner has renounced ownership)
77
+ * ----------------------------------------------------------------------------
78
+ * The following function is restricted to the artist when a project is
79
+ * unlocked, and only callable by Admin ACL contract when a project is locked:
80
+ * - updateProjectDescription
81
+ * ----------------------------------------------------------------------------
82
+ * The following function is restricted to owner calling directly:
83
+ * - transferOwnership
84
+ * - renounceOwnership
85
+ * ----------------------------------------------------------------------------
86
+ * Additional admin and artist privileged roles may be described on minters,
87
+ * registries, and other contracts that may interact with this core contract.
88
+ */
89
+ contract GenArt721CoreV3 is
90
+ ERC721_PackedHashSeed,
91
+ Ownable,
92
+ IGenArt721CoreContractV3
93
+ {
94
+ using BytecodeStorage for string;
95
+ using BytecodeStorage for address;
96
+ using Bytes32Strings for bytes32;
97
+ using Strings for uint256;
98
+ uint256 constant ONE_HUNDRED = 100;
99
+ uint256 constant ONE_MILLION = 1_000_000;
100
+ uint24 constant ONE_MILLION_UINT24 = 1_000_000;
101
+ uint256 constant FOUR_WEEKS_IN_SECONDS = 2_419_200;
102
+ uint8 constant AT_CHARACTER_CODE = uint8(bytes1("@")); // 0x40
103
+
104
+ // numeric constants
105
+ uint256 constant ART_BLOCKS_MAX_PRIMARY_SALES_PERCENTAGE = 25; // 25%
106
+ uint256 constant ART_BLOCKS_MAX_SECONDARY_SALES_BPS = 10000; // 10_000 BPS = 100%
107
+ uint256 constant ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE = 95; // 95%
108
+
109
+ // This contract emits generic events that contain fields that indicate
110
+ // which parameter has been updated. This is sufficient for application
111
+ // state management, while also simplifying the contract and indexing code.
112
+ // This was done as an alternative to having custom events that emit what
113
+ // field-values have changed for each event, given that changed values can
114
+ // be introspected by indexers due to the design of this smart contract
115
+ // exposing these state changes via publicly viewable fields.
116
+ //
117
+ // The following fields are used to indicate which contract-level parameter
118
+ // has been updated in the `PlatformUpdated` event:
119
+ bytes32 constant FIELD_NEXT_PROJECT_ID = "nextProjectId";
120
+ bytes32 constant FIELD_NEW_PROJECTS_FORBIDDEN = "newProjectsForbidden";
121
+ bytes32 constant FIELD_DEFAULT_BASE_URI = "defaultBaseURI";
122
+ bytes32 constant FIELD_ARTBLOCKS_PRIMARY_SALES_ADDRESS =
123
+ "artblocksPrimarySalesAddress";
124
+ bytes32 constant FIELD_ARTBLOCKS_SECONDARY_SALES_ADDRESS =
125
+ "artblocksSecondarySalesAddress";
126
+ bytes32 constant FIELD_RANDOMIZER_ADDRESS = "randomizerAddress";
127
+ bytes32 constant FIELD_ARTBLOCKS_CURATION_REGISTRY_ADDRESS =
128
+ "curationRegistryAddress";
129
+ bytes32 constant FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS =
130
+ "dependencyRegistryAddress";
131
+ bytes32 constant FIELD_ARTBLOCKS_PRIMARY_SALES_PERCENTAGE =
132
+ "artblocksPrimaryPercentage";
133
+ bytes32 constant FIELD_ARTBLOCKS_SECONDARY_SALES_BPS =
134
+ "artblocksSecondaryBPS";
135
+ // The following fields are used to indicate which project-level parameter
136
+ // has been updated in the `ProjectUpdated` event:
137
+ bytes32 constant FIELD_PROJECT_COMPLETED = "completed";
138
+ bytes32 constant FIELD_PROJECT_ACTIVE = "active";
139
+ bytes32 constant FIELD_PROJECT_ARTIST_ADDRESS = "artistAddress";
140
+ bytes32 constant FIELD_PROJECT_PAUSED = "paused";
141
+ bytes32 constant FIELD_PROJECT_CREATED = "created";
142
+ bytes32 constant FIELD_PROJECT_NAME = "name";
143
+ bytes32 constant FIELD_PROJECT_ARTIST_NAME = "artistName";
144
+ bytes32 constant FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE =
145
+ "royaltyPercentage";
146
+ bytes32 constant FIELD_PROJECT_DESCRIPTION = "description";
147
+ bytes32 constant FIELD_PROJECT_WEBSITE = "website";
148
+ bytes32 constant FIELD_PROJECT_LICENSE = "license";
149
+ bytes32 constant FIELD_PROJECT_MAX_INVOCATIONS = "maxInvocations";
150
+ bytes32 constant FIELD_PROJECT_SCRIPT = "script";
151
+ bytes32 constant FIELD_PROJECT_SCRIPT_TYPE = "scriptType";
152
+ bytes32 constant FIELD_PROJECT_ASPECT_RATIO = "aspectRatio";
153
+ bytes32 constant FIELD_PROJECT_BASE_URI = "baseURI";
154
+
155
+ // Art Blocks previous flagship ERC721 token addresses (for reference)
156
+ /// Art Blocks Project ID range: [0-2]
157
+ address public constant ART_BLOCKS_ERC721TOKEN_ADDRESS_V0 =
158
+ 0x059EDD72Cd353dF5106D2B9cC5ab83a52287aC3a;
159
+ /// Art Blocks Project ID range: [3-373]
160
+ address public constant ART_BLOCKS_ERC721TOKEN_ADDRESS_V1 =
161
+ 0xa7d8d9ef8D8Ce8992Df33D8b8CF4Aebabd5bD270;
162
+
163
+ /// Curation registry managed by Art Blocks
164
+ address public artblocksCurationRegistryAddress;
165
+ /// Dependency registry managed by Art Blocks
166
+ address public artblocksDependencyRegistryAddress;
167
+
168
+ /// current randomizer contract
169
+ IRandomizerV2 public randomizerContract;
170
+
171
+ /// append-only array of all randomizer contract addresses ever used by
172
+ /// this contract
173
+ address[] private _historicalRandomizerAddresses;
174
+
175
+ /// admin ACL contract
176
+ IAdminACLV0 public adminACLContract;
177
+
178
+ struct Project {
179
+ uint24 invocations;
180
+ uint24 maxInvocations;
181
+ uint24 scriptCount;
182
+ // max uint64 ~= 1.8e19 sec ~= 570 billion years
183
+ uint64 completedTimestamp;
184
+ bool active;
185
+ bool paused;
186
+ string name;
187
+ string artist;
188
+ string description;
189
+ string website;
190
+ string license;
191
+ string projectBaseURI;
192
+ bytes32 scriptTypeAndVersion;
193
+ string aspectRatio;
194
+ // mapping from script index to address storing script in bytecode
195
+ mapping(uint256 => address) scriptBytecodeAddresses;
196
+ }
197
+
198
+ mapping(uint256 => Project) projects;
199
+
200
+ /// packed struct containing project financial information
201
+ struct ProjectFinance {
202
+ address payable additionalPayeePrimarySales;
203
+ // packed uint: max of 95, max uint8 = 255
204
+ uint8 secondaryMarketRoyaltyPercentage;
205
+ address payable additionalPayeeSecondarySales;
206
+ // packed uint: max of 100, max uint8 = 255
207
+ uint8 additionalPayeeSecondarySalesPercentage;
208
+ address payable artistAddress;
209
+ // packed uint: max of 100, max uint8 = 255
210
+ uint8 additionalPayeePrimarySalesPercentage;
211
+ }
212
+ // Project financials mapping
213
+ mapping(uint256 => ProjectFinance) projectIdToFinancials;
214
+
215
+ /// hash of artist's proposed payment updates to be approved by admin
216
+ mapping(uint256 => bytes32) public proposedArtistAddressesAndSplitsHash;
217
+
218
+ /// Art Blocks payment address for all primary sales revenues (packed)
219
+ address payable public artblocksPrimarySalesAddress;
220
+ /// Percentage of primary sales revenue allocated to Art Blocks (packed)
221
+ // packed uint: max of 25, max uint8 = 255
222
+ uint8 private _artblocksPrimarySalesPercentage = 10;
223
+
224
+ /// Art Blocks payment address for all secondary sales royalty revenues
225
+ address payable public artblocksSecondarySalesAddress;
226
+ /// Basis Points of secondary sales royalties allocated to Art Blocks
227
+ uint256 public artblocksSecondarySalesBPS = 250;
228
+
229
+ /// single minter allowed for this core contract
230
+ address public minterContract;
231
+
232
+ /// starting (initial) project ID on this contract
233
+ uint256 public immutable startingProjectId;
234
+
235
+ /// next project ID to be created
236
+ uint248 private _nextProjectId;
237
+
238
+ /// bool indicating if adding new projects is forbidden;
239
+ /// default behavior is to allow new projects
240
+ bool public newProjectsForbidden;
241
+
242
+ /// version & type of this core contract
243
+ string public constant coreVersion = "v3.0.0";
244
+ string public constant coreType = "GenArt721CoreV3";
245
+
246
+ /// default base URI to initialize all new project projectBaseURI values to
247
+ string public defaultBaseURI;
248
+
249
+ modifier onlyNonZeroAddress(address _address) {
250
+ require(_address != address(0), "Must input non-zero address");
251
+ _;
252
+ }
253
+
254
+ modifier onlyNonEmptyString(string memory _string) {
255
+ require(bytes(_string).length != 0, "Must input non-empty string");
256
+ _;
257
+ }
258
+
259
+ modifier onlyValidTokenId(uint256 _tokenId) {
260
+ require(_exists(_tokenId), "Token ID does not exist");
261
+ _;
262
+ }
263
+
264
+ modifier onlyValidProjectId(uint256 _projectId) {
265
+ require(
266
+ (_projectId >= startingProjectId) && (_projectId < _nextProjectId),
267
+ "Project ID does not exist"
268
+ );
269
+ _;
270
+ }
271
+
272
+ modifier onlyUnlocked(uint256 _projectId) {
273
+ // Note: calling `_projectUnlocked` enforces that the `_projectId`
274
+ // passed in is valid.`
275
+ require(_projectUnlocked(_projectId), "Only if unlocked");
276
+ _;
277
+ }
278
+
279
+ modifier onlyAdminACL(bytes4 _selector) {
280
+ require(
281
+ adminACLAllowed(msg.sender, address(this), _selector),
282
+ "Only Admin ACL allowed"
283
+ );
284
+ _;
285
+ }
286
+
287
+ modifier onlyArtist(uint256 _projectId) {
288
+ require(
289
+ msg.sender == projectIdToFinancials[_projectId].artistAddress,
290
+ "Only artist"
291
+ );
292
+ _;
293
+ }
294
+
295
+ modifier onlyArtistOrAdminACL(uint256 _projectId, bytes4 _selector) {
296
+ require(
297
+ msg.sender == projectIdToFinancials[_projectId].artistAddress ||
298
+ adminACLAllowed(msg.sender, address(this), _selector),
299
+ "Only artist or Admin ACL allowed"
300
+ );
301
+ _;
302
+ }
303
+
304
+ /**
305
+ * This modifier allows the artist of a project to call a function if the
306
+ * owner of the contract has renounced ownership. This is to allow the
307
+ * contract to continue to function if the owner decides to renounce
308
+ * ownership.
309
+ */
310
+ modifier onlyAdminACLOrRenouncedArtist(
311
+ uint256 _projectId,
312
+ bytes4 _selector
313
+ ) {
314
+ require(
315
+ adminACLAllowed(msg.sender, address(this), _selector) ||
316
+ (owner() == address(0) &&
317
+ msg.sender ==
318
+ projectIdToFinancials[_projectId].artistAddress),
319
+ "Only Admin ACL allowed, or artist if owner has renounced"
320
+ );
321
+ _;
322
+ }
323
+
324
+ /**
325
+ * @notice Initializes contract.
326
+ * @param _tokenName Name of token.
327
+ * @param _tokenSymbol Token symbol.
328
+ * @param _randomizerContract Randomizer contract.
329
+ * @param _adminACLContract Address of admin access control contract, to be
330
+ * set as contract owner.
331
+ * @param _startingProjectId The initial next project ID.
332
+ * @dev _startingProjectId should be set to a value much, much less than
333
+ * max(uint248), but an explicit input type of `uint248` is used as it is
334
+ * safer to cast up to `uint256` than it is to cast down for the purposes
335
+ * of setting `_nextProjectId`.
336
+ */
337
+ constructor(
338
+ string memory _tokenName,
339
+ string memory _tokenSymbol,
340
+ address _randomizerContract,
341
+ address _adminACLContract,
342
+ uint248 _startingProjectId
343
+ )
344
+ ERC721_PackedHashSeed(_tokenName, _tokenSymbol)
345
+ onlyNonZeroAddress(_randomizerContract)
346
+ {
347
+ // record contracts starting project ID
348
+ // casting-up is safe
349
+ startingProjectId = uint256(_startingProjectId);
350
+ _updateArtblocksPrimarySalesAddress(msg.sender);
351
+ _updateArtblocksSecondarySalesAddress(msg.sender);
352
+ _updateRandomizerAddress(_randomizerContract);
353
+ // set AdminACL management contract as owner
354
+ _transferOwnership(_adminACLContract);
355
+ // initialize default base URI
356
+ _updateDefaultBaseURI("https://token.artblocks.io/");
357
+ // initialize next project ID
358
+ _nextProjectId = _startingProjectId;
359
+ emit PlatformUpdated(FIELD_NEXT_PROJECT_ID);
360
+ }
361
+
362
+ /**
363
+ * @notice Mints a token from project `_projectId` and sets the
364
+ * token's owner to `_to`. Hash may or may not be assigned to the token
365
+ * during the mint transaction, depending on the randomizer contract.
366
+ * @param _to Address to be the minted token's owner.
367
+ * @param _projectId Project ID to mint a token on.
368
+ * @param _by Purchaser of minted token.
369
+ * @return _tokenId The ID of the minted token.
370
+ * @dev sender must be the allowed minterContract
371
+ * @dev name of function is optimized for gas usage
372
+ */
373
+ function mint_Ecf(
374
+ address _to,
375
+ uint256 _projectId,
376
+ address _by
377
+ ) external returns (uint256 _tokenId) {
378
+ // CHECKS
379
+ require(msg.sender == minterContract, "Must mint from minter contract");
380
+ Project storage project = projects[_projectId];
381
+ // load invocations into memory
382
+ uint24 invocationsBefore = project.invocations;
383
+ uint24 invocationsAfter;
384
+ unchecked {
385
+ // invocationsBefore guaranteed <= maxInvocations <= 1_000_000,
386
+ // 1_000_000 << max uint24, so no possible overflow
387
+ invocationsAfter = invocationsBefore + 1;
388
+ }
389
+ uint24 maxInvocations = project.maxInvocations;
390
+
391
+ require(
392
+ invocationsBefore < maxInvocations,
393
+ "Must not exceed max invocations"
394
+ );
395
+ require(
396
+ project.active ||
397
+ _by == projectIdToFinancials[_projectId].artistAddress,
398
+ "Project must exist and be active"
399
+ );
400
+ require(
401
+ !project.paused ||
402
+ _by == projectIdToFinancials[_projectId].artistAddress,
403
+ "Purchases are paused."
404
+ );
405
+
406
+ // EFFECTS
407
+ // increment project's invocations
408
+ project.invocations = invocationsAfter;
409
+ uint256 thisTokenId;
410
+ unchecked {
411
+ // invocationsBefore is uint24 << max uint256. In production use,
412
+ // _projectId * ONE_MILLION must be << max uint256, otherwise
413
+ // tokenIdToProjectId function become invalid.
414
+ // Therefore, no risk of overflow
415
+ thisTokenId = (_projectId * ONE_MILLION) + invocationsBefore;
416
+ }
417
+
418
+ // mark project as completed if hit max invocations
419
+ if (invocationsAfter == maxInvocations) {
420
+ _completeProject(_projectId);
421
+ }
422
+
423
+ // INTERACTIONS
424
+ _mint(_to, thisTokenId);
425
+
426
+ // token hash is updated by the randomizer contract on V3
427
+ randomizerContract.assignTokenHash(thisTokenId);
428
+
429
+ // Do not need to also log `projectId` in event, as the `projectId` for
430
+ // a given token can be derived from the `tokenId` with:
431
+ // projectId = tokenId / 1_000_000
432
+ emit Mint(_to, thisTokenId);
433
+
434
+ return thisTokenId;
435
+ }
436
+
437
+ /**
438
+ * @notice Sets the hash seed for a given token ID `_tokenId`.
439
+ * May only be called by the current randomizer contract.
440
+ * May only be called for tokens that have not already been assigned a
441
+ * non-zero hash.
442
+ * @param _tokenId Token ID to set the hash for.
443
+ * @param _hashSeed Hash seed to set for the token ID. Only last 12 bytes
444
+ * will be used.
445
+ * @dev gas-optimized function name because called during mint sequence
446
+ * @dev if a separate event is required when the token hash is set, e.g.
447
+ * for indexing purposes, it must be emitted by the randomizer. This is to
448
+ * minimize gas when minting.
449
+ */
450
+ function setTokenHash_8PT(
451
+ uint256 _tokenId,
452
+ bytes32 _hashSeed
453
+ ) external onlyValidTokenId(_tokenId) {
454
+ OwnerAndHashSeed storage ownerAndHashSeed = _ownersAndHashSeeds[
455
+ _tokenId
456
+ ];
457
+ require(
458
+ msg.sender == address(randomizerContract),
459
+ "Only randomizer may set"
460
+ );
461
+ require(
462
+ ownerAndHashSeed.hashSeed == bytes12(0),
463
+ "Token hash already set"
464
+ );
465
+ require(_hashSeed != bytes12(0), "No zero hash seed");
466
+ ownerAndHashSeed.hashSeed = bytes12(_hashSeed);
467
+ }
468
+
469
+ /**
470
+ * @notice Allows owner (AdminACL) to revoke ownership of the contract.
471
+ * Note that the contract is intended to continue to function after the
472
+ * owner renounces ownership, but no new projects will be able to be added.
473
+ * Renouncing ownership will leave the contract without an owner,
474
+ * thereby removing any functionality that is only available to the
475
+ * owner/AdminACL contract. The same is true for any dependent contracts
476
+ * that also integrate with the owner/AdminACL contract (e.g. potentially
477
+ * minter suite contracts, registry contracts, etc.).
478
+ * After renouncing ownership, artists will be in control of updates to
479
+ * their payment addresses and splits (see modifier
480
+ * onlyAdminACLOrRenouncedArtist`).
481
+ * While there is no currently intended reason to call this method based on
482
+ * defined Art Blocks business practices, this method exists to allow
483
+ * artists to continue to maintain the limited set of contract
484
+ * functionality that exists post-project-lock in an environment in which
485
+ * there is no longer an admin maintaining this smart contract.
486
+ * @dev This function is intended to be called directly by the AdminACL,
487
+ * not by an address allowed by the AdminACL contract.
488
+ */
489
+ function renounceOwnership() public override onlyOwner {
490
+ // broadcast that new projects are no longer allowed (if not already)
491
+ _forbidNewProjects();
492
+ // renounce ownership viw Ownable
493
+ Ownable.renounceOwnership();
494
+ }
495
+
496
+ /**
497
+ * @notice Updates reference to Art Blocks Curation Registry contract.
498
+ * @param _artblocksCurationRegistryAddress Address of new Curation
499
+ * Registry.
500
+ */
501
+ function updateArtblocksCurationRegistryAddress(
502
+ address _artblocksCurationRegistryAddress
503
+ )
504
+ external
505
+ onlyAdminACL(this.updateArtblocksCurationRegistryAddress.selector)
506
+ onlyNonZeroAddress(_artblocksCurationRegistryAddress)
507
+ {
508
+ artblocksCurationRegistryAddress = _artblocksCurationRegistryAddress;
509
+ emit PlatformUpdated(FIELD_ARTBLOCKS_CURATION_REGISTRY_ADDRESS);
510
+ }
511
+
512
+ /**
513
+ * @notice Updates reference to Art Blocks Dependency Registry contract.
514
+ * @param _artblocksDependencyRegistryAddress Address of new Dependency
515
+ * Registry.
516
+ */
517
+ function updateArtblocksDependencyRegistryAddress(
518
+ address _artblocksDependencyRegistryAddress
519
+ )
520
+ external
521
+ onlyAdminACL(this.updateArtblocksDependencyRegistryAddress.selector)
522
+ onlyNonZeroAddress(_artblocksDependencyRegistryAddress)
523
+ {
524
+ artblocksDependencyRegistryAddress = _artblocksDependencyRegistryAddress;
525
+ emit PlatformUpdated(FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS);
526
+ }
527
+
528
+ /**
529
+ * @notice Updates artblocksPrimarySalesAddress to
530
+ * `_artblocksPrimarySalesAddress`.
531
+ * @param _artblocksPrimarySalesAddress Address of new primary sales
532
+ * payment address.
533
+ */
534
+ function updateArtblocksPrimarySalesAddress(
535
+ address payable _artblocksPrimarySalesAddress
536
+ )
537
+ external
538
+ onlyAdminACL(this.updateArtblocksPrimarySalesAddress.selector)
539
+ onlyNonZeroAddress(_artblocksPrimarySalesAddress)
540
+ {
541
+ _updateArtblocksPrimarySalesAddress(_artblocksPrimarySalesAddress);
542
+ }
543
+
544
+ /**
545
+ * @notice Updates Art Blocks secondary sales royalty payment address to
546
+ * `_artblocksSecondarySalesAddress`.
547
+ * @param _artblocksSecondarySalesAddress Address of new secondary sales
548
+ * payment address.
549
+ */
550
+ function updateArtblocksSecondarySalesAddress(
551
+ address payable _artblocksSecondarySalesAddress
552
+ )
553
+ external
554
+ onlyAdminACL(this.updateArtblocksSecondarySalesAddress.selector)
555
+ onlyNonZeroAddress(_artblocksSecondarySalesAddress)
556
+ {
557
+ _updateArtblocksSecondarySalesAddress(_artblocksSecondarySalesAddress);
558
+ }
559
+
560
+ /**
561
+ * @notice Updates Art Blocks primary sales revenue percentage to
562
+ * `artblocksPrimarySalesPercentage_`.
563
+ * @param artblocksPrimarySalesPercentage_ New primary sales revenue
564
+ * percentage.
565
+ */
566
+ function updateArtblocksPrimarySalesPercentage(
567
+ uint256 artblocksPrimarySalesPercentage_
568
+ )
569
+ external
570
+ onlyAdminACL(this.updateArtblocksPrimarySalesPercentage.selector)
571
+ {
572
+ require(
573
+ artblocksPrimarySalesPercentage_ <=
574
+ ART_BLOCKS_MAX_PRIMARY_SALES_PERCENTAGE,
575
+ "Max of ART_BLOCKS_MAX_PRIMARY_SALES_PERCENTAGE percent"
576
+ );
577
+ _artblocksPrimarySalesPercentage = uint8(
578
+ artblocksPrimarySalesPercentage_
579
+ );
580
+ emit PlatformUpdated(FIELD_ARTBLOCKS_PRIMARY_SALES_PERCENTAGE);
581
+ }
582
+
583
+ /**
584
+ * @notice Updates Art Blocks secondary sales royalty Basis Points to
585
+ * `_artblocksSecondarySalesBPS`.
586
+ * @param _artblocksSecondarySalesBPS New secondary sales royalty Basis
587
+ * points.
588
+ * @dev Due to secondary royalties being ultimately enforced via social
589
+ * consensus, no hard upper limit is imposed on the BPS value, other than
590
+ * <= 100% royalty, which would not make mathematical sense. Realistically,
591
+ * changing this value is expected to either never occur, or be a rare
592
+ * occurrence.
593
+ */
594
+ function updateArtblocksSecondarySalesBPS(
595
+ uint256 _artblocksSecondarySalesBPS
596
+ ) external onlyAdminACL(this.updateArtblocksSecondarySalesBPS.selector) {
597
+ require(
598
+ _artblocksSecondarySalesBPS <= ART_BLOCKS_MAX_SECONDARY_SALES_BPS,
599
+ "Max of ART_BLOCKS_MAX_SECONDARY_SALES_BPS BPS"
600
+ );
601
+ artblocksSecondarySalesBPS = _artblocksSecondarySalesBPS;
602
+ emit PlatformUpdated(FIELD_ARTBLOCKS_SECONDARY_SALES_BPS);
603
+ }
604
+
605
+ /**
606
+ * @notice Updates minter to `_address`.
607
+ * @param _address Address of new minter.
608
+ */
609
+ function updateMinterContract(
610
+ address _address
611
+ )
612
+ external
613
+ onlyAdminACL(this.updateMinterContract.selector)
614
+ onlyNonZeroAddress(_address)
615
+ {
616
+ minterContract = _address;
617
+ emit MinterUpdated(_address);
618
+ }
619
+
620
+ /**
621
+ * @notice Updates randomizer to `_randomizerAddress`.
622
+ * @param _randomizerAddress Address of new randomizer.
623
+ */
624
+ function updateRandomizerAddress(
625
+ address _randomizerAddress
626
+ )
627
+ external
628
+ onlyAdminACL(this.updateRandomizerAddress.selector)
629
+ onlyNonZeroAddress(_randomizerAddress)
630
+ {
631
+ _updateRandomizerAddress(_randomizerAddress);
632
+ }
633
+
634
+ /**
635
+ * @notice Toggles project `_projectId` as active/inactive.
636
+ * @param _projectId Project ID to be toggled.
637
+ */
638
+ function toggleProjectIsActive(
639
+ uint256 _projectId
640
+ )
641
+ external
642
+ onlyAdminACL(this.toggleProjectIsActive.selector)
643
+ onlyValidProjectId(_projectId)
644
+ {
645
+ projects[_projectId].active = !projects[_projectId].active;
646
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_ACTIVE);
647
+ }
648
+
649
+ /**
650
+ * @notice Artist proposes updated set of artist address, additional payee
651
+ * addresses, and percentage splits for project `_projectId`. Addresses and
652
+ * percentages do not have to all be changed, but they must all be defined
653
+ * as a complete set.
654
+ * Note that if the artist is only proposing a change to the payee percentage
655
+ * splits, without modifying the payee addresses, the proposal will be
656
+ * automatically approved and the new splits will become active immediately.
657
+ * Automatic approval will also be granted if the artist is only removing
658
+ * additional payee addresses, without adding any new ones.
659
+ * Also note that if the artist is proposing sending funds to the zero
660
+ * address, this function will revert and the proposal will not be created.
661
+ * @param _projectId Project ID.
662
+ * @param _artistAddress Artist address that controls the project, and may
663
+ * receive payments.
664
+ * @param _additionalPayeePrimarySales Address that may receive a
665
+ * percentage split of the artist's primary sales revenue.
666
+ * @param _additionalPayeePrimarySalesPercentage Percent of artist's
667
+ * portion of primary sale revenue that will be split to address
668
+ * `_additionalPayeePrimarySales`.
669
+ * @param _additionalPayeeSecondarySales Address that may receive a percentage
670
+ * split of the secondary sales royalties.
671
+ * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
672
+ * of secondary sale royalties that will be split to address
673
+ * `_additionalPayeeSecondarySales`.
674
+ * @dev `_artistAddress` must be a valid address (non-zero-address), but it
675
+ * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
676
+ * and their associated percentages to be zero'd out by the controlling artist.
677
+ */
678
+ function proposeArtistPaymentAddressesAndSplits(
679
+ uint256 _projectId,
680
+ address payable _artistAddress,
681
+ address payable _additionalPayeePrimarySales,
682
+ uint256 _additionalPayeePrimarySalesPercentage,
683
+ address payable _additionalPayeeSecondarySales,
684
+ uint256 _additionalPayeeSecondarySalesPercentage
685
+ )
686
+ external
687
+ onlyValidProjectId(_projectId)
688
+ onlyArtist(_projectId)
689
+ onlyNonZeroAddress(_artistAddress)
690
+ {
691
+ ProjectFinance storage projectFinance = projectIdToFinancials[
692
+ _projectId
693
+ ];
694
+ // checks
695
+ require(
696
+ _additionalPayeePrimarySalesPercentage <= ONE_HUNDRED &&
697
+ _additionalPayeeSecondarySalesPercentage <= ONE_HUNDRED,
698
+ "Max of 100%"
699
+ );
700
+ require(
701
+ _additionalPayeePrimarySalesPercentage == 0 ||
702
+ _additionalPayeePrimarySales != address(0),
703
+ "Primary payee is zero address"
704
+ );
705
+ require(
706
+ _additionalPayeeSecondarySalesPercentage == 0 ||
707
+ _additionalPayeeSecondarySales != address(0),
708
+ "Secondary payee is zero address"
709
+ );
710
+ // effects
711
+ // emit event for off-chain indexing
712
+ // note: always emit a proposal event, even in the pathway of
713
+ // automatic approval, to simplify indexing expectations
714
+ emit ProposedArtistAddressesAndSplits(
715
+ _projectId,
716
+ _artistAddress,
717
+ _additionalPayeePrimarySales,
718
+ _additionalPayeePrimarySalesPercentage,
719
+ _additionalPayeeSecondarySales,
720
+ _additionalPayeeSecondarySalesPercentage
721
+ );
722
+ // automatically accept if no proposed addresses modifications, or if
723
+ // the proposal only removes payee addresses.
724
+ // store proposal hash on-chain, only if not automatic accept
725
+ bool automaticAccept;
726
+ {
727
+ // block scope to avoid stack too deep error
728
+ bool artistUnchanged = _artistAddress ==
729
+ projectFinance.artistAddress;
730
+ bool additionalPrimaryUnchangedOrRemoved = (_additionalPayeePrimarySales ==
731
+ projectFinance.additionalPayeePrimarySales) ||
732
+ (_additionalPayeePrimarySales == address(0));
733
+ bool additionalSecondaryUnchangedOrRemoved = (_additionalPayeeSecondarySales ==
734
+ projectFinance.additionalPayeeSecondarySales) ||
735
+ (_additionalPayeeSecondarySales == address(0));
736
+ automaticAccept =
737
+ artistUnchanged &&
738
+ additionalPrimaryUnchangedOrRemoved &&
739
+ additionalSecondaryUnchangedOrRemoved;
740
+ }
741
+ if (automaticAccept) {
742
+ // clear any previously proposed values
743
+ proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
744
+ // update storage
745
+ // (artist address cannot change during automatic accept)
746
+ projectFinance
747
+ .additionalPayeePrimarySales = _additionalPayeePrimarySales;
748
+ // safe to cast as uint8 as max is 100%, max uint8 is 255
749
+ projectFinance.additionalPayeePrimarySalesPercentage = uint8(
750
+ _additionalPayeePrimarySalesPercentage
751
+ );
752
+ projectFinance
753
+ .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
754
+ // safe to cast as uint8 as max is 100%, max uint8 is 255
755
+ projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
756
+ _additionalPayeeSecondarySalesPercentage
757
+ );
758
+ // emit event for off-chain indexing
759
+ emit AcceptedArtistAddressesAndSplits(_projectId);
760
+ } else {
761
+ proposedArtistAddressesAndSplitsHash[_projectId] = keccak256(
762
+ abi.encode(
763
+ _artistAddress,
764
+ _additionalPayeePrimarySales,
765
+ _additionalPayeePrimarySalesPercentage,
766
+ _additionalPayeeSecondarySales,
767
+ _additionalPayeeSecondarySalesPercentage
768
+ )
769
+ );
770
+ }
771
+ }
772
+
773
+ /**
774
+ * @notice Admin accepts a proposed set of updated artist address,
775
+ * additional payee addresses, and percentage splits for project
776
+ * `_projectId`. Addresses and percentages do not have to all be changed,
777
+ * but they must all be defined as a complete set.
778
+ * @param _projectId Project ID.
779
+ * @param _artistAddress Artist address that controls the project, and may
780
+ * receive payments.
781
+ * @param _additionalPayeePrimarySales Address that may receive a
782
+ * percentage split of the artist's primary sales revenue.
783
+ * @param _additionalPayeePrimarySalesPercentage Percent of artist's
784
+ * portion of primary sale revenue that will be split to address
785
+ * `_additionalPayeePrimarySales`.
786
+ * @param _additionalPayeeSecondarySales Address that may receive a percentage
787
+ * split of the secondary sales royalties.
788
+ * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
789
+ * of secondary sale royalties that will be split to address
790
+ * `_additionalPayeeSecondarySales`.
791
+ * @dev this must be called by the Admin ACL contract, and must only accept
792
+ * the most recent proposed values for a given project (validated on-chain
793
+ * by comparing the hash of the proposed and accepted values).
794
+ * @dev `_artistAddress` must be a valid address (non-zero-address), but it
795
+ * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
796
+ * and their associated percentages to be zero'd out by the controlling artist.
797
+ */
798
+ function adminAcceptArtistAddressesAndSplits(
799
+ uint256 _projectId,
800
+ address payable _artistAddress,
801
+ address payable _additionalPayeePrimarySales,
802
+ uint256 _additionalPayeePrimarySalesPercentage,
803
+ address payable _additionalPayeeSecondarySales,
804
+ uint256 _additionalPayeeSecondarySalesPercentage
805
+ )
806
+ external
807
+ onlyValidProjectId(_projectId)
808
+ onlyAdminACLOrRenouncedArtist(
809
+ _projectId,
810
+ this.adminAcceptArtistAddressesAndSplits.selector
811
+ )
812
+ onlyNonZeroAddress(_artistAddress)
813
+ {
814
+ // checks
815
+ require(
816
+ proposedArtistAddressesAndSplitsHash[_projectId] ==
817
+ keccak256(
818
+ abi.encode(
819
+ _artistAddress,
820
+ _additionalPayeePrimarySales,
821
+ _additionalPayeePrimarySalesPercentage,
822
+ _additionalPayeeSecondarySales,
823
+ _additionalPayeeSecondarySalesPercentage
824
+ )
825
+ ),
826
+ "Must match artist proposal"
827
+ );
828
+ // effects
829
+ ProjectFinance storage projectFinance = projectIdToFinancials[
830
+ _projectId
831
+ ];
832
+ projectFinance.artistAddress = _artistAddress;
833
+ projectFinance
834
+ .additionalPayeePrimarySales = _additionalPayeePrimarySales;
835
+ projectFinance.additionalPayeePrimarySalesPercentage = uint8(
836
+ _additionalPayeePrimarySalesPercentage
837
+ );
838
+ projectFinance
839
+ .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
840
+ projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
841
+ _additionalPayeeSecondarySalesPercentage
842
+ );
843
+ // clear proposed values
844
+ proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
845
+ // emit event for off-chain indexing
846
+ emit AcceptedArtistAddressesAndSplits(_projectId);
847
+ }
848
+
849
+ /**
850
+ * @notice Updates artist of project `_projectId` to `_artistAddress`.
851
+ * This is to only be used in the event that the artist address is
852
+ * compromised or sanctioned.
853
+ * @param _projectId Project ID.
854
+ * @param _artistAddress New artist address.
855
+ */
856
+ function updateProjectArtistAddress(
857
+ uint256 _projectId,
858
+ address payable _artistAddress
859
+ )
860
+ external
861
+ onlyValidProjectId(_projectId)
862
+ onlyAdminACLOrRenouncedArtist(
863
+ _projectId,
864
+ this.updateProjectArtistAddress.selector
865
+ )
866
+ onlyNonZeroAddress(_artistAddress)
867
+ {
868
+ projectIdToFinancials[_projectId].artistAddress = _artistAddress;
869
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_ADDRESS);
870
+ }
871
+
872
+ /**
873
+ * @notice Toggles paused state of project `_projectId`.
874
+ * @param _projectId Project ID to be toggled.
875
+ */
876
+ function toggleProjectIsPaused(
877
+ uint256 _projectId
878
+ ) external onlyArtist(_projectId) {
879
+ projects[_projectId].paused = !projects[_projectId].paused;
880
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_PAUSED);
881
+ }
882
+
883
+ /**
884
+ * @notice Adds new project `_projectName` by `_artistAddress`.
885
+ * @param _projectName Project name.
886
+ * @param _artistAddress Artist's address.
887
+ * @dev token price now stored on minter
888
+ */
889
+ function addProject(
890
+ string memory _projectName,
891
+ address payable _artistAddress
892
+ )
893
+ external
894
+ onlyAdminACL(this.addProject.selector)
895
+ onlyNonEmptyString(_projectName)
896
+ onlyNonZeroAddress(_artistAddress)
897
+ {
898
+ require(!newProjectsForbidden, "New projects forbidden");
899
+ uint256 projectId = _nextProjectId;
900
+ projectIdToFinancials[projectId].artistAddress = _artistAddress;
901
+ projects[projectId].name = _projectName;
902
+ projects[projectId].paused = true;
903
+ projects[projectId].maxInvocations = ONE_MILLION_UINT24;
904
+ projects[projectId].projectBaseURI = defaultBaseURI;
905
+
906
+ _nextProjectId = uint248(projectId) + 1;
907
+ emit ProjectUpdated(projectId, FIELD_PROJECT_CREATED);
908
+ }
909
+
910
+ /**
911
+ * @notice Forever forbids new projects from being added to this contract.
912
+ */
913
+ function forbidNewProjects()
914
+ external
915
+ onlyAdminACL(this.forbidNewProjects.selector)
916
+ {
917
+ require(!newProjectsForbidden, "Already forbidden");
918
+ _forbidNewProjects();
919
+ }
920
+
921
+ /**
922
+ * @notice Updates name of project `_projectId` to be `_projectName`.
923
+ * @param _projectId Project ID.
924
+ * @param _projectName New project name.
925
+ */
926
+ function updateProjectName(
927
+ uint256 _projectId,
928
+ string memory _projectName
929
+ )
930
+ external
931
+ onlyUnlocked(_projectId)
932
+ onlyArtistOrAdminACL(_projectId, this.updateProjectName.selector)
933
+ onlyNonEmptyString(_projectName)
934
+ {
935
+ projects[_projectId].name = _projectName;
936
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_NAME);
937
+ }
938
+
939
+ /**
940
+ * @notice Updates artist name for project `_projectId` to be
941
+ * `_projectArtistName`.
942
+ * @param _projectId Project ID.
943
+ * @param _projectArtistName New artist name.
944
+ */
945
+ function updateProjectArtistName(
946
+ uint256 _projectId,
947
+ string memory _projectArtistName
948
+ )
949
+ external
950
+ onlyUnlocked(_projectId)
951
+ onlyArtistOrAdminACL(_projectId, this.updateProjectArtistName.selector)
952
+ onlyNonEmptyString(_projectArtistName)
953
+ {
954
+ projects[_projectId].artist = _projectArtistName;
955
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_NAME);
956
+ }
957
+
958
+ /**
959
+ * @notice Updates artist secondary market royalties for project
960
+ * `_projectId` to be `_secondMarketRoyalty` percent.
961
+ * This DOES NOT include the secondary market royalty percentages collected
962
+ * by Art Blocks; this is only the total percentage of royalties that will
963
+ * be split to artist and additionalSecondaryPayee.
964
+ * @param _projectId Project ID.
965
+ * @param _secondMarketRoyalty Percent of secondary sales revenue that will
966
+ * be split to artist and additionalSecondaryPayee. This must be less than
967
+ * or equal to ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent.
968
+ */
969
+ function updateProjectSecondaryMarketRoyaltyPercentage(
970
+ uint256 _projectId,
971
+ uint256 _secondMarketRoyalty
972
+ ) external onlyArtist(_projectId) {
973
+ require(
974
+ _secondMarketRoyalty <= ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE,
975
+ "Max of ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent"
976
+ );
977
+ projectIdToFinancials[_projectId]
978
+ .secondaryMarketRoyaltyPercentage = uint8(_secondMarketRoyalty);
979
+ emit ProjectUpdated(
980
+ _projectId,
981
+ FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE
982
+ );
983
+ }
984
+
985
+ /**
986
+ * @notice Updates description of project `_projectId`.
987
+ * Only artist may call when unlocked, only admin may call when locked.
988
+ * @param _projectId Project ID.
989
+ * @param _projectDescription New project description.
990
+ */
991
+ function updateProjectDescription(
992
+ uint256 _projectId,
993
+ string memory _projectDescription
994
+ ) external {
995
+ // checks
996
+ require(
997
+ _projectUnlocked(_projectId)
998
+ ? msg.sender == projectIdToFinancials[_projectId].artistAddress
999
+ : adminACLAllowed(
1000
+ msg.sender,
1001
+ address(this),
1002
+ this.updateProjectDescription.selector
1003
+ ),
1004
+ "Only artist when unlocked, owner when locked"
1005
+ );
1006
+ // effects
1007
+ projects[_projectId].description = _projectDescription;
1008
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_DESCRIPTION);
1009
+ }
1010
+
1011
+ /**
1012
+ * @notice Updates website of project `_projectId` to be `_projectWebsite`.
1013
+ * @param _projectId Project ID.
1014
+ * @param _projectWebsite New project website.
1015
+ * @dev It is intentionally allowed for this to be set to the empty string.
1016
+ */
1017
+ function updateProjectWebsite(
1018
+ uint256 _projectId,
1019
+ string memory _projectWebsite
1020
+ ) external onlyArtist(_projectId) {
1021
+ projects[_projectId].website = _projectWebsite;
1022
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_WEBSITE);
1023
+ }
1024
+
1025
+ /**
1026
+ * @notice Updates license for project `_projectId`.
1027
+ * @param _projectId Project ID.
1028
+ * @param _projectLicense New project license.
1029
+ */
1030
+ function updateProjectLicense(
1031
+ uint256 _projectId,
1032
+ string memory _projectLicense
1033
+ )
1034
+ external
1035
+ onlyUnlocked(_projectId)
1036
+ onlyArtistOrAdminACL(_projectId, this.updateProjectLicense.selector)
1037
+ onlyNonEmptyString(_projectLicense)
1038
+ {
1039
+ projects[_projectId].license = _projectLicense;
1040
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_LICENSE);
1041
+ }
1042
+
1043
+ /**
1044
+ * @notice Updates maximum invocations for project `_projectId` to
1045
+ * `_maxInvocations`. Maximum invocations may only be decreased by the
1046
+ * artist, and must be greater than or equal to current invocations.
1047
+ * New projects are created with maximum invocations of 1 million by
1048
+ * default.
1049
+ * @param _projectId Project ID.
1050
+ * @param _maxInvocations New maximum invocations.
1051
+ */
1052
+ function updateProjectMaxInvocations(
1053
+ uint256 _projectId,
1054
+ uint24 _maxInvocations
1055
+ ) external onlyArtist(_projectId) {
1056
+ // CHECKS
1057
+ Project storage project = projects[_projectId];
1058
+ uint256 _invocations = project.invocations;
1059
+ require(
1060
+ (_maxInvocations < project.maxInvocations),
1061
+ "maxInvocations may only be decreased"
1062
+ );
1063
+ require(
1064
+ _maxInvocations >= _invocations,
1065
+ "Only max invocations gte current invocations"
1066
+ );
1067
+ // EFFECTS
1068
+ project.maxInvocations = _maxInvocations;
1069
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_MAX_INVOCATIONS);
1070
+
1071
+ // register completed timestamp if action completed the project
1072
+ if (_maxInvocations == _invocations) {
1073
+ _completeProject(_projectId);
1074
+ }
1075
+ }
1076
+
1077
+ /**
1078
+ * @notice Adds a script to project `_projectId`.
1079
+ * @param _projectId Project to be updated.
1080
+ * @param _script Script to be added. Required to be a non-empty string,
1081
+ * but no further validation is performed.
1082
+ */
1083
+ function addProjectScript(
1084
+ uint256 _projectId,
1085
+ string memory _script
1086
+ )
1087
+ external
1088
+ onlyUnlocked(_projectId)
1089
+ onlyArtistOrAdminACL(_projectId, this.addProjectScript.selector)
1090
+ onlyNonEmptyString(_script)
1091
+ {
1092
+ Project storage project = projects[_projectId];
1093
+ // store script in contract bytecode
1094
+ project.scriptBytecodeAddresses[project.scriptCount] = _script
1095
+ .writeToBytecode();
1096
+ project.scriptCount = project.scriptCount + 1;
1097
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
1098
+ }
1099
+
1100
+ /**
1101
+ * @notice Updates script for project `_projectId` at script ID `_scriptId`.
1102
+ * @param _projectId Project to be updated.
1103
+ * @param _scriptId Script ID to be updated.
1104
+ * @param _script The updated script value. Required to be a non-empty
1105
+ * string, but no further validation is performed.
1106
+ */
1107
+ function updateProjectScript(
1108
+ uint256 _projectId,
1109
+ uint256 _scriptId,
1110
+ string memory _script
1111
+ )
1112
+ external
1113
+ onlyUnlocked(_projectId)
1114
+ onlyArtistOrAdminACL(_projectId, this.updateProjectScript.selector)
1115
+ onlyNonEmptyString(_script)
1116
+ {
1117
+ Project storage project = projects[_projectId];
1118
+ require(_scriptId < project.scriptCount, "scriptId out of range");
1119
+ // purge old contract bytecode contract from the blockchain state
1120
+ // note: Although this does reduce usage of Ethereum state, it does not
1121
+ // reduce the gas costs of removal transactions. We believe this is the
1122
+ // best behavior at the time of writing, and do not expect this to
1123
+ // result in any breaking changes in the future. All current proposals
1124
+ // to change the self-destruct opcode are backwards compatible, but may
1125
+ // result in not removing the bytecode from the blockchain state. This
1126
+ // implementation is compatible with that architecture, as it does not
1127
+ // rely on the bytecode being removed from the blockchain state.
1128
+ project.scriptBytecodeAddresses[_scriptId].purgeBytecode();
1129
+ // store script in contract bytecode, replacing reference address from
1130
+ // the contract that no longer exists with the newly created one
1131
+ project.scriptBytecodeAddresses[_scriptId] = _script.writeToBytecode();
1132
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
1133
+ }
1134
+
1135
+ /**
1136
+ * @notice Removes last script from project `_projectId`.
1137
+ * @param _projectId Project to be updated.
1138
+ */
1139
+ function removeProjectLastScript(
1140
+ uint256 _projectId
1141
+ )
1142
+ external
1143
+ onlyUnlocked(_projectId)
1144
+ onlyArtistOrAdminACL(_projectId, this.removeProjectLastScript.selector)
1145
+ {
1146
+ Project storage project = projects[_projectId];
1147
+ require(project.scriptCount > 0, "there are no scripts to remove");
1148
+ // purge old contract bytecode contract from the blockchain state
1149
+ // note: Although this does reduce usage of Ethereum state, it does not
1150
+ // reduce the gas costs of removal transactions. We believe this is the
1151
+ // best behavior at the time of writing, and do not expect this to
1152
+ // result in any breaking changes in the future. All current proposals
1153
+ // to change the self-destruct opcode are backwards compatible, but may
1154
+ // result in not removing the bytecode from the blockchain state. This
1155
+ // implementation is compatible with that architecture, as it does not
1156
+ // rely on the bytecode being removed from the blockchain state.
1157
+ project
1158
+ .scriptBytecodeAddresses[project.scriptCount - 1]
1159
+ .purgeBytecode();
1160
+ // delete reference to contract address that no longer exists
1161
+ delete project.scriptBytecodeAddresses[project.scriptCount - 1];
1162
+ unchecked {
1163
+ project.scriptCount = project.scriptCount - 1;
1164
+ }
1165
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
1166
+ }
1167
+
1168
+ /**
1169
+ * @notice Updates script type for project `_projectId`.
1170
+ * @param _projectId Project to be updated.
1171
+ * @param _scriptTypeAndVersion Script type and version e.g. "p5js@1.0.0",
1172
+ * as bytes32 encoded string.
1173
+ */
1174
+ function updateProjectScriptType(
1175
+ uint256 _projectId,
1176
+ bytes32 _scriptTypeAndVersion
1177
+ )
1178
+ external
1179
+ onlyUnlocked(_projectId)
1180
+ onlyArtistOrAdminACL(_projectId, this.updateProjectScriptType.selector)
1181
+ {
1182
+ Project storage project = projects[_projectId];
1183
+ // require exactly one @ symbol in _scriptTypeAndVersion
1184
+ require(
1185
+ _scriptTypeAndVersion.containsExactCharacterQty(
1186
+ AT_CHARACTER_CODE,
1187
+ uint8(1)
1188
+ ),
1189
+ "must contain exactly one @"
1190
+ );
1191
+ project.scriptTypeAndVersion = _scriptTypeAndVersion;
1192
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT_TYPE);
1193
+ }
1194
+
1195
+ /**
1196
+ * @notice Updates project's aspect ratio.
1197
+ * @param _projectId Project to be updated.
1198
+ * @param _aspectRatio Aspect ratio to be set. Intended to be string in the
1199
+ * format of a decimal, e.g. "1" for square, "1.77777778" for 16:9, etc.,
1200
+ * allowing for a maximum of 10 digits and one (optional) decimal separator.
1201
+ */
1202
+ function updateProjectAspectRatio(
1203
+ uint256 _projectId,
1204
+ string memory _aspectRatio
1205
+ )
1206
+ external
1207
+ onlyUnlocked(_projectId)
1208
+ onlyArtistOrAdminACL(_projectId, this.updateProjectAspectRatio.selector)
1209
+ onlyNonEmptyString(_aspectRatio)
1210
+ {
1211
+ // Perform more detailed input validation for aspect ratio.
1212
+ bytes memory aspectRatioBytes = bytes(_aspectRatio);
1213
+ uint256 bytesLength = aspectRatioBytes.length;
1214
+ require(bytesLength <= 11, "Aspect ratio format too long");
1215
+ bool hasSeenDecimalSeparator = false;
1216
+ bool hasSeenNumber = false;
1217
+ for (uint256 i; i < bytesLength; i++) {
1218
+ bytes1 character = aspectRatioBytes[i];
1219
+ // Allow as many #s as desired.
1220
+ if (character >= 0x30 && character <= 0x39) {
1221
+ // 9-0
1222
+ // We need to ensure there is at least 1 `9-0` occurrence.
1223
+ hasSeenNumber = true;
1224
+ continue;
1225
+ }
1226
+ if (character == 0x2E) {
1227
+ // .
1228
+ // Allow no more than 1 `.` occurrence.
1229
+ if (!hasSeenDecimalSeparator) {
1230
+ hasSeenDecimalSeparator = true;
1231
+ continue;
1232
+ }
1233
+ }
1234
+ revert("Improperly formatted aspect ratio");
1235
+ }
1236
+ require(hasSeenNumber, "Aspect ratio has no numbers");
1237
+
1238
+ projects[_projectId].aspectRatio = _aspectRatio;
1239
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_ASPECT_RATIO);
1240
+ }
1241
+
1242
+ /**
1243
+ * @notice Updates base URI for project `_projectId` to `_newBaseURI`.
1244
+ * This is the controlling base URI for all tokens in the project. The
1245
+ * contract-level defaultBaseURI is only used when initializing new
1246
+ * projects.
1247
+ * @param _projectId Project to be updated.
1248
+ * @param _newBaseURI New base URI.
1249
+ */
1250
+ function updateProjectBaseURI(
1251
+ uint256 _projectId,
1252
+ string memory _newBaseURI
1253
+ ) external onlyArtist(_projectId) onlyNonEmptyString(_newBaseURI) {
1254
+ projects[_projectId].projectBaseURI = _newBaseURI;
1255
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_BASE_URI);
1256
+ }
1257
+
1258
+ /**
1259
+ * @notice Updates default base URI to `_defaultBaseURI`. The
1260
+ * contract-level defaultBaseURI is only used when initializing new
1261
+ * projects. Token URIs are determined by their project's `projectBaseURI`.
1262
+ * @param _defaultBaseURI New default base URI.
1263
+ */
1264
+ function updateDefaultBaseURI(
1265
+ string memory _defaultBaseURI
1266
+ )
1267
+ external
1268
+ onlyAdminACL(this.updateDefaultBaseURI.selector)
1269
+ onlyNonEmptyString(_defaultBaseURI)
1270
+ {
1271
+ _updateDefaultBaseURI(_defaultBaseURI);
1272
+ }
1273
+
1274
+ /**
1275
+ * @notice Next project ID to be created on this contract.
1276
+ * @return uint256 Next project ID.
1277
+ */
1278
+ function nextProjectId() external view returns (uint256) {
1279
+ return _nextProjectId;
1280
+ }
1281
+
1282
+ /**
1283
+ * @notice Returns token hash for token ID `_tokenId`. Returns null if hash
1284
+ * has not been set.
1285
+ * @param _tokenId Token ID to be queried.
1286
+ * @return bytes32 Token hash.
1287
+ * @dev token hash is the keccak256 hash of the stored hash seed
1288
+ */
1289
+ function tokenIdToHash(uint256 _tokenId) external view returns (bytes32) {
1290
+ bytes12 _hashSeed = _ownersAndHashSeeds[_tokenId].hashSeed;
1291
+ if (_hashSeed == 0) {
1292
+ return 0;
1293
+ }
1294
+ return keccak256(abi.encode(_hashSeed));
1295
+ }
1296
+
1297
+ /**
1298
+ * @notice View function returning Art Blocks portion of primary sales, in
1299
+ * percent.
1300
+ * @return uint256 Art Blocks portion of primary sales, in percent.
1301
+ */
1302
+ function artblocksPrimarySalesPercentage() external view returns (uint256) {
1303
+ return _artblocksPrimarySalesPercentage;
1304
+ }
1305
+
1306
+ /**
1307
+ * @notice View function returning Artist's address for project
1308
+ * `_projectId`.
1309
+ * @param _projectId Project ID to be queried.
1310
+ * @return address Artist's address.
1311
+ */
1312
+ function projectIdToArtistAddress(
1313
+ uint256 _projectId
1314
+ ) external view returns (address payable) {
1315
+ return projectIdToFinancials[_projectId].artistAddress;
1316
+ }
1317
+
1318
+ /**
1319
+ * @notice View function returning Artist's secondary market royalty
1320
+ * percentage for project `_projectId`.
1321
+ * This does not include Art Blocks portion of secondary market royalties.
1322
+ * @param _projectId Project ID to be queried.
1323
+ * @return uint256 Artist's secondary market royalty percentage.
1324
+ */
1325
+ function projectIdToSecondaryMarketRoyaltyPercentage(
1326
+ uint256 _projectId
1327
+ ) external view returns (uint256) {
1328
+ return
1329
+ projectIdToFinancials[_projectId].secondaryMarketRoyaltyPercentage;
1330
+ }
1331
+
1332
+ /**
1333
+ * @notice View function returning Artist's additional payee address for
1334
+ * primary sales, for project `_projectId`.
1335
+ * @param _projectId Project ID to be queried.
1336
+ * @return address Artist's additional payee address for primary sales.
1337
+ */
1338
+ function projectIdToAdditionalPayeePrimarySales(
1339
+ uint256 _projectId
1340
+ ) external view returns (address payable) {
1341
+ return projectIdToFinancials[_projectId].additionalPayeePrimarySales;
1342
+ }
1343
+
1344
+ /**
1345
+ * @notice View function returning Artist's additional payee primary sales
1346
+ * percentage, for project `_projectId`.
1347
+ * @param _projectId Project ID to be queried.
1348
+ * @return uint256 Artist's additional payee primary sales percentage.
1349
+ */
1350
+ function projectIdToAdditionalPayeePrimarySalesPercentage(
1351
+ uint256 _projectId
1352
+ ) external view returns (uint256) {
1353
+ return
1354
+ projectIdToFinancials[_projectId]
1355
+ .additionalPayeePrimarySalesPercentage;
1356
+ }
1357
+
1358
+ /**
1359
+ * @notice View function returning Artist's additional payee address for
1360
+ * secondary sales, for project `_projectId`.
1361
+ * @param _projectId Project ID to be queried.
1362
+ * @return address payable Artist's additional payee address for secondary
1363
+ * sales.
1364
+ */
1365
+ function projectIdToAdditionalPayeeSecondarySales(
1366
+ uint256 _projectId
1367
+ ) external view returns (address payable) {
1368
+ return projectIdToFinancials[_projectId].additionalPayeeSecondarySales;
1369
+ }
1370
+
1371
+ /**
1372
+ * @notice View function returning Artist's additional payee secondary
1373
+ * sales percentage, for project `_projectId`.
1374
+ * @param _projectId Project ID to be queried.
1375
+ * @return uint256 Artist's additional payee secondary sales percentage.
1376
+ */
1377
+ function projectIdToAdditionalPayeeSecondarySalesPercentage(
1378
+ uint256 _projectId
1379
+ ) external view returns (uint256) {
1380
+ return
1381
+ projectIdToFinancials[_projectId]
1382
+ .additionalPayeeSecondarySalesPercentage;
1383
+ }
1384
+
1385
+ /**
1386
+ * @notice Returns project details for project `_projectId`.
1387
+ * @param _projectId Project to be queried.
1388
+ * @return projectName Name of project
1389
+ * @return artist Artist of project
1390
+ * @return description Project description
1391
+ * @return website Project website
1392
+ * @return license Project license
1393
+ * @dev this function was named projectDetails prior to V3 core contract.
1394
+ */
1395
+ function projectDetails(
1396
+ uint256 _projectId
1397
+ )
1398
+ external
1399
+ view
1400
+ returns (
1401
+ string memory projectName,
1402
+ string memory artist,
1403
+ string memory description,
1404
+ string memory website,
1405
+ string memory license
1406
+ )
1407
+ {
1408
+ Project storage project = projects[_projectId];
1409
+ projectName = project.name;
1410
+ artist = project.artist;
1411
+ description = project.description;
1412
+ website = project.website;
1413
+ license = project.license;
1414
+ }
1415
+
1416
+ /**
1417
+ * @notice Returns project state data for project `_projectId`.
1418
+ * @param _projectId Project to be queried
1419
+ * @return invocations Current number of invocations
1420
+ * @return maxInvocations Maximum allowed invocations
1421
+ * @return active Boolean representing if project is currently active
1422
+ * @return paused Boolean representing if project is paused
1423
+ * @return completedTimestamp zero if project not complete, otherwise
1424
+ * timestamp of project completion.
1425
+ * @return locked Boolean representing if project is locked
1426
+ * @dev price and currency info are located on minter contracts
1427
+ */
1428
+ function projectStateData(
1429
+ uint256 _projectId
1430
+ )
1431
+ external
1432
+ view
1433
+ returns (
1434
+ uint256 invocations,
1435
+ uint256 maxInvocations,
1436
+ bool active,
1437
+ bool paused,
1438
+ uint256 completedTimestamp,
1439
+ bool locked
1440
+ )
1441
+ {
1442
+ Project storage project = projects[_projectId];
1443
+ invocations = project.invocations;
1444
+ maxInvocations = project.maxInvocations;
1445
+ active = project.active;
1446
+ paused = project.paused;
1447
+ completedTimestamp = project.completedTimestamp;
1448
+ locked = !_projectUnlocked(_projectId);
1449
+ }
1450
+
1451
+ /**
1452
+ * @notice Returns artist payment information for project `_projectId`.
1453
+ * @param _projectId Project to be queried
1454
+ * @return artistAddress Project Artist's address
1455
+ * @return additionalPayeePrimarySales Additional payee address for primary
1456
+ * sales
1457
+ * @return additionalPayeePrimarySalesPercentage Percentage of artist revenue
1458
+ * to be sent to the additional payee address for primary sales
1459
+ * @return additionalPayeeSecondarySales Additional payee address for secondary
1460
+ * sales royalties
1461
+ * @return additionalPayeeSecondarySalesPercentage Percentage of artist revenue
1462
+ * to be sent to the additional payee address for secondary sales royalties
1463
+ * @return secondaryMarketRoyaltyPercentage Royalty percentage to be sent to
1464
+ * combination of artist and additional payee. This does not include the
1465
+ * platform's percentage of secondary sales royalties, which is defined by
1466
+ * `artblocksSecondarySalesBPS`.
1467
+ */
1468
+ function projectArtistPaymentInfo(
1469
+ uint256 _projectId
1470
+ )
1471
+ external
1472
+ view
1473
+ returns (
1474
+ address artistAddress,
1475
+ address additionalPayeePrimarySales,
1476
+ uint256 additionalPayeePrimarySalesPercentage,
1477
+ address additionalPayeeSecondarySales,
1478
+ uint256 additionalPayeeSecondarySalesPercentage,
1479
+ uint256 secondaryMarketRoyaltyPercentage
1480
+ )
1481
+ {
1482
+ ProjectFinance storage projectFinance = projectIdToFinancials[
1483
+ _projectId
1484
+ ];
1485
+ artistAddress = projectFinance.artistAddress;
1486
+ additionalPayeePrimarySales = projectFinance
1487
+ .additionalPayeePrimarySales;
1488
+ additionalPayeePrimarySalesPercentage = projectFinance
1489
+ .additionalPayeePrimarySalesPercentage;
1490
+ additionalPayeeSecondarySales = projectFinance
1491
+ .additionalPayeeSecondarySales;
1492
+ additionalPayeeSecondarySalesPercentage = projectFinance
1493
+ .additionalPayeeSecondarySalesPercentage;
1494
+ secondaryMarketRoyaltyPercentage = projectFinance
1495
+ .secondaryMarketRoyaltyPercentage;
1496
+ }
1497
+
1498
+ /**
1499
+ * @notice Returns script information for project `_projectId`.
1500
+ * @param _projectId Project to be queried.
1501
+ * @return scriptTypeAndVersion Project's script type and version
1502
+ * (e.g. "p5js(atSymbol)1.0.0")
1503
+ * @return aspectRatio Aspect ratio of project (e.g. "1" for square,
1504
+ * "1.77777778" for 16:9, etc.)
1505
+ * @return scriptCount Count of scripts for project
1506
+ */
1507
+ function projectScriptDetails(
1508
+ uint256 _projectId
1509
+ )
1510
+ external
1511
+ view
1512
+ returns (
1513
+ string memory scriptTypeAndVersion,
1514
+ string memory aspectRatio,
1515
+ uint256 scriptCount
1516
+ )
1517
+ {
1518
+ Project storage project = projects[_projectId];
1519
+ scriptTypeAndVersion = project.scriptTypeAndVersion.toString();
1520
+ aspectRatio = project.aspectRatio;
1521
+ scriptCount = project.scriptCount;
1522
+ }
1523
+
1524
+ /**
1525
+ * @notice Returns address with bytecode containing project script for
1526
+ * project `_projectId` at script index `_index`.
1527
+ */
1528
+ function projectScriptBytecodeAddressByIndex(
1529
+ uint256 _projectId,
1530
+ uint256 _index
1531
+ ) external view returns (address) {
1532
+ return projects[_projectId].scriptBytecodeAddresses[_index];
1533
+ }
1534
+
1535
+ /**
1536
+ * @notice Returns script for project `_projectId` at script index `_index`.
1537
+ * @param _projectId Project to be queried.
1538
+ * @param _index Index of script to be queried.
1539
+ */
1540
+ function projectScriptByIndex(
1541
+ uint256 _projectId,
1542
+ uint256 _index
1543
+ ) external view returns (string memory) {
1544
+ Project storage project = projects[_projectId];
1545
+ // If trying to access an out-of-index script, return the empty string.
1546
+ if (_index >= project.scriptCount) {
1547
+ return "";
1548
+ }
1549
+ return project.scriptBytecodeAddresses[_index].readFromBytecode();
1550
+ }
1551
+
1552
+ /**
1553
+ * @notice Returns base URI for project `_projectId`.
1554
+ * @param _projectId Project to be queried.
1555
+ * @return projectBaseURI Base URI for project
1556
+ */
1557
+ function projectURIInfo(
1558
+ uint256 _projectId
1559
+ ) external view returns (string memory projectBaseURI) {
1560
+ projectBaseURI = projects[_projectId].projectBaseURI;
1561
+ }
1562
+
1563
+ /**
1564
+ * @notice Backwards-compatible (pre-V3) function returning if `_minter` is
1565
+ * minterContract.
1566
+ * @param _minter Address to be queried.
1567
+ * @return bool Boolean representing if `_minter` is minterContract.
1568
+ */
1569
+ function isMintWhitelisted(address _minter) external view returns (bool) {
1570
+ return (minterContract == _minter);
1571
+ }
1572
+
1573
+ /**
1574
+ * @notice Gets qty of randomizers in history of all randomizers used by
1575
+ * this core contract. If a randomizer is switched away from then back to,
1576
+ * it will show up in the history twice.
1577
+ * @return randomizerHistoryCount Count of randomizers in history
1578
+ */
1579
+ function numHistoricalRandomizers() external view returns (uint256) {
1580
+ return _historicalRandomizerAddresses.length;
1581
+ }
1582
+
1583
+ /**
1584
+ * @notice Gets address of randomizer at index `_index` in history of all
1585
+ * randomizers used by this core contract. Index is zero-based.
1586
+ * @param _index Historical index of randomizer to be queried.
1587
+ * @return randomizerAddress Address of randomizer at index `_index`.
1588
+ * @dev If a randomizer is switched away from and then switched back to, it
1589
+ * will show up in the history twice.
1590
+ */
1591
+ function getHistoricalRandomizerAt(
1592
+ uint256 _index
1593
+ ) external view returns (address) {
1594
+ require(
1595
+ _index < _historicalRandomizerAddresses.length,
1596
+ "Index out of bounds"
1597
+ );
1598
+ return _historicalRandomizerAddresses[_index];
1599
+ }
1600
+
1601
+ /**
1602
+ * @notice Backwards-compatible (pre-V3) function returning Art Blocks
1603
+ * primary sales payment address (now called artblocksPrimarySalesAddress).
1604
+ * @return address payable Art Blocks primary sales payment address
1605
+ */
1606
+ function artblocksAddress() external view returns (address payable) {
1607
+ return artblocksPrimarySalesAddress;
1608
+ }
1609
+
1610
+ /**
1611
+ * @notice Backwards-compatible (pre-V3) function returning Art Blocks
1612
+ * primary sales percentage (now called artblocksPrimarySalesPercentage).
1613
+ * @return uint256 Art Blocks primary sales percentage
1614
+ */
1615
+ function artblocksPercentage() external view returns (uint256) {
1616
+ return _artblocksPrimarySalesPercentage;
1617
+ }
1618
+
1619
+ /**
1620
+ * @notice Backwards-compatible (pre-V3) function.
1621
+ * Gets artist + artist's additional payee royalty data for token ID
1622
+ `_tokenId`.
1623
+ * WARNING: Does not include Art Blocks portion of royalties.
1624
+ * @param _tokenId Token ID to be queried.
1625
+ * @return artistAddress Artist's payment address
1626
+ * @return additionalPayee Additional payee's payment address
1627
+ * @return additionalPayeePercentage Percentage of artist revenue
1628
+ * to be sent to the additional payee's address
1629
+ * @return royaltyFeeByID Total royalty percentage to be sent to
1630
+ * combination of artist and additional payee
1631
+ * @dev Does not include Art Blocks portion of royalties.
1632
+ */
1633
+ function getRoyaltyData(
1634
+ uint256 _tokenId
1635
+ )
1636
+ external
1637
+ view
1638
+ returns (
1639
+ address artistAddress,
1640
+ address additionalPayee,
1641
+ uint256 additionalPayeePercentage,
1642
+ uint256 royaltyFeeByID
1643
+ )
1644
+ {
1645
+ uint256 projectId = tokenIdToProjectId(_tokenId);
1646
+ ProjectFinance storage projectFinance = projectIdToFinancials[
1647
+ projectId
1648
+ ];
1649
+ artistAddress = projectFinance.artistAddress;
1650
+ additionalPayee = projectFinance.additionalPayeeSecondarySales;
1651
+ additionalPayeePercentage = projectFinance
1652
+ .additionalPayeeSecondarySalesPercentage;
1653
+ royaltyFeeByID = projectFinance.secondaryMarketRoyaltyPercentage;
1654
+ }
1655
+
1656
+ /**
1657
+ * @notice Gets royalty Basis Points (BPS) for token ID `_tokenId`.
1658
+ * This conforms to the IManifold interface designated in the Royalty
1659
+ * Registry's RoyaltyEngineV1.sol contract.
1660
+ * ref: https://github.com/manifoldxyz/royalty-registry-solidity
1661
+ * @param _tokenId Token ID to be queried.
1662
+ * @return recipients Array of royalty payment recipients
1663
+ * @return bps Array of Basis Points (BPS) allocated to each recipient,
1664
+ * aligned by index.
1665
+ * @dev reverts if invalid _tokenId
1666
+ * @dev only returns recipients that have a non-zero BPS allocation
1667
+ */
1668
+ function getRoyalties(
1669
+ uint256 _tokenId
1670
+ )
1671
+ external
1672
+ view
1673
+ onlyValidTokenId(_tokenId)
1674
+ returns (address payable[] memory recipients, uint256[] memory bps)
1675
+ {
1676
+ // initialize arrays with maximum potential length
1677
+ recipients = new address payable[](3);
1678
+ bps = new uint256[](3);
1679
+
1680
+ uint256 projectId = tokenIdToProjectId(_tokenId);
1681
+ ProjectFinance storage projectFinance = projectIdToFinancials[
1682
+ projectId
1683
+ ];
1684
+ // load values into memory
1685
+ uint256 royaltyPercentageForArtistAndAdditional = projectFinance
1686
+ .secondaryMarketRoyaltyPercentage;
1687
+ uint256 additionalPayeePercentage = projectFinance
1688
+ .additionalPayeeSecondarySalesPercentage;
1689
+ // calculate BPS = percentage * 100
1690
+ uint256 artistBPS = (ONE_HUNDRED - additionalPayeePercentage) *
1691
+ royaltyPercentageForArtistAndAdditional;
1692
+
1693
+ uint256 additionalBPS = additionalPayeePercentage *
1694
+ royaltyPercentageForArtistAndAdditional;
1695
+ uint256 artblocksBPS = artblocksSecondarySalesBPS;
1696
+ // populate arrays
1697
+ uint256 payeeCount;
1698
+ if (artistBPS > 0) {
1699
+ recipients[payeeCount] = projectFinance.artistAddress;
1700
+ bps[payeeCount++] = artistBPS;
1701
+ }
1702
+ if (additionalBPS > 0) {
1703
+ recipients[payeeCount] = projectFinance
1704
+ .additionalPayeeSecondarySales;
1705
+ bps[payeeCount++] = additionalBPS;
1706
+ }
1707
+ if (artblocksBPS > 0) {
1708
+ recipients[payeeCount] = artblocksSecondarySalesAddress;
1709
+ bps[payeeCount++] = artblocksBPS;
1710
+ }
1711
+ // trim arrays if necessary
1712
+ if (3 > payeeCount) {
1713
+ assembly {
1714
+ let decrease := sub(3, payeeCount)
1715
+ mstore(recipients, sub(mload(recipients), decrease))
1716
+ mstore(bps, sub(mload(bps), decrease))
1717
+ }
1718
+ }
1719
+ return (recipients, bps);
1720
+ }
1721
+
1722
+ /**
1723
+ * @notice View function that returns appropriate revenue splits between
1724
+ * different Art Blocks, Artist, and Artist's additional primary sales
1725
+ * payee given a sale price of `_price` on project `_projectId`.
1726
+ * This always returns three revenue amounts and three addresses, but if a
1727
+ * revenue is zero for either Artist or additional payee, the corresponding
1728
+ * address returned will also be null (for gas optimization).
1729
+ * Does not account for refund if user overpays for a token (minter should
1730
+ * handle a refund of the difference, if appropriate).
1731
+ * Some minters may have alternative methods of splitting payments, in
1732
+ * which case they should implement their own payment splitting logic.
1733
+ * @param _projectId Project ID to be queried.
1734
+ * @param _price Sale price of token.
1735
+ * @return artblocksRevenue_ amount of revenue to be sent to Art Blocks
1736
+ * @return artblocksAddress_ address to send Art Blocks revenue to
1737
+ * @return artistRevenue_ amount of revenue to be sent to Artist
1738
+ * @return artistAddress_ address to send Artist revenue to. Will be null
1739
+ * if no revenue is due to artist (gas optimization).
1740
+ * @return additionalPayeePrimaryRevenue_ amount of revenue to be sent to
1741
+ * additional payee for primary sales
1742
+ * @return additionalPayeePrimaryAddress_ address to send Artist's
1743
+ * additional payee for primary sales revenue to. Will be null if no
1744
+ * revenue is due to additional payee for primary sales (gas optimization).
1745
+ * @dev this always returns three addresses and three revenues, but if the
1746
+ * revenue is zero, the corresponding address will be address(0). It is up
1747
+ * to the contract performing the revenue split to handle this
1748
+ * appropriately.
1749
+ */
1750
+ function getPrimaryRevenueSplits(
1751
+ uint256 _projectId,
1752
+ uint256 _price
1753
+ )
1754
+ external
1755
+ view
1756
+ returns (
1757
+ uint256 artblocksRevenue_,
1758
+ address payable artblocksAddress_,
1759
+ uint256 artistRevenue_,
1760
+ address payable artistAddress_,
1761
+ uint256 additionalPayeePrimaryRevenue_,
1762
+ address payable additionalPayeePrimaryAddress_
1763
+ )
1764
+ {
1765
+ ProjectFinance storage projectFinance = projectIdToFinancials[
1766
+ _projectId
1767
+ ];
1768
+ // calculate revenues
1769
+ artblocksRevenue_ =
1770
+ (_price * uint256(_artblocksPrimarySalesPercentage)) /
1771
+ ONE_HUNDRED;
1772
+ uint256 projectFunds;
1773
+ unchecked {
1774
+ // artblocksRevenue_ is always <=25, so guaranteed to never underflow
1775
+ projectFunds = _price - artblocksRevenue_;
1776
+ }
1777
+ additionalPayeePrimaryRevenue_ =
1778
+ (projectFunds *
1779
+ projectFinance.additionalPayeePrimarySalesPercentage) /
1780
+ ONE_HUNDRED;
1781
+ unchecked {
1782
+ // projectIdToAdditionalPayeePrimarySalesPercentage is always
1783
+ // <=100, so guaranteed to never underflow
1784
+ artistRevenue_ = projectFunds - additionalPayeePrimaryRevenue_;
1785
+ }
1786
+ // set addresses from storage
1787
+ artblocksAddress_ = artblocksPrimarySalesAddress;
1788
+ if (artistRevenue_ > 0) {
1789
+ artistAddress_ = projectFinance.artistAddress;
1790
+ }
1791
+ if (additionalPayeePrimaryRevenue_ > 0) {
1792
+ additionalPayeePrimaryAddress_ = projectFinance
1793
+ .additionalPayeePrimarySales;
1794
+ }
1795
+ }
1796
+
1797
+ /**
1798
+ * @notice Backwards-compatible (pre-V3) getter returning contract admin
1799
+ * @return address Address of contract admin (same as owner)
1800
+ */
1801
+ function admin() external view returns (address) {
1802
+ return owner();
1803
+ }
1804
+
1805
+ /**
1806
+ * @notice Gets the project ID for a given `_tokenId`.
1807
+ * @param _tokenId Token ID to be queried.
1808
+ * @return _projectId Project ID for given `_tokenId`.
1809
+ */
1810
+ function tokenIdToProjectId(
1811
+ uint256 _tokenId
1812
+ ) public pure returns (uint256 _projectId) {
1813
+ return _tokenId / ONE_MILLION;
1814
+ }
1815
+
1816
+ /**
1817
+ * @notice Convenience function that returns whether `_sender` is allowed
1818
+ * to call function with selector `_selector` on contract `_contract`, as
1819
+ * determined by this contract's current Admin ACL contract. Expected use
1820
+ * cases include minter contracts checking if caller is allowed to call
1821
+ * admin-gated functions on minter contracts.
1822
+ * @param _sender Address of the sender calling function with selector
1823
+ * `_selector` on contract `_contract`.
1824
+ * @param _contract Address of the contract being called by `_sender`.
1825
+ * @param _selector Function selector of the function being called by
1826
+ * `_sender`.
1827
+ * @return bool Whether `_sender` is allowed to call function with selector
1828
+ * `_selector` on contract `_contract`.
1829
+ * @dev assumes the Admin ACL contract is the owner of this contract, which
1830
+ * is expected to always be true.
1831
+ * @dev adminACLContract is expected to either be null address (if owner
1832
+ * has renounced ownership), or conform to IAdminACLV0 interface. Check for
1833
+ * null address first to avoid revert when admin has renounced ownership.
1834
+ */
1835
+ function adminACLAllowed(
1836
+ address _sender,
1837
+ address _contract,
1838
+ bytes4 _selector
1839
+ ) public returns (bool) {
1840
+ return
1841
+ owner() != address(0) &&
1842
+ adminACLContract.allowed(_sender, _contract, _selector);
1843
+ }
1844
+
1845
+ /**
1846
+ * @notice Returns contract owner. Set to deployer's address by default on
1847
+ * contract deployment.
1848
+ * @return address Address of contract owner.
1849
+ * @dev ref: https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable
1850
+ * @dev owner role was called `admin` prior to V3 core contract
1851
+ */
1852
+ function owner()
1853
+ public
1854
+ view
1855
+ override(Ownable, IGenArt721CoreContractV3_Base)
1856
+ returns (address)
1857
+ {
1858
+ return Ownable.owner();
1859
+ }
1860
+
1861
+ /**
1862
+ * @notice Gets token URI for token ID `_tokenId`.
1863
+ * @param _tokenId Token ID to be queried.
1864
+ * @return string URI of token ID `_tokenId`.
1865
+ * @dev token URIs are the concatenation of the project base URI and the
1866
+ * token ID.
1867
+ */
1868
+ function tokenURI(
1869
+ uint256 _tokenId
1870
+ ) public view override onlyValidTokenId(_tokenId) returns (string memory) {
1871
+ string memory _projectBaseURI = projects[tokenIdToProjectId(_tokenId)]
1872
+ .projectBaseURI;
1873
+ return string.concat(_projectBaseURI, _tokenId.toString());
1874
+ }
1875
+
1876
+ /**
1877
+ * @dev See {IERC165-supportsInterface}.
1878
+ */
1879
+ function supportsInterface(
1880
+ bytes4 interfaceId
1881
+ ) public view virtual override returns (bool) {
1882
+ return
1883
+ interfaceId == type(IManifold).interfaceId ||
1884
+ super.supportsInterface(interfaceId);
1885
+ }
1886
+
1887
+ /**
1888
+ * @notice Forbids new projects from being created
1889
+ * @dev only performs operation and emits event if contract is not already
1890
+ * forbidding new projects.
1891
+ */
1892
+ function _forbidNewProjects() internal {
1893
+ if (!newProjectsForbidden) {
1894
+ newProjectsForbidden = true;
1895
+ emit PlatformUpdated(FIELD_NEW_PROJECTS_FORBIDDEN);
1896
+ }
1897
+ }
1898
+
1899
+ /**
1900
+ * @notice Transfers ownership of the contract to a new account (`newOwner`).
1901
+ * Internal function without access restriction.
1902
+ * @param newOwner New owner.
1903
+ * @dev owner role was called `admin` prior to V3 core contract.
1904
+ * @dev Overrides and wraps OpenZeppelin's _transferOwnership function to
1905
+ * also update adminACLContract for improved introspection.
1906
+ */
1907
+ function _transferOwnership(address newOwner) internal override {
1908
+ Ownable._transferOwnership(newOwner);
1909
+ adminACLContract = IAdminACLV0(newOwner);
1910
+ }
1911
+
1912
+ /**
1913
+ * @notice Updates Art Blocks payment address to `_artblocksPrimarySalesAddress`.
1914
+ * @param _artblocksPrimarySalesAddress New Art Blocks payment address.
1915
+ * @dev Note that this method does not check that the input address is
1916
+ * not `address(0)`, as it is expected that callers of this method should
1917
+ * perform input validation where applicable.
1918
+ */
1919
+ function _updateArtblocksPrimarySalesAddress(
1920
+ address _artblocksPrimarySalesAddress
1921
+ ) internal {
1922
+ artblocksPrimarySalesAddress = payable(_artblocksPrimarySalesAddress);
1923
+ emit PlatformUpdated(FIELD_ARTBLOCKS_PRIMARY_SALES_ADDRESS);
1924
+ }
1925
+
1926
+ /**
1927
+ * @notice Updates Art Blocks secondary sales royalty payment address to
1928
+ * `_artblocksSecondarySalesAddress`.
1929
+ * @param _artblocksSecondarySalesAddress New Art Blocks secondary sales
1930
+ * payment address.
1931
+ * @dev Note that this method does not check that the input address is
1932
+ * not `address(0)`, as it is expected that callers of this method should
1933
+ * perform input validation where applicable.
1934
+ */
1935
+ function _updateArtblocksSecondarySalesAddress(
1936
+ address _artblocksSecondarySalesAddress
1937
+ ) internal {
1938
+ artblocksSecondarySalesAddress = payable(
1939
+ _artblocksSecondarySalesAddress
1940
+ );
1941
+ emit PlatformUpdated(FIELD_ARTBLOCKS_SECONDARY_SALES_ADDRESS);
1942
+ }
1943
+
1944
+ /**
1945
+ * @notice Updates randomizer address to `_randomizerAddress`.
1946
+ * @param _randomizerAddress New randomizer address.
1947
+ * @dev Note that this method does not check that the input address is
1948
+ * not `address(0)`, as it is expected that callers of this method should
1949
+ * perform input validation where applicable.
1950
+ */
1951
+ function _updateRandomizerAddress(address _randomizerAddress) internal {
1952
+ randomizerContract = IRandomizerV2(_randomizerAddress);
1953
+ // populate historical randomizer array
1954
+ _historicalRandomizerAddresses.push(_randomizerAddress);
1955
+ emit PlatformUpdated(FIELD_RANDOMIZER_ADDRESS);
1956
+ }
1957
+
1958
+ /**
1959
+ * @notice Updates default base URI to `_defaultBaseURI`.
1960
+ * When new projects are added, their `projectBaseURI` is automatically
1961
+ * initialized to `_defaultBaseURI`.
1962
+ * @param _defaultBaseURI New default base URI.
1963
+ * @dev Note that this method does not check that the input string is not
1964
+ * the empty string, as it is expected that callers of this method should
1965
+ * perform input validation where applicable.
1966
+ */
1967
+ function _updateDefaultBaseURI(string memory _defaultBaseURI) internal {
1968
+ defaultBaseURI = _defaultBaseURI;
1969
+ emit PlatformUpdated(FIELD_DEFAULT_BASE_URI);
1970
+ }
1971
+
1972
+ /**
1973
+ * @notice Internal function to complete a project.
1974
+ * @param _projectId Project ID to be completed.
1975
+ */
1976
+ function _completeProject(uint256 _projectId) internal {
1977
+ projects[_projectId].completedTimestamp = uint64(block.timestamp);
1978
+ emit ProjectUpdated(_projectId, FIELD_PROJECT_COMPLETED);
1979
+ }
1980
+
1981
+ /**
1982
+ * @notice Internal function that returns whether a project is unlocked.
1983
+ * Projects automatically lock four weeks after they are completed.
1984
+ * Projects are considered completed when they have been invoked the
1985
+ * maximum number of times.
1986
+ * @param _projectId Project ID to be queried.
1987
+ * @return bool true if project is unlocked, false otherwise.
1988
+ * @dev This also enforces that the `_projectId` passed in is valid.
1989
+ */
1990
+ function _projectUnlocked(
1991
+ uint256 _projectId
1992
+ ) internal view onlyValidProjectId(_projectId) returns (bool) {
1993
+ uint256 projectCompletedTimestamp = projects[_projectId]
1994
+ .completedTimestamp;
1995
+ bool projectOpen = projectCompletedTimestamp == 0;
1996
+ return
1997
+ projectOpen ||
1998
+ (block.timestamp - projectCompletedTimestamp <
1999
+ FOUR_WEEKS_IN_SECONDS);
2000
+ }
2001
+ }