@artblocks/contracts 0.0.1 → 1.0.1

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