@artblocks/contracts 1.3.2 → 1.4.0

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 (229) hide show
  1. package/README.md +11 -10
  2. package/artifacts/contracts/AdminACLV0RoyaltyRegistry.sol/AdminACLV0RoyaltyRegistry.json +193 -0
  3. package/artifacts/contracts/AdminACLV0RoyaltyRegistry.sol/IRoyaltyRegistry.json +35 -0
  4. package/artifacts/contracts/AdminACLV1.sol/AdminACLV1.json +2 -2
  5. package/artifacts/contracts/BytecodeStorageReaders/BytecodeStorageReaderContractV2.sol/BytecodeStorageReaderContractV2.json +2 -2
  6. package/artifacts/contracts/BytecodeStorageReaders/BytecodeStorageReaderContractV2_Web3Call.sol/BytecodeStorageReaderContractV2_Web3Call.json +2 -2
  7. package/artifacts/contracts/BytecodeStorageV2Writer.sol/BytecodeStorageV2Writer.json +2 -2
  8. package/artifacts/contracts/DependencyRegistryV0.sol/DependencyRegistryV0.json +210 -2
  9. package/artifacts/contracts/GenArt721CoreV3_Curated.sol/GenArt721CoreV3_Curated.json +235 -20
  10. package/artifacts/contracts/GenArt721CoreV3_Curated_Flex.sol/GenArt721CoreV3_Curated_Flex.json +3197 -0
  11. package/artifacts/contracts/GenArt721CoreV3_Explorations_Flex.sol/GenArt721CoreV3_Explorations_Flex.json +3192 -0
  12. package/artifacts/contracts/OwnedCreate2FactoryV0.sol/OwnedCreate2FactoryV0.json +2 -2
  13. package/artifacts/contracts/archive/GenArt721CoreV3.sol/GenArt721CoreV3.json +27 -2
  14. package/artifacts/contracts/archive/GenArt721CoreV3_Explorations.sol/GenArt721CoreV3_Explorations.json +27 -2
  15. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV2.sol/MinterDAExpV2.json +2 -2
  16. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV3.sol/MinterDAExpV3.json +2 -2
  17. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV4.sol/MinterDAExpV4.json +2 -2
  18. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol/MinterDAExpSettlementV0.json +2 -2
  19. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV1.sol/MinterDAExpSettlementV1.json +2 -2
  20. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV2.sol/MinterDAExpSettlementV2.json +2 -2
  21. package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV2.sol/MinterDALinV2.json +2 -2
  22. package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV3.sol/MinterDALinV3.json +2 -2
  23. package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV4.sol/MinterDALinV4.json +2 -2
  24. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol/MinterHolderV1.json +2 -2
  25. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +2 -2
  26. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV3.sol/MinterHolderV3.json +2 -2
  27. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV4.sol/MinterHolderV4.json +2 -2
  28. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol/MinterMerkleV1.json +2 -2
  29. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol/MinterMerkleV2.json +2 -2
  30. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV3.sol/MinterMerkleV3.json +2 -2
  31. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV4.sol/MinterMerkleV4.json +2 -2
  32. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV5.sol/MinterMerkleV5.json +2 -2
  33. package/artifacts/contracts/archive/minter-suite/Minters/MinterPolyptych/MinterPolyptychV0.sol/IGenArt721CoreContractV3WithRandomizer.json +25 -0
  34. package/artifacts/contracts/archive/minter-suite/Minters/MinterPolyptych/MinterPolyptychV0.sol/MinterPolyptychV0.json +2 -2
  35. package/artifacts/contracts/archive/minter-suite/Minters/MinterSEA/MinterSEAV0.sol/MinterSEAV0.json +2 -2
  36. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV2.sol/MinterSetPriceV2.json +2 -2
  37. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV3.sol/MinterSetPriceV3.json +2 -2
  38. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV4.sol/MinterSetPriceV4.json +2 -2
  39. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol/MinterSetPriceERC20V2.json +2 -2
  40. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol/MinterSetPriceERC20V3.json +2 -2
  41. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V4.sol/MinterSetPriceERC20V4.json +2 -2
  42. package/artifacts/contracts/engine/V3/EngineFactoryV0.sol/EngineFactoryV0.json +28 -2
  43. package/artifacts/contracts/engine/V3/GenArt721CoreV3_Engine.sol/GenArt721CoreV3_Engine.json +239 -20
  44. package/artifacts/contracts/engine/V3/GenArt721CoreV3_Engine_Flex.sol/GenArt721CoreV3_Engine_Flex.json +267 -48
  45. package/artifacts/contracts/engine/V3/forks/GenArt721CoreV3_Engine_Flex_PROOF.sol/GenArt721CoreV3_Engine_Flex_PROOF.json +27 -2
  46. package/artifacts/contracts/engine/V3/forks/PROHIBITION/AdminACLV0_PROHIBITION.sol/AdminACLV0_PROHIBITION.json +2 -2
  47. package/artifacts/contracts/engine/V3/forks/PROHIBITION/GenArt721CoreV3_Engine_Flex_PROHIBITION.sol/GenArt721CoreV3_Engine_Flex_PROHIBITION.json +27 -2
  48. package/artifacts/contracts/engine/V3/forks/PROHIBITION/interfaces/IGenArt721CoreContractV3_Engine_Flex_PROHIBITION.sol/IGenArt721CoreContractV3_Engine_Flex_PROHIBITION.json +25 -0
  49. package/artifacts/contracts/engine/V3/transfer-hooks/AbstractTransferHook.sol/AbstractTransferHook.json +79 -0
  50. package/artifacts/contracts/engine/V3/transfer-hooks/OwnerHistoryTransferHook.sol/OwnerHistoryTransferHook.json +315 -0
  51. package/artifacts/contracts/generator/GenArt721GeneratorV0.sol/GenArt721GeneratorV0.json +2 -2
  52. package/artifacts/contracts/interfaces/v0.8.x/IClaimMinter.sol/IClaimMinter.json +189 -0
  53. package/artifacts/contracts/interfaces/v0.8.x/IDependencyRegistryV0.sol/IDependencyRegistryV0.json +149 -0
  54. package/artifacts/contracts/interfaces/v0.8.x/IEngineFactoryV0.sol/IEngineFactoryV0.json +26 -0
  55. package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3.sol/IGenArt721CoreContractV3.json +25 -0
  56. package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol/IGenArt721CoreContractV3_Base.json +25 -0
  57. package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol/IGenArt721CoreContractV3_Engine.json +123 -0
  58. package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol/IGenArt721CoreContractV3_Engine_Flex.json +123 -0
  59. package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_Flex_PreV3p2.sol/IGenArt721CoreContractV3_Engine_Flex_PreV3p2.json +25 -0
  60. package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_PreV3p2.sol/IGenArt721CoreContractV3_Engine_PreV3p2.json +25 -0
  61. package/artifacts/contracts/interfaces/v0.8.x/IOwnerHistoryTransferHook.sol/IOwnerHistoryTransferHook.json +299 -0
  62. package/artifacts/contracts/interfaces/v0.8.x/ISRHooks.sol/ISRHooks.json +432 -0
  63. package/artifacts/contracts/interfaces/v0.8.x/ISeaDropShimForContract.sol/ISeaDropShimForContract.json +0 -13
  64. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterTieredAllowV0.sol/ISharedMinterTieredAllowV0.json +94 -0
  65. package/artifacts/contracts/interfaces/v0.8.x/ITransferHook.sol/ITransferHook.json +63 -0
  66. package/artifacts/contracts/libs/v0.8.x/AuthLib.sol/AuthLib.json +2 -2
  67. package/artifacts/contracts/libs/v0.8.x/BytecodeStorageV2.sol/BytecodeStorageReader.json +2 -2
  68. package/artifacts/contracts/libs/v0.8.x/BytecodeStorageV2.sol/BytecodeStorageWriter.json +2 -2
  69. package/artifacts/contracts/libs/v0.8.x/DependencyRegistryStorageLib.sol/DependencyRegistryStorageLib.json +2 -2
  70. package/artifacts/contracts/libs/v0.8.x/ENSLib.sol/ENSLib.json +10 -0
  71. package/artifacts/contracts/libs/v0.8.x/ENSLib.sol/IUniversalResolver.json +45 -0
  72. package/artifacts/contracts/libs/v0.8.x/EnumerableSetUint16.sol/EnumerableSetUint16.json +10 -0
  73. package/artifacts/contracts/libs/v0.8.x/FeistelWalkLib.sol/FeistelWalkLib.json +10 -0
  74. package/artifacts/contracts/libs/v0.8.x/ImmutableUint16Array.sol/ImmutableUint16Array.json +10 -0
  75. package/artifacts/contracts/libs/v0.8.x/V3EngineLib.sol/V3EngineLib.json +163 -0
  76. package/artifacts/contracts/libs/v0.8.x/V3FlexLib.sol/V3FlexLib.json +2 -2
  77. package/artifacts/contracts/libs/v0.8.x/V3TransferHookLib.sol/V3TransferHookLib.json +79 -0
  78. package/artifacts/contracts/libs/v0.8.x/minter-libs/MaxInvocationsLib.sol/MaxInvocationsLib.json +2 -2
  79. package/artifacts/contracts/libs/v0.8.x/minter-libs/OnChainAllowlistLib.sol/OnChainAllowlistLib.json +10 -0
  80. package/artifacts/contracts/libs/v0.8.x/minter-libs/PolyptychLib.sol/PolyptychLib.json +2 -2
  81. package/artifacts/contracts/libs/v0.8.x/minter-libs/RAMLib.sol/RAMLib.json +2 -2
  82. package/artifacts/contracts/libs/v0.8.x/minter-libs/SEALib.sol/SEALib.json +2 -2
  83. package/artifacts/contracts/libs/v0.8.x/minter-libs/SettlementExpLib.sol/SettlementExpLib.json +2 -2
  84. package/artifacts/contracts/libs/v0.8.x/minter-libs/SplitFundsLib.sol/SplitFundsLib.json +2 -2
  85. package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV1.sol/MinterFilterV1.json +2 -2
  86. package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV2.sol/MinterFilterV2.json +2 -2
  87. package/artifacts/contracts/minter-suite/Minters/MinterDAExpHolderV5.sol/MinterDAExpHolderV5.json +2 -2
  88. package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV3.sol/MinterDAExpSettlementV3.json +2 -2
  89. package/artifacts/contracts/minter-suite/Minters/MinterDAExpV5.sol/MinterDAExpV5.json +2 -2
  90. package/artifacts/contracts/minter-suite/Minters/MinterDALinHolderV5.sol/MinterDALinHolderV5.json +2 -2
  91. package/artifacts/contracts/minter-suite/Minters/MinterDALinV5.sol/MinterDALinV5.json +2 -2
  92. package/artifacts/contracts/minter-suite/Minters/MinterMinPriceMerkleV0.sol/MinterMinPriceMerkleV0.json +2 -2
  93. package/artifacts/contracts/minter-suite/Minters/MinterMinPriceV0.sol/MinterMinPriceV0.json +2 -2
  94. package/artifacts/contracts/minter-suite/Minters/MinterRAMV0.sol/MinterRAMV0.json +2 -2
  95. package/artifacts/contracts/minter-suite/Minters/MinterSEAV1.sol/MinterSEAV1.json +2 -2
  96. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceERC20V5.sol/MinterSetPriceERC20V5.json +2 -2
  97. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceHolderV5.sol/MinterSetPriceHolderV5.json +2 -2
  98. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceMerkleV5.sol/MinterSetPriceMerkleV5.json +2 -2
  99. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceOnChainAllowV0.sol/MinterSetPriceOnChainAllowV0.json +522 -0
  100. package/artifacts/contracts/minter-suite/Minters/MinterSetPricePolyptychERC20V5.sol/MinterSetPricePolyptychERC20V5.json +2 -2
  101. package/artifacts/contracts/minter-suite/Minters/MinterSetPricePolyptychV5.sol/MinterSetPricePolyptychV5.json +2 -2
  102. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceTieredAllowV1.sol/MinterSetPriceTieredAllowV1.json +715 -0
  103. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceV5.sol/MinterSetPriceV5.json +2 -2
  104. package/artifacts/contracts/minter-suite/Minters/MinterSlidingScaleV0.sol/MinterSlidingScaleV0.json +431 -0
  105. package/artifacts/contracts/minter-suite/Minters/one-off/ClaimMinter.sol/ClaimMinter.json +502 -0
  106. package/artifacts/contracts/minter-suite/Minters/one-off/MinterSetPriceTieredOnChainAllowV0.sol/MinterSetPriceTieredOnChainAllowV0.json +673 -0
  107. package/artifacts/contracts/minter-suite/Minters/one-off/MinterSetPriceV5_SingleCore.sol/MinterSetPriceV5_SingleCore.json +2 -2
  108. package/artifacts/contracts/minter-suite/Minters/one-off/SeaDropXArtBlocksShim.sol/SeaDropXArtBlocksShim.json +6 -19
  109. package/artifacts/contracts/mock/BytecodeV2TextCR_DMock.sol/BytecodeV2TextCR_DMock.json +2 -2
  110. package/artifacts/contracts/mock/DummySharedMinter.sol/DummySharedMinter.json +2 -2
  111. package/artifacts/contracts/mock/ENSLibMock.sol/ENSLibMock.json +30 -0
  112. package/artifacts/contracts/mock/EnumerableSetUint16Mock.sol/EnumerableSetUint16Mock.json +146 -0
  113. package/artifacts/contracts/mock/FeistelWalkLibMock.sol/FeistelWalkLibMock.json +142 -0
  114. package/artifacts/contracts/mock/ForceSendMock.sol/ForceSendMock.json +2 -2
  115. package/artifacts/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol/GenArt721CoreV3_Engine_IncorrectCoreType.json +2 -2
  116. package/artifacts/contracts/mock/ImmutableUint16ArrayMock.sol/ImmutableUint16ArrayMock.json +89 -0
  117. package/artifacts/contracts/mock/MockAdminACLV0_PROHIBITION.sol/MockAdminACLV0Events_PROHIBITION.json +2 -2
  118. package/artifacts/contracts/mock/MockGenArt721V3Core.sol/MockGenArt721V3Core.json +490 -0
  119. package/artifacts/contracts/mock/MockPMP.sol/MockPMP.json +523 -0
  120. package/artifacts/contracts/mock/MockTransferHook.sol/MockTransferHook.json +453 -0
  121. package/artifacts/contracts/mock/MockTransferHookTestHelpers.sol/MockBatchTransferrer.json +39 -0
  122. package/artifacts/contracts/mock/MockTransferHookTestHelpers.sol/MockForwardingERC721Receiver.json +71 -0
  123. package/artifacts/contracts/mock/MockTransferHookTestHelpers.sol/MockSpoofingCore.json +94 -0
  124. package/artifacts/contracts/mock/RandomizerV2_NoAssignMock.sol/RandomizerV2_NoAssignMock.json +2 -2
  125. package/artifacts/contracts/mock/ReentrancyBatchMockShared.sol/IBatchMinter.json +64 -0
  126. package/artifacts/contracts/mock/ReentrancyBatchMockShared.sol/ReentrancyBatchMockShared.json +113 -0
  127. package/artifacts/contracts/mock/RelayExecutorTestHelper.sol/MockTarget.json +74 -0
  128. package/artifacts/contracts/mock/RelayExecutorTestHelper.sol/RelayExecutorPartialRevertsTestHelper.json +138 -0
  129. package/artifacts/contracts/mock/RelayExecutorTestHelper.sol/RelayExecutorTestHelper.json +119 -0
  130. package/artifacts/contracts/randomizer/deprecated/PolyptychRandomizerV0.sol/BasicPolyptychRandomizerV0.json +2 -2
  131. package/artifacts/contracts/randomizer/deprecated/RandomizerV2.sol/BasicRandomizerV2.json +2 -2
  132. package/artifacts/contracts/randomizer/shared/SharedRandomizerV0.sol/SharedRandomizerV0.json +2 -2
  133. package/artifacts/contracts/relay/RelayExecutor.sol/RelayExecutor.json +101 -0
  134. package/artifacts/contracts/relay/RelayExecutorPartialReverts.sol/RelayExecutorPartialReverts.json +120 -0
  135. package/artifacts/contracts/royalty-registry/GenArt721RoyaltyOverrideSplits.sol/GenArt721RoyaltyOverrideSplits.json +263 -0
  136. package/artifacts/contracts/utils/MintMulticallUtil.sol/IMinterSetPriceV5.json +40 -0
  137. package/artifacts/contracts/utils/MintMulticallUtil.sol/MintMulticallUtil.json +163 -0
  138. package/artifacts/contracts/web3call/PMPV0.sol/PMPV0.json +2 -2
  139. package/artifacts/contracts/web3call/PMPV1.sol/PMPV1.json +648 -0
  140. package/artifacts/contracts/web3call/augment-hooks/InjectBaseGasFee.sol/InjectBaseGasFee.json +2 -2
  141. package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeight.sol/InjectBlockHeight.json +83 -0
  142. package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeightAndArtistProjectOverrides.sol/InjectBlockHeightAndArtistProjectOverrides.json +226 -0
  143. package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeightAndNumOwnedProjectTokens.sol/InjectBlockHeightAndNumOwnedProjectTokens.json +182 -0
  144. package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeightProjectInvocationsAndArtistProjectOverrides.sol/InjectBlockHeightProjectInvocationsAndArtistProjectOverrides.json +226 -0
  145. package/artifacts/contracts/web3call/augment-hooks/InjectBlockTimestamp.sol/InjectBlockTimestamp.json +83 -0
  146. package/artifacts/contracts/web3call/augment-hooks/InjectBytesPerToken.sol/InjectBytesPerToken.json +358 -0
  147. package/artifacts/contracts/web3call/augment-hooks/InjectChromieSquiggleFloorPrice.sol/InjectChromieSquiggleFloorPrice.json +2 -2
  148. package/artifacts/contracts/web3call/augment-hooks/InjectIsNounsDaoMember.sol/InjectIsNounsDaoMember.json +2 -2
  149. package/artifacts/contracts/web3call/augment-hooks/InjectIsProjectFullyMinted.sol/InjectIsProjectFullyMinted.json +2 -2
  150. package/artifacts/contracts/web3call/augment-hooks/InjectPolyptychTokenHash.sol/InjectPolyptychTokenHash.json +2 -2
  151. package/artifacts/contracts/web3call/augment-hooks/InjectTokenHashSeed.sol/InjectTokenHashSeed.json +2 -2
  152. package/artifacts/contracts/web3call/augment-hooks/InjectTokenOwner.sol/InjectTokenOwner.json +2 -2
  153. package/artifacts/contracts/web3call/augment-hooks/InjectTokenOwnerEthBalance.sol/InjectTokenOwnerEthBalance.json +2 -2
  154. package/artifacts/contracts/web3call/augment-hooks/InjectUSDCTotalSupply.sol/InjectUSDCTotalSupply.json +2 -2
  155. package/artifacts/contracts/web3call/augment-hooks/SRHooks.sol/SRHooks.json +899 -0
  156. package/artifacts/contracts/web3call/combined-hooks/LiftHooks.sol/IGenArt721V0_Minimal.json +30 -0
  157. package/artifacts/contracts/web3call/combined-hooks/LiftHooks.sol/IRelic.json +35 -0
  158. package/artifacts/contracts/web3call/combined-hooks/LiftHooks.sol/LiftHooks.json +256 -0
  159. package/artifacts/contracts/web3call/configure-hooks/{abstractPMPConfigureHook.sol → AbstractPMPConfigureHook.sol}/AbstractPMPConfigureHook.json +1 -1
  160. package/artifacts/contracts/web3call/configure-hooks/PMPConfigureLockHook.sol/PMPConfigureLockHook.json +195 -0
  161. package/contracts/AdminACLV0RoyaltyRegistry.sol +142 -0
  162. package/contracts/DependencyRegistryV0.sol +143 -13
  163. package/contracts/GenArt721CoreV3_Curated.sol +1 -1
  164. package/contracts/GenArt721CoreV3_Curated_Flex.sol +173 -0
  165. package/contracts/GenArt721CoreV3_Explorations_Flex.sol +159 -0
  166. package/contracts/engine/V3/EngineFactoryV0.sol +20 -1
  167. package/contracts/engine/V3/GenArt721CoreV3_Engine.sol +286 -299
  168. package/contracts/engine/V3/GenArt721CoreV3_Engine_Flex.sol +292 -301
  169. package/contracts/engine/V3/transfer-hooks/AbstractTransferHook.sol +101 -0
  170. package/contracts/engine/V3/transfer-hooks/OwnerHistoryTransferHook.sol +291 -0
  171. package/contracts/generator/GenArt721GeneratorV0.sol +145 -47
  172. package/contracts/helpers/ERC1967ProxyImport.sol +5 -0
  173. package/contracts/interfaces/v0.8.x/IArtblocksRoyaltyOverride.sol +1 -1
  174. package/contracts/interfaces/v0.8.x/IClaimMinter.sol +70 -0
  175. package/contracts/interfaces/v0.8.x/IDependencyRegistryV0.sol +95 -0
  176. package/contracts/interfaces/v0.8.x/IEngineFactoryV0.sol +15 -0
  177. package/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol +21 -2
  178. package/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol +72 -0
  179. package/contracts/interfaces/v0.8.x/IOwnerHistoryTransferHook.sol +119 -0
  180. package/contracts/interfaces/v0.8.x/ISRHooks.sol +229 -0
  181. package/contracts/interfaces/v0.8.x/ISeaDropShimForContract.sol +0 -6
  182. package/contracts/interfaces/v0.8.x/ISharedMinterTieredAllowV0.sol +69 -0
  183. package/contracts/interfaces/v0.8.x/ITransferHook.sol +48 -0
  184. package/contracts/libs/v0.8.x/DependencyRegistryStorageLib.sol +12 -0
  185. package/contracts/libs/v0.8.x/ENSLib.sol +85 -0
  186. package/contracts/libs/v0.8.x/EnumerableSetUint16.sol +429 -0
  187. package/contracts/libs/v0.8.x/FeistelWalkLib.sol +221 -0
  188. package/contracts/libs/v0.8.x/ImmutableUint16Array.sol +201 -0
  189. package/contracts/libs/v0.8.x/V3EngineLib.sol +490 -0
  190. package/contracts/libs/v0.8.x/V3TransferHookLib.sol +251 -0
  191. package/contracts/libs/v0.8.x/minter-libs/OnChainAllowlistLib.sol +165 -0
  192. package/contracts/minter-suite/Minters/MinterSetPriceOnChainAllowV0.sol +632 -0
  193. package/contracts/minter-suite/Minters/MinterSetPriceTieredAllowV1.sol +871 -0
  194. package/contracts/minter-suite/Minters/MinterSlidingScaleV0.sol +493 -0
  195. package/contracts/minter-suite/Minters/one-off/ClaimMinter.sol +595 -0
  196. package/contracts/minter-suite/Minters/one-off/MinterSetPriceTieredOnChainAllowV0.sol +894 -0
  197. package/contracts/minter-suite/Minters/one-off/SeaDropXArtBlocksShim.sol +59 -41
  198. package/contracts/mock/ENSLibMock.sol +25 -0
  199. package/contracts/mock/EnumerableSetUint16Mock.sol +88 -0
  200. package/contracts/mock/FeistelWalkLibMock.sol +81 -0
  201. package/contracts/mock/ImmutableUint16ArrayMock.sol +62 -0
  202. package/contracts/mock/MockGenArt721V3Core.sol +416 -0
  203. package/contracts/mock/MockPMP.sol +287 -0
  204. package/contracts/mock/MockTransferHook.sol +160 -0
  205. package/contracts/mock/MockTransferHookTestHelpers.sol +96 -0
  206. package/contracts/mock/ReentrancyBatchMockShared.sol +53 -0
  207. package/contracts/mock/RelayExecutorTestHelper.sol +44 -0
  208. package/contracts/relay/RelayExecutor.sol +61 -0
  209. package/contracts/relay/RelayExecutorPartialReverts.sol +79 -0
  210. package/contracts/royalty-registry/GenArt721RoyaltyOverrideSplits.sol +130 -0
  211. package/contracts/utils/MintMulticallUtil.sol +103 -0
  212. package/contracts/web3call/PMPV1.sol +1204 -0
  213. package/contracts/web3call/augment-hooks/AbstractPMPAugmentHook.sol +1 -1
  214. package/contracts/web3call/augment-hooks/InjectBaseGasFee.sol +1 -1
  215. package/contracts/web3call/augment-hooks/InjectBlockHeight.sol +57 -0
  216. package/contracts/web3call/augment-hooks/InjectBlockHeightAndArtistProjectOverrides.sol +310 -0
  217. package/contracts/web3call/augment-hooks/InjectBlockHeightAndNumOwnedProjectTokens.sol +141 -0
  218. package/contracts/web3call/augment-hooks/InjectBlockHeightProjectInvocationsAndArtistProjectOverrides.sol +326 -0
  219. package/contracts/web3call/augment-hooks/InjectBlockTimestamp.sol +57 -0
  220. package/contracts/web3call/augment-hooks/InjectBytesPerToken.sol +279 -0
  221. package/contracts/web3call/augment-hooks/InjectIsProjectFullyMinted.sol +1 -1
  222. package/contracts/web3call/augment-hooks/InjectTokenHashSeed.sol +1 -1
  223. package/contracts/web3call/augment-hooks/InjectTokenOwner.sol +1 -1
  224. package/contracts/web3call/augment-hooks/InjectTokenOwnerEthBalance.sol +1 -1
  225. package/contracts/web3call/augment-hooks/SRHooks.sol +1722 -0
  226. package/contracts/web3call/combined-hooks/LiftHooks.sol +304 -0
  227. package/contracts/web3call/configure-hooks/{abstractPMPConfigureHook.sol → AbstractPMPConfigureHook.sol} +1 -1
  228. package/contracts/web3call/configure-hooks/PMPConfigureLockHook.sol +107 -0
  229. package/package.json +39 -24
