@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.
- package/README.md +11 -10
- package/artifacts/contracts/AdminACLV0RoyaltyRegistry.sol/AdminACLV0RoyaltyRegistry.json +193 -0
- package/artifacts/contracts/AdminACLV0RoyaltyRegistry.sol/IRoyaltyRegistry.json +35 -0
- package/artifacts/contracts/AdminACLV1.sol/AdminACLV1.json +2 -2
- package/artifacts/contracts/BytecodeStorageReaders/BytecodeStorageReaderContractV2.sol/BytecodeStorageReaderContractV2.json +2 -2
- package/artifacts/contracts/BytecodeStorageReaders/BytecodeStorageReaderContractV2_Web3Call.sol/BytecodeStorageReaderContractV2_Web3Call.json +2 -2
- package/artifacts/contracts/BytecodeStorageV2Writer.sol/BytecodeStorageV2Writer.json +2 -2
- package/artifacts/contracts/DependencyRegistryV0.sol/DependencyRegistryV0.json +210 -2
- package/artifacts/contracts/GenArt721CoreV3_Curated.sol/GenArt721CoreV3_Curated.json +235 -20
- package/artifacts/contracts/GenArt721CoreV3_Curated_Flex.sol/GenArt721CoreV3_Curated_Flex.json +3197 -0
- package/artifacts/contracts/GenArt721CoreV3_Explorations_Flex.sol/GenArt721CoreV3_Explorations_Flex.json +3192 -0
- package/artifacts/contracts/OwnedCreate2FactoryV0.sol/OwnedCreate2FactoryV0.json +2 -2
- package/artifacts/contracts/archive/GenArt721CoreV3.sol/GenArt721CoreV3.json +27 -2
- package/artifacts/contracts/archive/GenArt721CoreV3_Explorations.sol/GenArt721CoreV3_Explorations.json +27 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV2.sol/MinterDAExpV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV3.sol/MinterDAExpV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV4.sol/MinterDAExpV4.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol/MinterDAExpSettlementV0.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV1.sol/MinterDAExpSettlementV1.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV2.sol/MinterDAExpSettlementV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV2.sol/MinterDALinV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV3.sol/MinterDALinV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV4.sol/MinterDALinV4.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol/MinterHolderV1.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV3.sol/MinterHolderV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV4.sol/MinterHolderV4.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol/MinterMerkleV1.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol/MinterMerkleV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV3.sol/MinterMerkleV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV4.sol/MinterMerkleV4.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV5.sol/MinterMerkleV5.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterPolyptych/MinterPolyptychV0.sol/IGenArt721CoreContractV3WithRandomizer.json +25 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterPolyptych/MinterPolyptychV0.sol/MinterPolyptychV0.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSEA/MinterSEAV0.sol/MinterSEAV0.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV2.sol/MinterSetPriceV2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV3.sol/MinterSetPriceV3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV4.sol/MinterSetPriceV4.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol/MinterSetPriceERC20V2.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol/MinterSetPriceERC20V3.json +2 -2
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V4.sol/MinterSetPriceERC20V4.json +2 -2
- package/artifacts/contracts/engine/V3/EngineFactoryV0.sol/EngineFactoryV0.json +28 -2
- package/artifacts/contracts/engine/V3/GenArt721CoreV3_Engine.sol/GenArt721CoreV3_Engine.json +239 -20
- package/artifacts/contracts/engine/V3/GenArt721CoreV3_Engine_Flex.sol/GenArt721CoreV3_Engine_Flex.json +267 -48
- package/artifacts/contracts/engine/V3/forks/GenArt721CoreV3_Engine_Flex_PROOF.sol/GenArt721CoreV3_Engine_Flex_PROOF.json +27 -2
- package/artifacts/contracts/engine/V3/forks/PROHIBITION/AdminACLV0_PROHIBITION.sol/AdminACLV0_PROHIBITION.json +2 -2
- package/artifacts/contracts/engine/V3/forks/PROHIBITION/GenArt721CoreV3_Engine_Flex_PROHIBITION.sol/GenArt721CoreV3_Engine_Flex_PROHIBITION.json +27 -2
- package/artifacts/contracts/engine/V3/forks/PROHIBITION/interfaces/IGenArt721CoreContractV3_Engine_Flex_PROHIBITION.sol/IGenArt721CoreContractV3_Engine_Flex_PROHIBITION.json +25 -0
- package/artifacts/contracts/engine/V3/transfer-hooks/AbstractTransferHook.sol/AbstractTransferHook.json +79 -0
- package/artifacts/contracts/engine/V3/transfer-hooks/OwnerHistoryTransferHook.sol/OwnerHistoryTransferHook.json +315 -0
- package/artifacts/contracts/generator/GenArt721GeneratorV0.sol/GenArt721GeneratorV0.json +2 -2
- package/artifacts/contracts/interfaces/v0.8.x/IClaimMinter.sol/IClaimMinter.json +189 -0
- package/artifacts/contracts/interfaces/v0.8.x/IDependencyRegistryV0.sol/IDependencyRegistryV0.json +149 -0
- package/artifacts/contracts/interfaces/v0.8.x/IEngineFactoryV0.sol/IEngineFactoryV0.json +26 -0
- package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3.sol/IGenArt721CoreContractV3.json +25 -0
- package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol/IGenArt721CoreContractV3_Base.json +25 -0
- package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol/IGenArt721CoreContractV3_Engine.json +123 -0
- package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol/IGenArt721CoreContractV3_Engine_Flex.json +123 -0
- package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_Flex_PreV3p2.sol/IGenArt721CoreContractV3_Engine_Flex_PreV3p2.json +25 -0
- package/artifacts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_PreV3p2.sol/IGenArt721CoreContractV3_Engine_PreV3p2.json +25 -0
- package/artifacts/contracts/interfaces/v0.8.x/IOwnerHistoryTransferHook.sol/IOwnerHistoryTransferHook.json +299 -0
- package/artifacts/contracts/interfaces/v0.8.x/ISRHooks.sol/ISRHooks.json +432 -0
- package/artifacts/contracts/interfaces/v0.8.x/ISeaDropShimForContract.sol/ISeaDropShimForContract.json +0 -13
- package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterTieredAllowV0.sol/ISharedMinterTieredAllowV0.json +94 -0
- package/artifacts/contracts/interfaces/v0.8.x/ITransferHook.sol/ITransferHook.json +63 -0
- package/artifacts/contracts/libs/v0.8.x/AuthLib.sol/AuthLib.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/BytecodeStorageV2.sol/BytecodeStorageReader.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/BytecodeStorageV2.sol/BytecodeStorageWriter.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/DependencyRegistryStorageLib.sol/DependencyRegistryStorageLib.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/ENSLib.sol/ENSLib.json +10 -0
- package/artifacts/contracts/libs/v0.8.x/ENSLib.sol/IUniversalResolver.json +45 -0
- package/artifacts/contracts/libs/v0.8.x/EnumerableSetUint16.sol/EnumerableSetUint16.json +10 -0
- package/artifacts/contracts/libs/v0.8.x/FeistelWalkLib.sol/FeistelWalkLib.json +10 -0
- package/artifacts/contracts/libs/v0.8.x/ImmutableUint16Array.sol/ImmutableUint16Array.json +10 -0
- package/artifacts/contracts/libs/v0.8.x/V3EngineLib.sol/V3EngineLib.json +163 -0
- package/artifacts/contracts/libs/v0.8.x/V3FlexLib.sol/V3FlexLib.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/V3TransferHookLib.sol/V3TransferHookLib.json +79 -0
- package/artifacts/contracts/libs/v0.8.x/minter-libs/MaxInvocationsLib.sol/MaxInvocationsLib.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/minter-libs/OnChainAllowlistLib.sol/OnChainAllowlistLib.json +10 -0
- package/artifacts/contracts/libs/v0.8.x/minter-libs/PolyptychLib.sol/PolyptychLib.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/minter-libs/RAMLib.sol/RAMLib.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/minter-libs/SEALib.sol/SEALib.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/minter-libs/SettlementExpLib.sol/SettlementExpLib.json +2 -2
- package/artifacts/contracts/libs/v0.8.x/minter-libs/SplitFundsLib.sol/SplitFundsLib.json +2 -2
- package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV1.sol/MinterFilterV1.json +2 -2
- package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV2.sol/MinterFilterV2.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterDAExpHolderV5.sol/MinterDAExpHolderV5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV3.sol/MinterDAExpSettlementV3.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterDAExpV5.sol/MinterDAExpV5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterDALinHolderV5.sol/MinterDALinHolderV5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterDALinV5.sol/MinterDALinV5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterMinPriceMerkleV0.sol/MinterMinPriceMerkleV0.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterMinPriceV0.sol/MinterMinPriceV0.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterRAMV0.sol/MinterRAMV0.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterSEAV1.sol/MinterSEAV1.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceERC20V5.sol/MinterSetPriceERC20V5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceHolderV5.sol/MinterSetPriceHolderV5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceMerkleV5.sol/MinterSetPriceMerkleV5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceOnChainAllowV0.sol/MinterSetPriceOnChainAllowV0.json +522 -0
- package/artifacts/contracts/minter-suite/Minters/MinterSetPricePolyptychERC20V5.sol/MinterSetPricePolyptychERC20V5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterSetPricePolyptychV5.sol/MinterSetPricePolyptychV5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceTieredAllowV1.sol/MinterSetPriceTieredAllowV1.json +715 -0
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceV5.sol/MinterSetPriceV5.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/MinterSlidingScaleV0.sol/MinterSlidingScaleV0.json +431 -0
- package/artifacts/contracts/minter-suite/Minters/one-off/ClaimMinter.sol/ClaimMinter.json +502 -0
- package/artifacts/contracts/minter-suite/Minters/one-off/MinterSetPriceTieredOnChainAllowV0.sol/MinterSetPriceTieredOnChainAllowV0.json +673 -0
- package/artifacts/contracts/minter-suite/Minters/one-off/MinterSetPriceV5_SingleCore.sol/MinterSetPriceV5_SingleCore.json +2 -2
- package/artifacts/contracts/minter-suite/Minters/one-off/SeaDropXArtBlocksShim.sol/SeaDropXArtBlocksShim.json +6 -19
- package/artifacts/contracts/mock/BytecodeV2TextCR_DMock.sol/BytecodeV2TextCR_DMock.json +2 -2
- package/artifacts/contracts/mock/DummySharedMinter.sol/DummySharedMinter.json +2 -2
- package/artifacts/contracts/mock/ENSLibMock.sol/ENSLibMock.json +30 -0
- package/artifacts/contracts/mock/EnumerableSetUint16Mock.sol/EnumerableSetUint16Mock.json +146 -0
- package/artifacts/contracts/mock/FeistelWalkLibMock.sol/FeistelWalkLibMock.json +142 -0
- package/artifacts/contracts/mock/ForceSendMock.sol/ForceSendMock.json +2 -2
- package/artifacts/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol/GenArt721CoreV3_Engine_IncorrectCoreType.json +2 -2
- package/artifacts/contracts/mock/ImmutableUint16ArrayMock.sol/ImmutableUint16ArrayMock.json +89 -0
- package/artifacts/contracts/mock/MockAdminACLV0_PROHIBITION.sol/MockAdminACLV0Events_PROHIBITION.json +2 -2
- package/artifacts/contracts/mock/MockGenArt721V3Core.sol/MockGenArt721V3Core.json +490 -0
- package/artifacts/contracts/mock/MockPMP.sol/MockPMP.json +523 -0
- package/artifacts/contracts/mock/MockTransferHook.sol/MockTransferHook.json +453 -0
- package/artifacts/contracts/mock/MockTransferHookTestHelpers.sol/MockBatchTransferrer.json +39 -0
- package/artifacts/contracts/mock/MockTransferHookTestHelpers.sol/MockForwardingERC721Receiver.json +71 -0
- package/artifacts/contracts/mock/MockTransferHookTestHelpers.sol/MockSpoofingCore.json +94 -0
- package/artifacts/contracts/mock/RandomizerV2_NoAssignMock.sol/RandomizerV2_NoAssignMock.json +2 -2
- package/artifacts/contracts/mock/ReentrancyBatchMockShared.sol/IBatchMinter.json +64 -0
- package/artifacts/contracts/mock/ReentrancyBatchMockShared.sol/ReentrancyBatchMockShared.json +113 -0
- package/artifacts/contracts/mock/RelayExecutorTestHelper.sol/MockTarget.json +74 -0
- package/artifacts/contracts/mock/RelayExecutorTestHelper.sol/RelayExecutorPartialRevertsTestHelper.json +138 -0
- package/artifacts/contracts/mock/RelayExecutorTestHelper.sol/RelayExecutorTestHelper.json +119 -0
- package/artifacts/contracts/randomizer/deprecated/PolyptychRandomizerV0.sol/BasicPolyptychRandomizerV0.json +2 -2
- package/artifacts/contracts/randomizer/deprecated/RandomizerV2.sol/BasicRandomizerV2.json +2 -2
- package/artifacts/contracts/randomizer/shared/SharedRandomizerV0.sol/SharedRandomizerV0.json +2 -2
- package/artifacts/contracts/relay/RelayExecutor.sol/RelayExecutor.json +101 -0
- package/artifacts/contracts/relay/RelayExecutorPartialReverts.sol/RelayExecutorPartialReverts.json +120 -0
- package/artifacts/contracts/royalty-registry/GenArt721RoyaltyOverrideSplits.sol/GenArt721RoyaltyOverrideSplits.json +263 -0
- package/artifacts/contracts/utils/MintMulticallUtil.sol/IMinterSetPriceV5.json +40 -0
- package/artifacts/contracts/utils/MintMulticallUtil.sol/MintMulticallUtil.json +163 -0
- package/artifacts/contracts/web3call/PMPV0.sol/PMPV0.json +2 -2
- package/artifacts/contracts/web3call/PMPV1.sol/PMPV1.json +648 -0
- package/artifacts/contracts/web3call/augment-hooks/InjectBaseGasFee.sol/InjectBaseGasFee.json +2 -2
- package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeight.sol/InjectBlockHeight.json +83 -0
- package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeightAndArtistProjectOverrides.sol/InjectBlockHeightAndArtistProjectOverrides.json +226 -0
- package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeightAndNumOwnedProjectTokens.sol/InjectBlockHeightAndNumOwnedProjectTokens.json +182 -0
- package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeightProjectInvocationsAndArtistProjectOverrides.sol/InjectBlockHeightProjectInvocationsAndArtistProjectOverrides.json +226 -0
- package/artifacts/contracts/web3call/augment-hooks/InjectBlockTimestamp.sol/InjectBlockTimestamp.json +83 -0
- package/artifacts/contracts/web3call/augment-hooks/InjectBytesPerToken.sol/InjectBytesPerToken.json +358 -0
- package/artifacts/contracts/web3call/augment-hooks/InjectChromieSquiggleFloorPrice.sol/InjectChromieSquiggleFloorPrice.json +2 -2
- package/artifacts/contracts/web3call/augment-hooks/InjectIsNounsDaoMember.sol/InjectIsNounsDaoMember.json +2 -2
- package/artifacts/contracts/web3call/augment-hooks/InjectIsProjectFullyMinted.sol/InjectIsProjectFullyMinted.json +2 -2
- package/artifacts/contracts/web3call/augment-hooks/InjectPolyptychTokenHash.sol/InjectPolyptychTokenHash.json +2 -2
- package/artifacts/contracts/web3call/augment-hooks/InjectTokenHashSeed.sol/InjectTokenHashSeed.json +2 -2
- package/artifacts/contracts/web3call/augment-hooks/InjectTokenOwner.sol/InjectTokenOwner.json +2 -2
- package/artifacts/contracts/web3call/augment-hooks/InjectTokenOwnerEthBalance.sol/InjectTokenOwnerEthBalance.json +2 -2
- package/artifacts/contracts/web3call/augment-hooks/InjectUSDCTotalSupply.sol/InjectUSDCTotalSupply.json +2 -2
- package/artifacts/contracts/web3call/augment-hooks/SRHooks.sol/SRHooks.json +899 -0
- package/artifacts/contracts/web3call/combined-hooks/LiftHooks.sol/IGenArt721V0_Minimal.json +30 -0
- package/artifacts/contracts/web3call/combined-hooks/LiftHooks.sol/IRelic.json +35 -0
- package/artifacts/contracts/web3call/combined-hooks/LiftHooks.sol/LiftHooks.json +256 -0
- package/artifacts/contracts/web3call/configure-hooks/{abstractPMPConfigureHook.sol → AbstractPMPConfigureHook.sol}/AbstractPMPConfigureHook.json +1 -1
- package/artifacts/contracts/web3call/configure-hooks/PMPConfigureLockHook.sol/PMPConfigureLockHook.json +195 -0
- package/contracts/AdminACLV0RoyaltyRegistry.sol +142 -0
- package/contracts/DependencyRegistryV0.sol +143 -13
- package/contracts/GenArt721CoreV3_Curated.sol +1 -1
- package/contracts/GenArt721CoreV3_Curated_Flex.sol +173 -0
- package/contracts/GenArt721CoreV3_Explorations_Flex.sol +159 -0
- package/contracts/engine/V3/EngineFactoryV0.sol +20 -1
- package/contracts/engine/V3/GenArt721CoreV3_Engine.sol +286 -299
- package/contracts/engine/V3/GenArt721CoreV3_Engine_Flex.sol +292 -301
- package/contracts/engine/V3/transfer-hooks/AbstractTransferHook.sol +101 -0
- package/contracts/engine/V3/transfer-hooks/OwnerHistoryTransferHook.sol +291 -0
- package/contracts/generator/GenArt721GeneratorV0.sol +145 -47
- package/contracts/helpers/ERC1967ProxyImport.sol +5 -0
- package/contracts/interfaces/v0.8.x/IArtblocksRoyaltyOverride.sol +1 -1
- package/contracts/interfaces/v0.8.x/IClaimMinter.sol +70 -0
- package/contracts/interfaces/v0.8.x/IDependencyRegistryV0.sol +95 -0
- package/contracts/interfaces/v0.8.x/IEngineFactoryV0.sol +15 -0
- package/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol +21 -2
- package/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol +72 -0
- package/contracts/interfaces/v0.8.x/IOwnerHistoryTransferHook.sol +119 -0
- package/contracts/interfaces/v0.8.x/ISRHooks.sol +229 -0
- package/contracts/interfaces/v0.8.x/ISeaDropShimForContract.sol +0 -6
- package/contracts/interfaces/v0.8.x/ISharedMinterTieredAllowV0.sol +69 -0
- package/contracts/interfaces/v0.8.x/ITransferHook.sol +48 -0
- package/contracts/libs/v0.8.x/DependencyRegistryStorageLib.sol +12 -0
- package/contracts/libs/v0.8.x/ENSLib.sol +85 -0
- package/contracts/libs/v0.8.x/EnumerableSetUint16.sol +429 -0
- package/contracts/libs/v0.8.x/FeistelWalkLib.sol +221 -0
- package/contracts/libs/v0.8.x/ImmutableUint16Array.sol +201 -0
- package/contracts/libs/v0.8.x/V3EngineLib.sol +490 -0
- package/contracts/libs/v0.8.x/V3TransferHookLib.sol +251 -0
- package/contracts/libs/v0.8.x/minter-libs/OnChainAllowlistLib.sol +165 -0
- package/contracts/minter-suite/Minters/MinterSetPriceOnChainAllowV0.sol +632 -0
- package/contracts/minter-suite/Minters/MinterSetPriceTieredAllowV1.sol +871 -0
- package/contracts/minter-suite/Minters/MinterSlidingScaleV0.sol +493 -0
- package/contracts/minter-suite/Minters/one-off/ClaimMinter.sol +595 -0
- package/contracts/minter-suite/Minters/one-off/MinterSetPriceTieredOnChainAllowV0.sol +894 -0
- package/contracts/minter-suite/Minters/one-off/SeaDropXArtBlocksShim.sol +59 -41
- package/contracts/mock/ENSLibMock.sol +25 -0
- package/contracts/mock/EnumerableSetUint16Mock.sol +88 -0
- package/contracts/mock/FeistelWalkLibMock.sol +81 -0
- package/contracts/mock/ImmutableUint16ArrayMock.sol +62 -0
- package/contracts/mock/MockGenArt721V3Core.sol +416 -0
- package/contracts/mock/MockPMP.sol +287 -0
- package/contracts/mock/MockTransferHook.sol +160 -0
- package/contracts/mock/MockTransferHookTestHelpers.sol +96 -0
- package/contracts/mock/ReentrancyBatchMockShared.sol +53 -0
- package/contracts/mock/RelayExecutorTestHelper.sol +44 -0
- package/contracts/relay/RelayExecutor.sol +61 -0
- package/contracts/relay/RelayExecutorPartialReverts.sol +79 -0
- package/contracts/royalty-registry/GenArt721RoyaltyOverrideSplits.sol +130 -0
- package/contracts/utils/MintMulticallUtil.sol +103 -0
- package/contracts/web3call/PMPV1.sol +1204 -0
- package/contracts/web3call/augment-hooks/AbstractPMPAugmentHook.sol +1 -1
- package/contracts/web3call/augment-hooks/InjectBaseGasFee.sol +1 -1
- package/contracts/web3call/augment-hooks/InjectBlockHeight.sol +57 -0
- package/contracts/web3call/augment-hooks/InjectBlockHeightAndArtistProjectOverrides.sol +310 -0
- package/contracts/web3call/augment-hooks/InjectBlockHeightAndNumOwnedProjectTokens.sol +141 -0
- package/contracts/web3call/augment-hooks/InjectBlockHeightProjectInvocationsAndArtistProjectOverrides.sol +326 -0
- package/contracts/web3call/augment-hooks/InjectBlockTimestamp.sol +57 -0
- package/contracts/web3call/augment-hooks/InjectBytesPerToken.sol +279 -0
- package/contracts/web3call/augment-hooks/InjectIsProjectFullyMinted.sol +1 -1
- package/contracts/web3call/augment-hooks/InjectTokenHashSeed.sol +1 -1
- package/contracts/web3call/augment-hooks/InjectTokenOwner.sol +1 -1
- package/contracts/web3call/augment-hooks/InjectTokenOwnerEthBalance.sol +1 -1
- package/contracts/web3call/augment-hooks/SRHooks.sol +1722 -0
- package/contracts/web3call/combined-hooks/LiftHooks.sol +304 -0
- package/contracts/web3call/configure-hooks/{abstractPMPConfigureHook.sol → AbstractPMPConfigureHook.sol} +1 -1
- package/contracts/web3call/configure-hooks/PMPConfigureLockHook.sol +107 -0
- 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
|
+
}
|