@@ -0,0 +1,1204 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-only
2
+ // Created By: Art Blocks Inc.
3
+
4
+ pragma solidity 0.8.22;
5
+
6
+ import {IWeb3Call} from "../interfaces/v0.8.x/IWeb3Call.sol";
7
+ import {IPMPV0} from "../interfaces/v0.8.x/IPMPV0.sol";
8
+ import {IPMPConfigureHook} from "../interfaces/v0.8.x/IPMPConfigureHook.sol";
9
+ import {IPMPAugmentHook} from "../interfaces/v0.8.x/IPMPAugmentHook.sol";
10
+ import {IGenArt721CoreContractV3_Base} from "../interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol";
11
+ import {IERC721} from "@openzeppelin-5.0/contracts/token/ERC721/IERC721.sol";
12
+ import {IDelegateRegistry} from "../interfaces/v0.8.x/IDelegateRegistry.sol";
13
+
14
+ import {ERC165Checker} from "@openzeppelin-5.0/contracts/utils/introspection/ERC165Checker.sol";
15
+ import {Strings} from "@openzeppelin-5.0/contracts/utils/Strings.sol";
16
+ import {ReentrancyGuard} from "@openzeppelin-5.0/contracts/utils/ReentrancyGuard.sol";
17
+
18
+ import {Web3Call} from "./Web3Call.sol";
19
+ import {ImmutableStringArray} from "../libs/v0.8.x/ImmutableStringArray.sol";
20
+ import {ABHelpers} from "../libs/v0.8.x/ABHelpers.sol";
21
+ import {Bytes32Strings} from "../libs/v0.8.x/Bytes32Strings.sol";
22
+
23
+ /**
24
+ * @title Project Metadata Parameters (PMP) contract, V1
25
+ * @author Art Blocks Inc.
26
+ * @notice This contract enables Artists to define and configure project parameters that token
27
+ * owners can set within constraints. This provides a standardized way for projects to expose
28
+ * configurable parameters that can be used by renderers and other contracts.
29
+ * @notice V1 changes vs V0:
30
+ * 1. `pmpLockedAfterTimestamp` is interpreted as BOTH a configuration lock AND a value lock
31
+ * (no party may configure a token's value for the param after the timestamp). In V0, only
32
+ * the configuration was locked and token values remained writable after the timestamp.
33
+ * 2. A locked param definition may be restated in a later `configureProject` call if and only
34
+ * if every artist-configured field is identical to storage. This lets the artist add or edit
35
+ * still-unlocked params without rewriting locked ones. Omitting a locked key remains allowed
36
+ * (it drops out of the active key list). Any difference in a restated locked definition
37
+ * reverts. Locked definition fields are never written (only `highestConfigNonce` is bumped
38
+ * so the key stays in the active config).
39
+ * WARNING: This contract implements an open protocol for parameter configuration, and does not
40
+ * restrict usage to Art Blocks or its affiliates. Use with caution. The contract does assume
41
+ * that the core contract conforms to the IGenArt721CoreContractV3_Base interface for authentication,
42
+ * and that the core contract is using the ERC721 standard for token management. These assumptions
43
+ * may not hold for all core contracts, especially those outside of the Art Blocks ecosystem.
44
+ * @notice Artists may configure arbitrary external hooks for post-token-configuration and
45
+ * read-augmentation. These hooks are executed at the end of the token configuration process
46
+ * and when reading token PMPs, respectively. These hooks are validated for ERC165 interface
47
+ * compatibility, but have the ability to execute arbitrary code at the discretion of the artist.
48
+ * The contract implements reentrancy guards to protect against reentrancy attacks during hook calls,
49
+ * resulting in the subcall having a similar level as a minting contract sending funds to an artist's
50
+ * additional payee address during a token sale. Use appropriate discretion.
51
+ * WARNING: Hook calls are not validated, and may cause unexpected behavior. These include:
52
+ * - Hooks may revert the transaction, resulting in denial of service
53
+ * - Hooks may augment the token's returned parameters with unintended data, resulting in
54
+ * unexpected behavior
55
+ * The artist is solely responsible for configuring hooks and validating their behavior.
56
+ * @dev This contract implements the IWeb3Call and IPMPV0 interfaces, providing functionality
57
+ * for parameter configuration and retrieval. It includes support for various parameter types,
58
+ * authorization options, and hooks for extending functionality.
59
+ */
60
+ contract PMPV1 is IPMPV0, Web3Call, ReentrancyGuard {
61
+ using Strings for string;
62
+ using Strings for uint256;
63
+ using Strings for int256;
64
+ using ImmutableStringArray for ImmutableStringArray.StringArray;
65
+ using Bytes32Strings for bytes32;
66
+
67
+ IDelegateRegistry public immutable delegateRegistry;
68
+ bytes32 public constant DELEGATION_REGISTRY_TOKEN_OWNER_RIGHTS =
69
+ bytes32("postmintparameters");
70
+
71
+ bytes32 private constant _TYPE = "PMPV1";
72
+
73
+ bytes16 private constant _HEX_DIGITS = "0123456789abcdef";
74
+ uint256 private constant _DECIMAL_PRECISION_DIGITS = 10;
75
+ uint256 private constant _DECIMAL_PRECISION =
76
+ 10 ** _DECIMAL_PRECISION_DIGITS;
77
+
78
+ // @dev min hex color assumed to be 0x000000
79
+ // @dev intentionally not including alpha channel, can be separate PMP if desired
80
+ uint256 private constant _HEX_COLOR_MAX = 0xFFFFFF;
81
+
82
+ uint256 private constant _TIMESTAMP_MIN = 0; // @dev unix timestamp, 0 = 1970-01-01
83
+ uint256 private constant _TIMESTAMP_MAX = type(uint64).max; // max guardrail, ~10 billion years
84
+
85
+ /**
86
+ * @notice Storage structure for parameter configuration.
87
+ * @dev Includes additional field highestConfigNonce compared to PMPConfig.
88
+ */
89
+ struct PMPConfigStorage {
90
+ // @dev highest config nonce for which this PMPConfig is valid (relative to projectConfig.configNonce)
91
+ uint8 highestConfigNonce; // slot 0: 1 byte
92
+ AuthOption authOption; // slot 0: 1 byte
93
+ ParamType paramType; // slot 0: 1 byte
94
+ uint48 pmpLockedAfterTimestamp; // slot 0: 6 bytes // @dev uint48 is sufficient to store ~2^48 seconds, ~8,900 years
95
+ address authAddress; // slot 0: 20 bytes
96
+ // @dev store array length as uint8 in slot 0 for SLOAD efficiency during token configuration
97
+ uint8 selectOptionsLength; // slot 0: 1 byte
98
+ // @dev use immutable string array for storage efficiency during project configuration
99
+ ImmutableStringArray.StringArray selectOptions; // slot 1: 32 bytes
100
+ bytes32 minRange; // slot 2: 32 bytes
101
+ bytes32 maxRange; // slot 3: 32 bytes
102
+ }
103
+
104
+ // @dev core contract address and projectId are implicit based on mapping pointing to ProjectConfig struct
105
+ struct ProjectConfig {
106
+ // @dev array of pmpKeys for efficient enumeration, uses efficient SSTORE2 storage
107
+ ImmutableStringArray.StringArray pmpKeys; // slot 0: 32 bytes
108
+ // @dev mapping of pmpKeys to PMPConfigStorage for O(1) access, and cheap updates when no changes
109
+ mapping(bytes32 pmpKeyHash => PMPConfigStorage pmpConfigStorage) pmpConfigsStorage; // slot 1: 32 bytes
110
+ // config nonce that is incremented during each configureProject call
111
+ uint8 configNonce; // slot 2: 1 byte
112
+ // post-configuration hook to be called after a token's PMP is configured
113
+ IPMPConfigureHook tokenPMPPostConfigHook; // slot 2: 20 bytes
114
+ // token pmp read augmentation hook to be called when reading a token's PMPs
115
+ IPMPAugmentHook tokenPMPReadAugmentationHook; // slot 3: 20 bytes
116
+ }
117
+
118
+ // mapping of ProjectConfig structs for each project
119
+ mapping(address coreContract => mapping(uint256 projectId => ProjectConfig projectConfig)) projectConfigs;
120
+
121
+ // mapping of PMP structs for each token
122
+ mapping(address coreContract => mapping(uint256 tokenId => mapping(bytes32 pmpKeyHash => PMPStorage pmp))) tokenPMPs;
123
+
124
+ /**
125
+ * @notice Constructor for PMPV1 contract.
126
+ * @param delegateRegistry_ The address of the delegate registry contract. Intended to be
127
+ * the delegate.xyz v2 contract.
128
+ */
129
+ constructor(IDelegateRegistry delegateRegistry_) {
130
+ delegateRegistry = delegateRegistry_;
131
+ emit DelegationRegistryUpdated(address(delegateRegistry_));
132
+ }
133
+
134
+ /**
135
+ * @notice Returns the type of this PMP contract, e.g. "PMPV1".
136
+ * @dev Enables on-chain and off-chain consumers to distinguish PMP versions, since
137
+ * PMPV0 and PMPV1 share the same IPMPV0 ABI but differ in value-lock behavior.
138
+ * @return The type identifier of this contract as a string.
139
+ */
140
+ function pmpType() external pure returns (string memory) {
141
+ return _TYPE.toString();
142
+ }
143
+
144
+ /**
145
+ * @notice Configure project hooks for post-configuration and read augmentation.
146
+ * WARNING: Hook calls may revert the transaction, and may cause unexpected behavior.
147
+ * The artist is solely responsible for configuring hooks and validating their behavior.
148
+ * Improper configuration or hooks with unexpected behavior may result in denial of service,
149
+ * unexpected behavior, or other issues.
150
+ * @param coreContract The address of the core contract.
151
+ * @param projectId The project ID to configure hooks for.
152
+ * @param tokenPMPPostConfigHook The hook to call after a token's PMP is configured.
153
+ * @param tokenPMPReadAugmentationHook The hook to call when reading a token's PMPs.
154
+ * @dev Only the project artist can configure project hooks.
155
+ * @dev Both hooks are validated for ERC165 interface compatibility.
156
+ * @dev Uses nonReentrant modifier to prevent reentrancy attacks during hook calls or auth checks.
157
+ */
158
+ function configureProjectHooks(
159
+ address coreContract,
160
+ uint256 projectId,
161
+ IPMPConfigureHook tokenPMPPostConfigHook,
162
+ IPMPAugmentHook tokenPMPReadAugmentationHook
163
+ ) external nonReentrant {
164
+ // only artists may configure project hooks
165
+ _onlyArtist({
166
+ coreContract: coreContract,
167
+ projectId: projectId,
168
+ sender: msg.sender
169
+ });
170
+ // validation - check for ERC165 implementation for non-null hooks
171
+ if (address(tokenPMPPostConfigHook) != address(0)) {
172
+ // use ERC165 checker to validate implementation
173
+ require(
174
+ ERC165Checker.supportsInterface({
175
+ account: address(tokenPMPPostConfigHook),
176
+ interfaceId: type(IPMPConfigureHook).interfaceId
177
+ }),
178
+ "PMP: tokenPMPPostConfigHook does not implement IPMPConfigureHook"
179
+ );
180
+ }
181
+ if (address(tokenPMPReadAugmentationHook) != address(0)) {
182
+ require(
183
+ ERC165Checker.supportsInterface({
184
+ account: address(tokenPMPReadAugmentationHook),
185
+ interfaceId: type(IPMPAugmentHook).interfaceId
186
+ }),
187
+ "PMP: tokenPMPReadAugmentationHook does not implement IPMPAugmentHook"
188
+ );
189
+ }
190
+ // update projectConfig
191
+ ProjectConfig storage projectConfig = projectConfigs[coreContract][
192
+ projectId
193
+ ];
194
+ projectConfig.tokenPMPPostConfigHook = tokenPMPPostConfigHook;
195
+ projectConfig
196
+ .tokenPMPReadAugmentationHook = tokenPMPReadAugmentationHook;
197
+
198
+ // emit event
199
+ emit ProjectHooksConfigured({
200
+ coreContract: coreContract,
201
+ projectId: projectId,
202
+ tokenPMPPostConfigHook: tokenPMPPostConfigHook,
203
+ tokenPMPReadAugmentationHook: tokenPMPReadAugmentationHook
204
+ });
205
+ }
206
+
207
+ /**
208
+ * @notice Configure the available parameters for a project and their constraints.
209
+ * @param coreContract The address of the core contract.
210
+ * @param projectId The project ID to configure parameters for.
211
+ * @param pmpInputConfigs Array of parameter configurations defining the available parameters.
212
+ * This replaces the project's active key list. Locked keys may be omitted (they drop
213
+ * out of the active list). Locked keys that are included must match storage exactly;
214
+ * any difference reverts. Unlocked keys are validated and written as usual.
215
+ * @dev Only the project artist can configure project parameters.
216
+ * @dev Each unlocked configuration is validated for proper parameter type and constraints.
217
+ * @dev Locked configurations skip `_validatePMPConfig` (their lock timestamp is already in
218
+ * the past) and are checked with `_pmpConfigsEqual` instead. Matching locked keys are not
219
+ * written — only `highestConfigNonce` is updated so they remain in the active config.
220
+ * @dev The project's configuration nonce is incremented with each call.
221
+ * @dev Only <= 256 configs are supported.
222
+ * @dev Only <= 255 bytes are supported for pmpKeys.
223
+ * @dev nonReentrant due to auth check that requires interaction.
224
+ */
225
+ function configureProject(
226
+ address coreContract,
227
+ uint256 projectId,
228
+ PMPInputConfig[] calldata pmpInputConfigs
229
+ ) external nonReentrant {
230
+ // only artists may configure projects
231
+ _onlyArtist({
232
+ coreContract: coreContract,
233
+ projectId: projectId,
234
+ sender: msg.sender
235
+ });
236
+ // validate pmpInputConfigs key lengths
237
+ uint256 pmpInputConfigsLength = pmpInputConfigs.length;
238
+ // @dev no coverage on else branch due to test complexity
239
+ require(pmpInputConfigsLength <= 256, "PMP: Only <= 256 configs");
240
+ for (uint256 i = 0; i < pmpInputConfigsLength; i++) {
241
+ uint256 keyLengthBytes = bytes(pmpInputConfigs[i].key).length;
242
+ // @dev max key length constraint is a reasonable gas guardrail
243
+ require(
244
+ keyLengthBytes > 0 && keyLengthBytes < 256,
245
+ "PMP: pmpKey cannot be empty or exceed 255 bytes"
246
+ );
247
+ }
248
+ // store pmpInputConfigs data in ProjectConfig struct
249
+ // @dev load projectConfig storage pointer
250
+ ProjectConfig storage projectConfig = projectConfigs[coreContract][
251
+ projectId
252
+ ];
253
+ // increment config nonce
254
+ // @dev solidity ^0.8 reverts on overflow (greater than 255 edits not supported)
255
+ uint8 newConfigNonce = projectConfig.configNonce + 1;
256
+ projectConfig.configNonce = newConfigNonce;
257
+
258
+ // efficiently sync pmp keys to ProjectConfig
259
+ // copy input pmp keys to memory array for efficient passing to _syncPMPKeys
260
+ string[] memory pmpKeys = new string[](pmpInputConfigsLength);
261
+ for (uint256 i = 0; i < pmpInputConfigsLength; i++) {
262
+ pmpKeys[i] = pmpInputConfigs[i].key;
263
+ }
264
+ _syncPMPKeys({inputKeys: pmpKeys, projectConfig: projectConfig});
265
+ // store pmp configs in ProjectConfig struct's mapping
266
+ for (uint256 i = 0; i < pmpInputConfigsLength; i++) {
267
+ // store pmpConfigStorage in ProjectConfig struct's mapping
268
+ PMPConfigStorage storage pmpConfigStorage = projectConfig
269
+ .pmpConfigsStorage[_getStringHash(pmpKeys[i])];
270
+ // locked keys may only be restated if the definition is unchanged.
271
+ // skip `_validatePMPConfig` (it rejects past lock timestamps) and do not
272
+ // write any definition fields, including the selectOptions SSTORE2 pointer.
273
+ if (
274
+ !_isPMPLockTimestampOpen(
275
+ pmpConfigStorage.pmpLockedAfterTimestamp
276
+ )
277
+ ) {
278
+ require(
279
+ _pmpConfigsEqual(
280
+ pmpConfigStorage,
281
+ pmpInputConfigs[i].pmpConfig
282
+ ),
283
+ "PMP: pmp is locked and cannot be updated"
284
+ );
285
+ pmpConfigStorage.highestConfigNonce = newConfigNonce;
286
+ continue;
287
+ }
288
+ _validatePMPConfig(pmpInputConfigs[i].pmpConfig);
289
+ // update highestConfigNonce
290
+ pmpConfigStorage.highestConfigNonce = newConfigNonce;
291
+ // copy function input pmpConfig data to pmpConfigStorage
292
+ PMPConfig memory inputPMPConfig = pmpInputConfigs[i].pmpConfig;
293
+ pmpConfigStorage.authOption = inputPMPConfig.authOption;
294
+ pmpConfigStorage.paramType = inputPMPConfig.paramType;
295
+ pmpConfigStorage.pmpLockedAfterTimestamp = inputPMPConfig
296
+ .pmpLockedAfterTimestamp;
297
+ pmpConfigStorage.authAddress = inputPMPConfig.authAddress;
298
+ // @dev length already validated <= 255 in _validatePMPConfig, safe to cast unchecked
299
+ pmpConfigStorage.selectOptionsLength = uint8(
300
+ inputPMPConfig.selectOptions.length
301
+ );
302
+ // @dev ImmutableStringArray is optimized for the case where the array is empty.
303
+ ImmutableStringArray.store(
304
+ pmpConfigStorage.selectOptions,
305
+ inputPMPConfig.selectOptions
306
+ );
307
+ pmpConfigStorage.minRange = inputPMPConfig.minRange;
308
+ pmpConfigStorage.maxRange = inputPMPConfig.maxRange;
309
+ }
310
+
311
+ // emit event
312
+ emit ProjectConfigured({
313
+ coreContract: coreContract,
314
+ projectId: projectId,
315
+ pmpInputConfigs: pmpInputConfigs,
316
+ projectConfigNonce: newConfigNonce
317
+ });
318
+ }
319
+
320
+ /**
321
+ * @notice Configure the parameters for a specific token according to project constraints.
322
+ * WARNING: This contract represents an open protocol for parameter configuration, and does not
323
+ * restrict usage to Art Blocks or its affiliates. Use with caution. The contract does assume
324
+ * that the core contract conforms to the IGenArt721CoreContractV3_Base interface for authentication,
325
+ * and that the core contract is using the ERC721 standard for token management. These assumptions
326
+ * may not hold for all core contracts, especially those outside of the Art Blocks ecosystem.
327
+ * WARNING: Hook calls may revert the transaction, and may cause unexpected behavior.
328
+ * The artist is solely responsible for configuring hooks and validating their behavior.
329
+ * Improper configuration or hooks with unexpected behavior may result in denial of service,
330
+ * unexpected behavior, or other issues.
331
+ * ERC-721 Token-level wallet delegation for the TokenOwner role is supported via delegate.xyz v2.
332
+ * For opt-in granular control of rights specific to these operations, vault owners may define subdelegations
333
+ * with bytes32 rights "postmintparameters". Per the delegate.xyz v2 specification, delegations made with the
334
+ * empty string "" will be interpreted as a full delegation of all rights.
335
+ * @param coreContract The address of the core contract.
336
+ * @param tokenId The tokenId of the token to configure.
337
+ * @param pmpInputs The parameter inputs to configure for the token.
338
+ * @dev Validates each parameter input against the project's configuration.
339
+ * @dev Stores the configured parameters for the token.
340
+ * @dev Calls the post-configuration hook if one is configured for the project.
341
+ * @dev Uses nonReentrant modifier to prevent reentrancy attacks during hook calls or auth checks.
342
+ */
343
+ function configureTokenParams(
344
+ address coreContract,
345
+ uint256 tokenId,
346
+ PMPInput[] calldata pmpInputs
347
+ ) external nonReentrant {
348
+ ProjectConfig storage projectConfig = projectConfigs[coreContract][
349
+ ABHelpers.tokenIdToProjectId(tokenId)
350
+ ];
351
+ // preallocate memory for auth addresses of each pmpInput
352
+ address[] memory authAddresses = new address[](pmpInputs.length);
353
+ // assign each pmpInput to the token
354
+ // @dev pmpInputs processed sequentially in order of input
355
+ for (uint256 i = 0; i < pmpInputs.length; i++) {
356
+ bytes32 pmpKeyHash = _getStringHash(pmpInputs[i].key);
357
+ PMPInput memory pmpInput = pmpInputs[i];
358
+ PMPStorage storage tokenPMP = tokenPMPs[coreContract][tokenId][
359
+ pmpKeyHash
360
+ ];
361
+ PMPConfigStorage storage pmpConfigStorage = projectConfig
362
+ .pmpConfigsStorage[pmpKeyHash];
363
+ // validate pmpInput + record the authenticated address used to configure the pmpInput
364
+ authAddresses[i] = _validatePMPInputAndAuth({
365
+ tokenId: tokenId,
366
+ coreContract: coreContract,
367
+ pmpInput: pmpInput,
368
+ pmpConfigStorage: pmpConfigStorage,
369
+ projectConfigNonce: projectConfig.configNonce
370
+ });
371
+ // store pmpInput data in PMPStorage struct
372
+ tokenPMP.configuredParamType = pmpConfigStorage.paramType;
373
+ // only assign the value that affects param type (gas savings)
374
+ if (pmpConfigStorage.paramType == ParamType.String) {
375
+ if (pmpInput.configuringArtistString) {
376
+ tokenPMP.artistConfiguredValueString = pmpInput
377
+ .configuredValueString;
378
+ } else {
379
+ tokenPMP.nonArtistConfiguredValueString = pmpInput
380
+ .configuredValueString;
381
+ }
382
+ } else {
383
+ tokenPMP.configuredValue = pmpInput.configuredValue;
384
+ }
385
+ // call post-config hook if configured for the project
386
+ // @dev this function is nonreentrant for additional reentrancy protection
387
+ // @dev intentionally revert entire transaction if hook reverts to prevent latent failure
388
+ if (address(projectConfig.tokenPMPPostConfigHook) != address(0)) {
389
+ projectConfig.tokenPMPPostConfigHook.onTokenPMPConfigure({
390
+ coreContract: coreContract,
391
+ tokenId: tokenId,
392
+ pmpInput: pmpInput
393
+ });
394
+ }
395
+ }
396
+
397
+ // emit event
398
+ emit TokenParamsConfigured({
399
+ coreContract: coreContract,
400
+ tokenId: tokenId,
401
+ pmpInputs: pmpInputs,
402
+ authAddresses: authAddresses
403
+ });
404
+ }
405
+
406
+ /**
407
+ * @notice Get the token parameters for a given token.
408
+ * If none are configured, the tokenParams should be empty.
409
+ * WARNING: Hook calls may revert the transaction, and may cause unexpected behavior.
410
+ * The artist is solely responsible for configuring hooks and validating their behavior.
411
+ * Improper configuration or hooks with unexpected behavior may result in denial of service,
412
+ * unexpected behavior, or other issues.
413
+ * @param coreContract The address of the core contract to call.
414
+ * @param tokenId The tokenId of the token to get data for.
415
+ * @return tokenParams An array of token parameters for the queried token.
416
+ */
417
+ function getTokenParams(
418
+ address coreContract,
419
+ uint256 tokenId
420
+ )
421
+ external
422
+ view
423
+ override
424
+ returns (IWeb3Call.TokenParam[] memory tokenParams)
425
+ {
426
+ uint256 projectId = ABHelpers.tokenIdToProjectId(tokenId);
427
+ ProjectConfig storage projectConfig = projectConfigs[coreContract][
428
+ projectId
429
+ ];
430
+ string[] memory pmpKeys = projectConfig.pmpKeys.getAll();
431
+ uint256 pmpKeysLength = pmpKeys.length;
432
+ // @dev initialize tokenParams array with maximum possible length
433
+ tokenParams = new IWeb3Call.TokenParam[](pmpKeysLength);
434
+ uint256 populatedParamsIndex = 0; // @dev index of next populated tokenParam
435
+ for (uint256 i = 0; i < pmpKeysLength; i++) {
436
+ // load pmp value
437
+ bytes32 pmpKeyHash = _getStringHash(pmpKeys[i]);
438
+ (bool isConfigured, string memory value) = _getPMPValue({
439
+ pmpConfigStorage: projectConfig.pmpConfigsStorage[pmpKeyHash],
440
+ pmp: tokenPMPs[coreContract][tokenId][pmpKeyHash]
441
+ });
442
+ // continue when param is unconfigured
443
+ if (!isConfigured) {
444
+ // continue without incrementing populatedParamsIndex
445
+ continue;
446
+ }
447
+ // append configured to tokenParams array
448
+ tokenParams[populatedParamsIndex] = IWeb3Call.TokenParam({
449
+ key: pmpKeys[i],
450
+ value: value
451
+ });
452
+ populatedParamsIndex++;
453
+ }
454
+ // truncate tokenParams array to populatedParamsIndex via assembly
455
+ assembly {
456
+ // directly modify the memory array length in memory
457
+ mstore(tokenParams, populatedParamsIndex)
458
+ }
459
+
460
+ // call augmentation hook if configured for the project
461
+ if (address(projectConfig.tokenPMPReadAugmentationHook) != address(0)) {
462
+ // assign return value to the augmented tokenParams
463
+ // @dev executed in read-only context, artist-configured hook contract (not entirely arbitrary)
464
+ tokenParams = projectConfig
465
+ .tokenPMPReadAugmentationHook
466
+ .onTokenPMPReadAugmentation({
467
+ coreContract: coreContract,
468
+ tokenId: tokenId,
469
+ tokenParams: tokenParams
470
+ });
471
+ }
472
+
473
+ // @dev implicitly returns tokenParams
474
+ }
475
+
476
+ // ---- introspection view functions ----
477
+
478
+ /**
479
+ * @notice Get the project config for a given project.
480
+ * @param coreContract The address of the core contract to call.
481
+ * @param projectId The projectId of the project to get data for.
482
+ * @return pmpKeys The configured pmpKeys for the project.
483
+ * @return configNonce The config nonce for the project.
484
+ * @return tokenPMPPostConfigHook The tokenPMPPostConfigHook for the project.
485
+ * @return tokenPMPReadAugmentationHook The tokenPMPReadAugmentationHook for the project.
486
+ */
487
+ function getProjectConfig(
488
+ address coreContract,
489
+ uint256 projectId
490
+ )
491
+ external
492
+ view
493
+ returns (
494
+ string[] memory pmpKeys,
495
+ uint8 configNonce,
496
+ IPMPConfigureHook tokenPMPPostConfigHook,
497
+ IPMPAugmentHook tokenPMPReadAugmentationHook
498
+ )
499
+ {
500
+ ProjectConfig storage projectConfig = projectConfigs[coreContract][
501
+ projectId
502
+ ];
503
+ // @dev edge case - uninitialized pmpKeys - empty array returned by getAll()
504
+ pmpKeys = projectConfig.pmpKeys.getAll();
505
+ configNonce = projectConfig.configNonce;
506
+ tokenPMPPostConfigHook = projectConfig.tokenPMPPostConfigHook;
507
+ tokenPMPReadAugmentationHook = projectConfig
508
+ .tokenPMPReadAugmentationHook;
509
+ }
510
+
511
+ /**
512
+ * @notice Checks if the given wallet has the owner role for the given token.
513
+ * It returns true if the wallet is the owner of the token or if the wallet
514
+ * is a delegate of the token owner; otherwise it returns false.
515
+ * Reverts if an invalid coreContract or tokenId is provided.
516
+ * Provided for convenience, as the same check is performed in the
517
+ * configureTokenParams function.
518
+ * @param wallet The wallet address to check.
519
+ * @param coreContract The address of the core contract to call.
520
+ * @param tokenId The tokenId of the token to check.
521
+ * @return isTokenOwnerOrDelegate_ True if the wallet is the owner or a delegate of the token,
522
+ * false otherwise.
523
+ */
524
+ function isTokenOwnerOrDelegate(
525
+ address wallet,
526
+ address coreContract,
527
+ uint256 tokenId
528
+ ) external view returns (bool isTokenOwnerOrDelegate_) {
529
+ (isTokenOwnerOrDelegate_, ) = _isTokenOwnerOrDelegate({
530
+ tokenId: tokenId,
531
+ coreContract: coreContract,
532
+ sender: wallet
533
+ });
534
+ }
535
+
536
+ /**
537
+ * @notice Get the PMP config from storage for a given project and pmpKey.
538
+ * @dev Returns the storage values, even if unconfigured or not part of the
539
+ * active project config. Check latestConfigNonce to verify if the pmpKey
540
+ * is part of the active project config.
541
+ * @dev any populated select options are loaded and returned as a string array
542
+ * @param coreContract The address of the core contract to call.
543
+ * @param projectId The projectId of the project to get data for.
544
+ * @param pmpKey The pmpKey of the pmp to get data for.
545
+ * @return pmpConfigView The PMP config for the given project and pmpKey.
546
+ */
547
+ function getProjectPMPConfig(
548
+ address coreContract,
549
+ uint256 projectId,
550
+ string memory pmpKey
551
+ ) external view returns (PMPConfigView memory pmpConfigView) {
552
+ PMPConfigStorage storage pmpConfigStorage = projectConfigs[
553
+ coreContract
554
+ ][projectId].pmpConfigsStorage[_getStringHash(pmpKey)];
555
+ // load values from storage
556
+ pmpConfigView.highestConfigNonce = pmpConfigStorage.highestConfigNonce;
557
+ pmpConfigView.authOption = pmpConfigStorage.authOption;
558
+ pmpConfigView.paramType = pmpConfigStorage.paramType;
559
+ pmpConfigView.pmpLockedAfterTimestamp = pmpConfigStorage
560
+ .pmpLockedAfterTimestamp;
561
+ pmpConfigView.authAddress = pmpConfigStorage.authAddress;
562
+ pmpConfigView.selectOptionsLength = pmpConfigStorage
563
+ .selectOptionsLength;
564
+ pmpConfigView.selectOptions = pmpConfigStorage.selectOptions.getAll();
565
+ pmpConfigView.minRange = pmpConfigStorage.minRange;
566
+ pmpConfigView.maxRange = pmpConfigStorage.maxRange;
567
+ }
568
+
569
+ /**
570
+ * @notice Get the PMP storage for a given token and pmpKey.
571
+ * Returns the PMP storage struct for the queried token and pmpKey. The storage
572
+ * may be stale if the token has been reconfigured, or empty if never configured.
573
+ * @param coreContract The address of the core contract to call.
574
+ * @param tokenId The tokenId of the token to get data for.
575
+ * @param pmpKey The pmpKey of the pmp to get data for.
576
+ * @return pmp The PMP storage for the given token and pmpKey.
577
+ */
578
+ function getTokenPMPStorage(
579
+ address coreContract,
580
+ uint256 tokenId,
581
+ string memory pmpKey
582
+ ) external view returns (PMPStorage memory pmp) {
583
+ pmp = tokenPMPs[coreContract][tokenId][_getStringHash(pmpKey)];
584
+ }
585
+
586
+ /**
587
+ * @notice Synchronizes the project's parameter keys with the provided input keys.
588
+ * @dev Only updates the storage if the keys have changed to optimize gas usage.
589
+ * @param inputKeys The new parameter keys to store.
590
+ * @param projectConfig The project configuration storage reference.
591
+ */
592
+ function _syncPMPKeys(
593
+ string[] memory inputKeys,
594
+ ProjectConfig storage projectConfig
595
+ ) internal {
596
+ string[] memory currentKeys = projectConfig.pmpKeys.getAll();
597
+ // determine if inputKeys are different from currentKeys to avoid expensive operation
598
+ // @dev in general, we don't expect many changes to pmpKeys after initial configuration
599
+ bool keysChanged = false;
600
+ if (inputKeys.length != currentKeys.length) {
601
+ keysChanged = true;
602
+ } else {
603
+ for (uint256 i = 0; i < inputKeys.length; i++) {
604
+ if (!inputKeys[i].equal(currentKeys[i])) {
605
+ keysChanged = true;
606
+ break;
607
+ }
608
+ }
609
+ }
610
+ // if keys changed, update projectConfig's pmpKeys (expensive operation)
611
+ if (keysChanged) {
612
+ ImmutableStringArray.store(projectConfig.pmpKeys, inputKeys);
613
+ }
614
+ }
615
+
616
+ /**
617
+ * @notice Whether a lock timestamp is still open at the current block.
618
+ * Zero means never locks. Locks once `block.timestamp >= pmpLockedAfterTimestamp`.
619
+ * @dev Shared by config-lock, value-lock, and new-config timestamp validation.
620
+ */
621
+ function _isPMPLockTimestampOpen(
622
+ uint256 pmpLockedAfterTimestamp
623
+ ) internal view returns (bool) {
624
+ return
625
+ pmpLockedAfterTimestamp == 0 ||
626
+ pmpLockedAfterTimestamp > block.timestamp;
627
+ }
628
+
629
+ /**
630
+ * @notice Exact equality of a stored PMP definition vs an input config.
631
+ * Compares every artist-configured field: authOption, paramType,
632
+ * pmpLockedAfterTimestamp, authAddress, minRange, maxRange, and selectOptions
633
+ * (length metadata plus keccak256 of the ABI-encoded string arrays, which is
634
+ * order-sensitive). Does not compare highestConfigNonce.
635
+ */
636
+ function _pmpConfigsEqual(
637
+ PMPConfigStorage storage stored,
638
+ PMPConfig calldata input
639
+ ) internal view returns (bool) {
640
+ if (
641
+ stored.authOption != input.authOption ||
642
+ stored.paramType != input.paramType ||
643
+ stored.pmpLockedAfterTimestamp != input.pmpLockedAfterTimestamp ||
644
+ stored.authAddress != input.authAddress ||
645
+ stored.minRange != input.minRange ||
646
+ stored.maxRange != input.maxRange ||
647
+ uint256(stored.selectOptionsLength) != input.selectOptions.length
648
+ ) {
649
+ return false;
650
+ }
651
+ // also compare the SSTORE2-backed strings, in case length metadata ever diverged
652
+ return
653
+ keccak256(abi.encode(stored.selectOptions.getAll())) ==
654
+ keccak256(abi.encode(input.selectOptions));
655
+ }
656
+
657
+ /**
658
+ * @notice Validates a PMP configuration. Ensures arbitrary input is valid and intentional.
659
+ * @dev Verifies parameter types, authorization options, and constraints.
660
+ * @param pmpConfig The PMP configuration to validate.
661
+ */
662
+ function _validatePMPConfig(PMPConfig calldata pmpConfig) internal view {
663
+ // memoize paramType and authOption
664
+ ParamType paramType = pmpConfig.paramType;
665
+ AuthOption authOption = pmpConfig.authOption;
666
+ // require type is not unconfigured
667
+ require(
668
+ paramType != ParamType.Unconfigured,
669
+ "PMP: paramType is unconfigured"
670
+ );
671
+ // validate locked after timestamp is in the future, or is zero (unlimited)
672
+ require(
673
+ _isPMPLockTimestampOpen(pmpConfig.pmpLockedAfterTimestamp),
674
+ "PMP: pmpLockedAfterTimestamp is in the past and not unlimited (zero)"
675
+ );
676
+ // validate enums are within bounds
677
+ // @dev no coverage else branch, this is redundant as used due to solidity compiler checks on function enum inputs
678
+ require(
679
+ authOption <= AuthOption.ArtistAndTokenOwnerAndAddress,
680
+ "PMP: Invalid authOption"
681
+ );
682
+ // @dev no coverage else branch, this is redundant as used due to solidity compiler checks on function enum inputs
683
+ require(paramType <= ParamType.String, "PMP: Invalid paramType");
684
+ // only artist+ authentication types are supported for string params
685
+ if (paramType == ParamType.String) {
686
+ require(
687
+ authOption == AuthOption.Artist ||
688
+ authOption == AuthOption.ArtistAndTokenOwner ||
689
+ authOption == AuthOption.ArtistAndTokenOwnerAndAddress ||
690
+ authOption == AuthOption.ArtistAndAddress,
691
+ "PMP: String params must have artist+ authentication"
692
+ );
693
+ }
694
+ // validate auth with address has non-zero auth address
695
+ if (
696
+ authOption == AuthOption.Address ||
697
+ authOption == AuthOption.TokenOwnerAndAddress ||
698
+ authOption == AuthOption.ArtistAndAddress ||
699
+ authOption == AuthOption.ArtistAndTokenOwnerAndAddress
700
+ ) {
701
+ require(
702
+ pmpConfig.authAddress != address(0),
703
+ "PMP: authAddress is zero"
704
+ );
705
+ } else {
706
+ // auth address must be zero for any non-address auth option
707
+ require(
708
+ pmpConfig.authAddress == address(0),
709
+ "PMP: authAddress is not zero"
710
+ );
711
+ }
712
+ // validate appropriate fields are empty
713
+ if (
714
+ paramType == ParamType.Bool ||
715
+ paramType == ParamType.String ||
716
+ paramType == ParamType.HexColor
717
+ ) {
718
+ // @dev should have all fields empty
719
+ require(
720
+ pmpConfig.selectOptions.length == 0,
721
+ "PMP: selectOptions is not empty"
722
+ );
723
+ // @dev min/max range for hex color checked during assignment, should be empty in config
724
+ require(pmpConfig.minRange == 0, "PMP: minRange is not empty");
725
+ require(pmpConfig.maxRange == 0, "PMP: maxRange is not empty");
726
+ } else if (paramType == ParamType.Select) {
727
+ // @dev select should have selectOptions and empty min/max range values
728
+ require(
729
+ pmpConfig.selectOptions.length > 0,
730
+ "PMP: selectOptions is empty"
731
+ );
732
+ require(
733
+ pmpConfig.selectOptions.length < 256,
734
+ "PMP: selectOptions length > 255"
735
+ );
736
+ // @dev do not check if options are unique on-chain
737
+ // require min/max range values are empty
738
+ require(pmpConfig.minRange == 0, "PMP: minRange is not empty");
739
+ require(pmpConfig.maxRange == 0, "PMP: maxRange is not empty");
740
+ } else if (
741
+ paramType == ParamType.Uint256Range ||
742
+ paramType == ParamType.Int256Range ||
743
+ paramType == ParamType.DecimalRange ||
744
+ paramType == ParamType.Timestamp
745
+ ) {
746
+ // @dev range params should have empty selectOptions
747
+ require(
748
+ pmpConfig.selectOptions.length == 0,
749
+ "PMP: selectOptions is not empty"
750
+ );
751
+ // require minRange is less than maxRange
752
+ if (paramType == ParamType.Int256Range) {
753
+ // cast minRange and maxRange to int256
754
+ int256 minRange = int256(uint256(pmpConfig.minRange));
755
+ int256 maxRange = int256(uint256(pmpConfig.maxRange));
756
+ require(minRange < maxRange, "PMP: minRange >= maxRange");
757
+ } else {
758
+ // cast minRange and maxRange to uint256
759
+ // @dev lt works for uint256, decimal, or timestamp types cast as uint256
760
+ uint256 minRange = uint256(pmpConfig.minRange);
761
+ uint256 maxRange = uint256(pmpConfig.maxRange);
762
+ require(minRange < maxRange, "PMP: minRange >= maxRange");
763
+ // additional guardrails on timestamp range
764
+ if (paramType == ParamType.Timestamp) {
765
+ // require maxRange is not gt _TIMESTAMP_MAX
766
+ require(
767
+ maxRange <= _TIMESTAMP_MAX,
768
+ "PMP: maxRange > _TIMESTAMP_MAX"
769
+ );
770
+ }
771
+ }
772
+ } else {
773
+ // @dev should never reach, no coverage
774
+ revert("PMP: Invalid paramType");
775
+ }
776
+ }
777
+
778
+ /**
779
+ * @notice Validates a PMP input against the project's configuration.
780
+ * Includes auth checks based on the pmpKey's current authOption.
781
+ * @dev Checks authorization, parameter type consistency, and value constraints.
782
+ * @dev Checks that pmp param is included in most recently configured PMP config for token's project.
783
+ * @param tokenId The token ID for which the parameter is being configured.
784
+ * @param coreContract The address of the core contract.
785
+ * @param pmpInput The parameter input to validate.
786
+ * @param pmpConfigStorage The project's configuration storage for this parameter.
787
+ * @param projectConfigNonce The project's current configuration nonce.
788
+ * @return permissionedAddress the address used to make the update, accounting for delegation.
789
+ */
790
+ function _validatePMPInputAndAuth(
791
+ uint256 tokenId,
792
+ address coreContract,
793
+ PMPInput memory pmpInput,
794
+ PMPConfigStorage storage pmpConfigStorage,
795
+ uint8 projectConfigNonce
796
+ ) internal view returns (address permissionedAddress) {
797
+ // check that the param is part of the project's most recently configured PMP params
798
+ // @dev use config nonce to check if param is part of most recently configured PMP params
799
+ require(
800
+ pmpConfigStorage.highestConfigNonce == projectConfigNonce,
801
+ "PMP: param not part of most recently configured PMP params"
802
+ );
803
+ // @dev V1 value-lock: once the lock timestamp has passed, the param's value is
804
+ // cemented and may not be configured by any party (artist, token owner, or address).
805
+ // A zero timestamp means the param never locks. Uses the same timestamp predicate
806
+ // as configureProject / _validatePMPConfig.
807
+ require(
808
+ _isPMPLockTimestampOpen(pmpConfigStorage.pmpLockedAfterTimestamp),
809
+ "PMP: param is locked"
810
+ );
811
+ // check that the param type matches and is not unconfigured
812
+ require(
813
+ pmpInput.configuredParamType == pmpConfigStorage.paramType,
814
+ "PMP: paramType mismatch"
815
+ );
816
+ // @dev checking value in memory for lower gas cost
817
+ require(
818
+ pmpInput.configuredParamType != ParamType.Unconfigured,
819
+ "PMP: input paramType is unconfigured"
820
+ );
821
+
822
+ // ensure caller has appropriate auth
823
+ bool isAuthenticated;
824
+ {
825
+ AuthOption authOption = pmpConfigStorage.authOption;
826
+ // if artist may configure, check and authenticate if not already authenticated
827
+ if (
828
+ authOption == AuthOption.Artist ||
829
+ authOption == AuthOption.ArtistAndAddress ||
830
+ authOption == AuthOption.ArtistAndTokenOwner ||
831
+ authOption == AuthOption.ArtistAndTokenOwnerAndAddress
832
+ ) {
833
+ bool isArtist;
834
+ (isArtist, permissionedAddress) = _isArtist({
835
+ tokenId: tokenId,
836
+ coreContract: coreContract,
837
+ sender: msg.sender
838
+ });
839
+ isAuthenticated = isArtist;
840
+ }
841
+ // if address may configure, check and authenticate if not already authenticated
842
+ if (
843
+ !isAuthenticated &&
844
+ (authOption == AuthOption.Address ||
845
+ authOption == AuthOption.TokenOwnerAndAddress ||
846
+ authOption == AuthOption.ArtistAndAddress ||
847
+ authOption == AuthOption.ArtistAndTokenOwnerAndAddress)
848
+ ) {
849
+ permissionedAddress = pmpConfigStorage.authAddress;
850
+ isAuthenticated = permissionedAddress == msg.sender;
851
+ }
852
+ // if token owner or delegate may configure, check and authenticate
853
+ // @dev check token ownerlast to enable pre-mint PMP configuration by non-token-owner
854
+ if (
855
+ !isAuthenticated &&
856
+ (authOption == AuthOption.TokenOwner ||
857
+ authOption == AuthOption.ArtistAndTokenOwner ||
858
+ authOption == AuthOption.TokenOwnerAndAddress ||
859
+ authOption == AuthOption.ArtistAndTokenOwnerAndAddress)
860
+ ) {
861
+ bool isTokenOwnerOrDelegate_;
862
+ (
863
+ isTokenOwnerOrDelegate_,
864
+ permissionedAddress
865
+ ) = _isTokenOwnerOrDelegate({
866
+ tokenId: tokenId,
867
+ coreContract: coreContract,
868
+ sender: msg.sender
869
+ });
870
+ isAuthenticated = isTokenOwnerOrDelegate_;
871
+ }
872
+ }
873
+ // if not authenticated, revert with appropriate auth error
874
+ if (!isAuthenticated) {
875
+ AuthOption authOption = pmpConfigStorage.authOption;
876
+ if (authOption == AuthOption.Artist) {
877
+ revert("PMP: artist auth required");
878
+ } else if (authOption == AuthOption.TokenOwner) {
879
+ revert("PMP: token owner auth required");
880
+ } else if (authOption == AuthOption.ArtistAndTokenOwner) {
881
+ revert("PMP: artist and token owner auth required");
882
+ } else if (authOption == AuthOption.Address) {
883
+ revert("PMP: address auth required");
884
+ } else if (authOption == AuthOption.ArtistAndTokenOwnerAndAddress) {
885
+ revert("PMP: artist and token owner and address auth required");
886
+ } else if (authOption == AuthOption.ArtistAndAddress) {
887
+ revert("PMP: artist and address auth required");
888
+ } else if (authOption == AuthOption.TokenOwnerAndAddress) {
889
+ revert("PMP: token owner and address auth required");
890
+ } else {
891
+ // @dev no coverage, this should never be reached
892
+ revert("PMP: invalid authOption");
893
+ }
894
+ }
895
+ // @dev auth check is complete
896
+
897
+ // ensure properly configured value
898
+ ParamType paramType = pmpConfigStorage.paramType;
899
+ // range checks for non-string params
900
+ if (paramType != ParamType.String) {
901
+ if (paramType == ParamType.Select) {
902
+ require(
903
+ uint256(pmpInput.configuredValue) <
904
+ pmpConfigStorage.selectOptionsLength,
905
+ "PMP: selectOptions index out of bounds"
906
+ );
907
+ } else if (paramType == ParamType.Bool) {
908
+ require(
909
+ pmpInput.configuredValue == bytes32(0) ||
910
+ uint256(pmpInput.configuredValue) == 1,
911
+ "PMP: bool param value must be 0 or 1"
912
+ );
913
+ } else if (
914
+ paramType == ParamType.Uint256Range ||
915
+ paramType == ParamType.DecimalRange
916
+ ) {
917
+ require(
918
+ pmpInput.configuredValue >= pmpConfigStorage.minRange &&
919
+ pmpInput.configuredValue <= pmpConfigStorage.maxRange,
920
+ "PMP: param value out of bounds"
921
+ );
922
+ } else if (paramType == ParamType.Int256Range) {
923
+ require(
924
+ int256(uint256(pmpInput.configuredValue)) >= // @dev ensure this converts as expected
925
+ int256(uint256(pmpConfigStorage.minRange)) &&
926
+ int256(uint256(pmpInput.configuredValue)) <=
927
+ int256(uint256(pmpConfigStorage.maxRange)),
928
+ "PMP: param value out of bounds"
929
+ );
930
+ } else if (paramType == ParamType.Timestamp) {
931
+ require(
932
+ uint256(pmpInput.configuredValue) <= _TIMESTAMP_MAX &&
933
+ uint256(pmpInput.configuredValue) >= _TIMESTAMP_MIN, // @dev no coverage, this is redundant due to being assigned zero
934
+ "PMP: param value out of bounds"
935
+ );
936
+ } else if (paramType == ParamType.HexColor) {
937
+ require(
938
+ uint256(pmpInput.configuredValue) <= _HEX_COLOR_MAX, // @dev minimum hex color of zero implicitly passed by using uint256
939
+ "PMP: invalid hex color"
940
+ );
941
+ } else {
942
+ // @dev no coverage, this should never be reached
943
+ revert("PMP: invalid paramType");
944
+ }
945
+ }
946
+
947
+ // string and non-string checks
948
+ if (pmpConfigStorage.paramType == ParamType.String) {
949
+ require(
950
+ pmpInput.configuredValue == bytes32(0),
951
+ "PMP: value must be empty for string params"
952
+ );
953
+ // require artist is caller if configuring artist string
954
+ if (pmpInput.configuringArtistString) {
955
+ // require artist is caller
956
+ (bool isArtist, ) = _isArtist({
957
+ tokenId: tokenId,
958
+ coreContract: coreContract,
959
+ sender: msg.sender
960
+ });
961
+ require(
962
+ isArtist,
963
+ "PMP: artist auth required to configure artist string"
964
+ );
965
+ }
966
+ } else {
967
+ // non-string - ensure configured string is empty
968
+ require(
969
+ bytes(pmpInput.configuredValueString).length == 0,
970
+ "PMP: non-string param must have empty string value"
971
+ );
972
+ // non-string - ensure configuring artist string is false, because it is not relevant for non-string params
973
+ require(
974
+ !pmpInput.configuringArtistString,
975
+ "PMP: artist string cannot be configured for non-string params"
976
+ );
977
+ }
978
+ }
979
+
980
+ /**
981
+ * @notice Gets the value of a PMP as a formatted string.
982
+ * @dev Used internally to retrieve a PMP value in string format for external consumption.
983
+ * @param pmpConfigStorage The config storage for the PMP.
984
+ * @param pmp The PMP storage instance.
985
+ * @return isConfigured Whether the PMP has been configured for the token.
986
+ * @return value The string representation of the PMP value, or empty if not configured.
987
+ */
988
+ function _getPMPValue(
989
+ PMPConfigStorage storage pmpConfigStorage,
990
+ PMPStorage storage pmp
991
+ ) internal view returns (bool isConfigured, string memory value) {
992
+ ParamType configuredParamType = pmp.configuredParamType;
993
+ // unconfigured param for token
994
+ if (
995
+ configuredParamType == ParamType.Unconfigured || // unconfigured for token
996
+ configuredParamType != pmpConfigStorage.paramType // stale - token configured param type is different from project config
997
+ ) {
998
+ return (false, "");
999
+ }
1000
+ // if string, return value
1001
+ if (pmpConfigStorage.paramType == ParamType.String) {
1002
+ // return artist configured value if present
1003
+ if (bytes(pmp.artistConfiguredValueString).length > 0) {
1004
+ return (true, pmp.artistConfiguredValueString);
1005
+ }
1006
+ // return non-artist configured value if present
1007
+ if (bytes(pmp.nonArtistConfiguredValueString).length > 0) {
1008
+ return (true, pmp.nonArtistConfiguredValueString);
1009
+ }
1010
+ // empty string is considered not configured
1011
+ return (false, "");
1012
+ }
1013
+ if (configuredParamType == ParamType.Select) {
1014
+ // return unconfigured if index is out of bounds (obviously stale)
1015
+ if (
1016
+ uint256(pmp.configuredValue) >=
1017
+ pmpConfigStorage.selectOptionsLength
1018
+ ) {
1019
+ return (false, "");
1020
+ }
1021
+ return (
1022
+ true,
1023
+ pmpConfigStorage.selectOptions.get(uint256(pmp.configuredValue))
1024
+ );
1025
+ }
1026
+ if (configuredParamType == ParamType.Bool) {
1027
+ return (true, pmp.configuredValue == bytes32(0) ? "false" : "true");
1028
+ }
1029
+ if (
1030
+ configuredParamType == ParamType.Uint256Range ||
1031
+ configuredParamType == ParamType.HexColor ||
1032
+ configuredParamType == ParamType.Timestamp ||
1033
+ configuredParamType == ParamType.DecimalRange
1034
+ ) {
1035
+ // verify configured value is within bounds (obviously stale if not)
1036
+ uint256 configuredValue = uint256(pmp.configuredValue);
1037
+ uint256 maxRange = configuredParamType == ParamType.HexColor
1038
+ ? _HEX_COLOR_MAX
1039
+ : uint256(pmpConfigStorage.maxRange);
1040
+ uint256 minRange = configuredParamType == ParamType.HexColor
1041
+ ? 0
1042
+ : uint256(pmpConfigStorage.minRange);
1043
+ if (
1044
+ configuredValue < uint256(minRange) ||
1045
+ configuredValue > uint256(maxRange)
1046
+ ) {
1047
+ return (false, "");
1048
+ }
1049
+ // handle decimal case
1050
+ if (configuredParamType == ParamType.DecimalRange) {
1051
+ return (true, _uintToDecimalString(configuredValue));
1052
+ }
1053
+ // handle hex color case
1054
+ if (configuredParamType == ParamType.HexColor) {
1055
+ return (true, _uintToHexColorString(configuredValue));
1056
+ }
1057
+ // handle other cases - uint256 and timestamp
1058
+ return (true, configuredValue.toString());
1059
+ }
1060
+ if (configuredParamType == ParamType.Int256Range) {
1061
+ // verify configured value is within bounds (obviously stale if not)
1062
+ int256 configuredValue = int256(uint256(pmp.configuredValue));
1063
+ if (
1064
+ configuredValue < int256(uint256(pmpConfigStorage.minRange)) ||
1065
+ configuredValue > int256(uint256(pmpConfigStorage.maxRange))
1066
+ ) {
1067
+ return (false, "");
1068
+ }
1069
+ return (true, configuredValue.toStringSigned());
1070
+ }
1071
+ // @dev should never reach
1072
+ // @dev no coverage, unreachable
1073
+ revert("PMP: Unhandled ParamType");
1074
+ }
1075
+
1076
+ /**
1077
+ * @notice Enforces that the caller is the artist of the specified project.
1078
+ * @dev Reverts if the caller is not the artist.
1079
+ * @dev Assumes Art Blocks V3 Core Contract interface.
1080
+ * @param coreContract The address of the core contract.
1081
+ * @param projectId The project ID to check artist permissions for.
1082
+ * @param sender The address to check if it's the artist.
1083
+ */
1084
+ function _onlyArtist(
1085
+ address coreContract,
1086
+ uint256 projectId,
1087
+ address sender
1088
+ ) internal view {
1089
+ require(
1090
+ IGenArt721CoreContractV3_Base(coreContract)
1091
+ .projectIdToArtistAddress(projectId) == sender,
1092
+ "PMP: only artist"
1093
+ );
1094
+ }
1095
+
1096
+ /**
1097
+ * @notice Checks if an address is the artist of the project associated with a token.
1098
+ * @dev Assumes Art Blocks V3 Core Contract interface.
1099
+ * @param tokenId The token ID to get the project ID from.
1100
+ * @param coreContract The address of the core contract.
1101
+ * @param sender The address to check if it's the artist.
1102
+ * @return isArtist true if the sender is the artist, false otherwise.
1103
+ * @return artistAddress the address of the artist.
1104
+ */
1105
+ function _isArtist(
1106
+ uint256 tokenId,
1107
+ address coreContract,
1108
+ address sender
1109
+ ) internal view returns (bool isArtist, address artistAddress) {
1110
+ uint256 projectId = ABHelpers.tokenIdToProjectId(tokenId);
1111
+ artistAddress = IGenArt721CoreContractV3_Base(coreContract)
1112
+ .projectIdToArtistAddress(projectId);
1113
+ isArtist = artistAddress == sender;
1114
+ }
1115
+
1116
+ /**
1117
+ * @notice Checks if an address is the owner of a token.
1118
+ * Supports ERC-721 Token-level wallet delegation for the TokenOwner role via delegate.xyz v2,
1119
+ * using the delegate.xyz v2 "postmintparameters" subdelegation rights.
1120
+ * @dev Assumes Art Blocks V3 Core Contract interface.
1121
+ * @param tokenId The token ID to check ownership for.
1122
+ * @param coreContract The address of the core contract.
1123
+ * @param sender The address to check if it's the token owner.
1124
+ * @return isTokenOwnerOrDelegate_ true if the sender is the token owner or delegate of the token owner, false otherwise.
1125
+ * @return tokenOwner the address of the token owner.
1126
+ * @dev Always execute within a nonReentrant context.
1127
+ */
1128
+ function _isTokenOwnerOrDelegate(
1129
+ uint256 tokenId,
1130
+ address coreContract,
1131
+ address sender
1132
+ ) internal view returns (bool isTokenOwnerOrDelegate_, address tokenOwner) {
1133
+ // @dev leading interaction - only execute within a nonReentrant context
1134
+ tokenOwner = IERC721(coreContract).ownerOf(tokenId);
1135
+ isTokenOwnerOrDelegate_ =
1136
+ (tokenOwner == sender) ||
1137
+ delegateRegistry.checkDelegateForERC721({
1138
+ to: sender, // hot wallet
1139
+ from: tokenOwner, // vault
1140
+ contract_: coreContract, // ERC-721 contract
1141
+ tokenId: tokenId, // tokenId
1142
+ rights: bytes32(DELEGATION_REGISTRY_TOKEN_OWNER_RIGHTS) // opt-in granular control of rights
1143
+ });
1144
+ }
1145
+
1146
+ /**
1147
+ * @notice Computes the keccak256 hash of a string.
1148
+ * @dev Used to create mapping keys from string values.
1149
+ * @param str The string to hash.
1150
+ * @return The bytes32 hash of the input string.
1151
+ */
1152
+ function _getStringHash(string memory str) internal pure returns (bytes32) {
1153
+ return keccak256(abi.encode(str));
1154
+ }
1155
+
1156
+ /**
1157
+ * @notice Converts a uint256 to a decimal string representation.
1158
+ * @dev Converts integer and fractional parts separately and combines them.
1159
+ * @param number The uint256 value to convert to decimal string.
1160
+ * @return A string representation of the decimal number.
1161
+ */
1162
+ function _uintToDecimalString(
1163
+ uint256 number
1164
+ ) private pure returns (string memory) {
1165
+ uint256 integerPart = number / _DECIMAL_PRECISION; // Integer part
1166
+ uint256 fractionalPart = number % _DECIMAL_PRECISION; // Fractional part
1167
+
1168
+ // Convert integer and fractional parts to strings
1169
+ string memory intStr = integerPart.toString();
1170
+ string memory fracStr = fractionalPart.toString();
1171
+
1172
+ // Pad fractional part with zeros if necessary
1173
+ while (bytes(fracStr).length < _DECIMAL_PRECISION_DIGITS) {
1174
+ fracStr = string(abi.encodePacked("0", fracStr));
1175
+ }
1176
+
1177
+ // Combine integer and fractional parts with a decimal point
1178
+ return string(abi.encodePacked(intStr, ".", fracStr));
1179
+ }
1180
+
1181
+ /**
1182
+ * @notice Converts a uint256 to a hex color string.
1183
+ * @dev forked from OpenZeppelin's Strings library to use # prefix
1184
+ * @dev Assumes the value is a valid hex color.
1185
+ * @param value The uint256 value to convert to hex color string.
1186
+ * @return A string representation of the hex color.
1187
+ */
1188
+ function _uintToHexColorString(
1189
+ uint256 value
1190
+ ) private pure returns (string memory) {
1191
+ uint256 localValue = value;
1192
+ bytes memory buffer = new bytes(2 * 3 + 1);
1193
+ buffer[0] = "#";
1194
+ for (uint256 i = 2 * 3; i > 0; --i) {
1195
+ buffer[i] = _HEX_DIGITS[localValue & 0xf];
1196
+ localValue >>= 4;
1197
+ }
1198
+ if (localValue != 0) {
1199
+ // @dev no coverage, this is redundant due to use of max hex color value
1200
+ revert("PMP: invalid hex color");
1201
+ }
1202
+ return string(buffer);
1203
+ }
1204
+ }