@artblocks/contracts 1.3.1 → 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 (234) 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/IPMPV0.sol/IPMPV0.json +7 -1
  63. package/artifacts/contracts/interfaces/v0.8.x/ISRHooks.sol/ISRHooks.json +432 -0
  64. package/artifacts/contracts/interfaces/v0.8.x/ISeaDropShimForContract.sol/ISeaDropShimForContract.json +0 -13
  65. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterTieredAllowV0.sol/ISharedMinterTieredAllowV0.json +94 -0
  66. package/artifacts/contracts/interfaces/v0.8.x/ITransferHook.sol/ITransferHook.json +63 -0
  67. package/artifacts/contracts/libs/v0.8.x/AuthLib.sol/AuthLib.json +2 -2
  68. package/artifacts/contracts/libs/v0.8.x/BytecodeStorageV2.sol/BytecodeStorageReader.json +2 -2
  69. package/artifacts/contracts/libs/v0.8.x/BytecodeStorageV2.sol/BytecodeStorageWriter.json +2 -2
  70. package/artifacts/contracts/libs/v0.8.x/DependencyRegistryStorageLib.sol/DependencyRegistryStorageLib.json +2 -2
  71. package/artifacts/contracts/libs/v0.8.x/ENSLib.sol/ENSLib.json +10 -0
  72. package/artifacts/contracts/libs/v0.8.x/ENSLib.sol/IUniversalResolver.json +45 -0
  73. package/artifacts/contracts/libs/v0.8.x/EnumerableSetUint16.sol/EnumerableSetUint16.json +10 -0
  74. package/artifacts/contracts/libs/v0.8.x/FeistelWalkLib.sol/FeistelWalkLib.json +10 -0
  75. package/artifacts/contracts/libs/v0.8.x/ImmutableUint16Array.sol/ImmutableUint16Array.json +10 -0
  76. package/artifacts/contracts/libs/v0.8.x/V3EngineLib.sol/V3EngineLib.json +163 -0
  77. package/artifacts/contracts/libs/v0.8.x/V3FlexLib.sol/V3FlexLib.json +2 -2
  78. package/artifacts/contracts/libs/v0.8.x/V3TransferHookLib.sol/V3TransferHookLib.json +79 -0
  79. package/artifacts/contracts/libs/v0.8.x/minter-libs/MaxInvocationsLib.sol/MaxInvocationsLib.json +2 -2
  80. package/artifacts/contracts/libs/v0.8.x/minter-libs/OnChainAllowlistLib.sol/OnChainAllowlistLib.json +10 -0
  81. package/artifacts/contracts/libs/v0.8.x/minter-libs/PolyptychLib.sol/PolyptychLib.json +2 -2
  82. package/artifacts/contracts/libs/v0.8.x/minter-libs/RAMLib.sol/RAMLib.json +2 -2
  83. package/artifacts/contracts/libs/v0.8.x/minter-libs/SEALib.sol/SEALib.json +2 -2
  84. package/artifacts/contracts/libs/v0.8.x/minter-libs/SettlementExpLib.sol/SettlementExpLib.json +2 -2
  85. package/artifacts/contracts/libs/v0.8.x/minter-libs/SplitFundsLib.sol/SplitFundsLib.json +2 -2
  86. package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV1.sol/MinterFilterV1.json +2 -2
  87. package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV2.sol/MinterFilterV2.json +2 -2
  88. package/artifacts/contracts/minter-suite/Minters/MinterDAExpHolderV5.sol/MinterDAExpHolderV5.json +2 -2
  89. package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV3.sol/MinterDAExpSettlementV3.json +2 -2
  90. package/artifacts/contracts/minter-suite/Minters/MinterDAExpV5.sol/MinterDAExpV5.json +2 -2
  91. package/artifacts/contracts/minter-suite/Minters/MinterDALinHolderV5.sol/MinterDALinHolderV5.json +2 -2
  92. package/artifacts/contracts/minter-suite/Minters/MinterDALinV5.sol/MinterDALinV5.json +2 -2
  93. package/artifacts/contracts/minter-suite/Minters/MinterMinPriceMerkleV0.sol/MinterMinPriceMerkleV0.json +2 -2
  94. package/artifacts/contracts/minter-suite/Minters/MinterMinPriceV0.sol/MinterMinPriceV0.json +2 -2
  95. package/artifacts/contracts/minter-suite/Minters/MinterRAMV0.sol/MinterRAMV0.json +2 -2
  96. package/artifacts/contracts/minter-suite/Minters/MinterSEAV1.sol/MinterSEAV1.json +2 -2
  97. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceERC20V5.sol/MinterSetPriceERC20V5.json +2 -2
  98. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceHolderV5.sol/MinterSetPriceHolderV5.json +2 -2
  99. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceMerkleV5.sol/MinterSetPriceMerkleV5.json +2 -2
  100. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceOnChainAllowV0.sol/MinterSetPriceOnChainAllowV0.json +522 -0
  101. package/artifacts/contracts/minter-suite/Minters/MinterSetPricePolyptychERC20V5.sol/MinterSetPricePolyptychERC20V5.json +2 -2
  102. package/artifacts/contracts/minter-suite/Minters/MinterSetPricePolyptychV5.sol/MinterSetPricePolyptychV5.json +2 -2
  103. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceTieredAllowV1.sol/MinterSetPriceTieredAllowV1.json +715 -0
  104. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceV5.sol/MinterSetPriceV5.json +2 -2
  105. package/artifacts/contracts/minter-suite/Minters/MinterSlidingScaleV0.sol/MinterSlidingScaleV0.json +431 -0
  106. package/artifacts/contracts/minter-suite/Minters/one-off/ClaimMinter.sol/ClaimMinter.json +502 -0
  107. package/artifacts/contracts/minter-suite/Minters/one-off/MinterSetPriceTieredOnChainAllowV0.sol/MinterSetPriceTieredOnChainAllowV0.json +673 -0
  108. package/artifacts/contracts/minter-suite/Minters/one-off/MinterSetPriceV5_SingleCore.sol/MinterSetPriceV5_SingleCore.json +2 -2
  109. package/artifacts/contracts/minter-suite/Minters/one-off/SeaDropXArtBlocksShim.sol/SeaDropXArtBlocksShim.json +6 -19
  110. package/artifacts/contracts/mock/BytecodeV2TextCR_DMock.sol/BytecodeV2TextCR_DMock.json +2 -2
  111. package/artifacts/contracts/mock/DummySharedMinter.sol/DummySharedMinter.json +2 -2
  112. package/artifacts/contracts/mock/ENSLibMock.sol/ENSLibMock.json +30 -0
  113. package/artifacts/contracts/mock/EnumerableSetUint16Mock.sol/EnumerableSetUint16Mock.json +146 -0
  114. package/artifacts/contracts/mock/FeistelWalkLibMock.sol/FeistelWalkLibMock.json +142 -0
  115. package/artifacts/contracts/mock/ForceSendMock.sol/ForceSendMock.json +2 -2
  116. package/artifacts/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol/GenArt721CoreV3_Engine_IncorrectCoreType.json +2 -2
  117. package/artifacts/contracts/mock/ImmutableUint16ArrayMock.sol/ImmutableUint16ArrayMock.json +89 -0
  118. package/artifacts/contracts/mock/MockAdminACLV0_PROHIBITION.sol/MockAdminACLV0Events_PROHIBITION.json +2 -2
  119. package/artifacts/contracts/mock/MockGenArt721V3Core.sol/MockGenArt721V3Core.json +490 -0
  120. package/artifacts/contracts/mock/MockPMP.sol/MockPMP.json +523 -0
  121. package/artifacts/contracts/mock/MockPMPAugmentHook.sol/MockPMPAugmentHook.json +2 -2
  122. package/artifacts/contracts/mock/MockPMPConfigureHook.sol/MockPMPConfigureHook.json +2 -2
  123. package/artifacts/contracts/mock/MockTransferHook.sol/MockTransferHook.json +453 -0
  124. package/artifacts/contracts/mock/MockTransferHookTestHelpers.sol/MockBatchTransferrer.json +39 -0
  125. package/artifacts/contracts/mock/MockTransferHookTestHelpers.sol/MockForwardingERC721Receiver.json +71 -0
  126. package/artifacts/contracts/mock/MockTransferHookTestHelpers.sol/MockSpoofingCore.json +94 -0
  127. package/artifacts/contracts/mock/RandomizerV2_NoAssignMock.sol/RandomizerV2_NoAssignMock.json +2 -2
  128. package/artifacts/contracts/mock/ReentrancyBatchMockShared.sol/IBatchMinter.json +64 -0
  129. package/artifacts/contracts/mock/ReentrancyBatchMockShared.sol/ReentrancyBatchMockShared.json +113 -0
  130. package/artifacts/contracts/mock/RelayExecutorTestHelper.sol/MockTarget.json +74 -0
  131. package/artifacts/contracts/mock/RelayExecutorTestHelper.sol/RelayExecutorPartialRevertsTestHelper.json +138 -0
  132. package/artifacts/contracts/mock/RelayExecutorTestHelper.sol/RelayExecutorTestHelper.json +119 -0
  133. package/artifacts/contracts/randomizer/deprecated/PolyptychRandomizerV0.sol/BasicPolyptychRandomizerV0.json +2 -2
  134. package/artifacts/contracts/randomizer/deprecated/RandomizerV2.sol/BasicRandomizerV2.json +2 -2
  135. package/artifacts/contracts/randomizer/shared/SharedRandomizerV0.sol/SharedRandomizerV0.json +2 -2
  136. package/artifacts/contracts/relay/RelayExecutor.sol/RelayExecutor.json +101 -0
  137. package/artifacts/contracts/relay/RelayExecutorPartialReverts.sol/RelayExecutorPartialReverts.json +120 -0
  138. package/artifacts/contracts/royalty-registry/GenArt721RoyaltyOverrideSplits.sol/GenArt721RoyaltyOverrideSplits.json +263 -0
  139. package/artifacts/contracts/utils/MintMulticallUtil.sol/IMinterSetPriceV5.json +40 -0
  140. package/artifacts/contracts/utils/MintMulticallUtil.sol/MintMulticallUtil.json +163 -0
  141. package/artifacts/contracts/web3call/PMPV0.sol/PMPV0.json +9 -3
  142. package/artifacts/contracts/web3call/PMPV1.sol/PMPV1.json +648 -0
  143. package/artifacts/contracts/web3call/augment-hooks/InjectBaseGasFee.sol/InjectBaseGasFee.json +2 -2
  144. package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeight.sol/InjectBlockHeight.json +83 -0
  145. package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeightAndArtistProjectOverrides.sol/InjectBlockHeightAndArtistProjectOverrides.json +226 -0
  146. package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeightAndNumOwnedProjectTokens.sol/InjectBlockHeightAndNumOwnedProjectTokens.json +182 -0
  147. package/artifacts/contracts/web3call/augment-hooks/InjectBlockHeightProjectInvocationsAndArtistProjectOverrides.sol/InjectBlockHeightProjectInvocationsAndArtistProjectOverrides.json +226 -0
  148. package/artifacts/contracts/web3call/augment-hooks/InjectBlockTimestamp.sol/InjectBlockTimestamp.json +83 -0
  149. package/artifacts/contracts/web3call/augment-hooks/InjectBytesPerToken.sol/InjectBytesPerToken.json +358 -0
  150. package/artifacts/contracts/web3call/augment-hooks/InjectChromieSquiggleFloorPrice.sol/InjectChromieSquiggleFloorPrice.json +2 -2
  151. package/artifacts/contracts/web3call/augment-hooks/InjectIsNounsDaoMember.sol/InjectIsNounsDaoMember.json +2 -2
  152. package/artifacts/contracts/web3call/augment-hooks/InjectIsProjectFullyMinted.sol/InjectIsProjectFullyMinted.json +2 -2
  153. package/artifacts/contracts/web3call/augment-hooks/InjectPolyptychTokenHash.sol/InjectPolyptychTokenHash.json +2 -2
  154. package/artifacts/contracts/web3call/augment-hooks/InjectTokenHashSeed.sol/InjectTokenHashSeed.json +2 -2
  155. package/artifacts/contracts/web3call/augment-hooks/InjectTokenOwner.sol/InjectTokenOwner.json +2 -2
  156. package/artifacts/contracts/web3call/augment-hooks/InjectTokenOwnerEthBalance.sol/InjectTokenOwnerEthBalance.json +2 -2
  157. package/artifacts/contracts/web3call/augment-hooks/InjectUSDCTotalSupply.sol/InjectUSDCTotalSupply.json +2 -2
  158. package/artifacts/contracts/web3call/augment-hooks/SRHooks.sol/SRHooks.json +899 -0
  159. package/artifacts/contracts/web3call/combined-hooks/LiftHooks.sol/IGenArt721V0_Minimal.json +30 -0
  160. package/artifacts/contracts/web3call/combined-hooks/LiftHooks.sol/IRelic.json +35 -0
  161. package/artifacts/contracts/web3call/combined-hooks/LiftHooks.sol/LiftHooks.json +256 -0
  162. package/artifacts/contracts/web3call/configure-hooks/{abstractPMPConfigureHook.sol → AbstractPMPConfigureHook.sol}/AbstractPMPConfigureHook.json +1 -1
  163. package/artifacts/contracts/web3call/configure-hooks/PMPConfigureLockHook.sol/PMPConfigureLockHook.json +195 -0
  164. package/contracts/AdminACLV0RoyaltyRegistry.sol +142 -0
  165. package/contracts/DependencyRegistryV0.sol +143 -13
  166. package/contracts/GenArt721CoreV3_Curated.sol +1 -1
  167. package/contracts/GenArt721CoreV3_Curated_Flex.sol +173 -0
  168. package/contracts/GenArt721CoreV3_Explorations_Flex.sol +159 -0
  169. package/contracts/engine/V3/EngineFactoryV0.sol +20 -1
  170. package/contracts/engine/V3/GenArt721CoreV3_Engine.sol +286 -299
  171. package/contracts/engine/V3/GenArt721CoreV3_Engine_Flex.sol +292 -301
  172. package/contracts/engine/V3/transfer-hooks/AbstractTransferHook.sol +101 -0
  173. package/contracts/engine/V3/transfer-hooks/OwnerHistoryTransferHook.sol +291 -0
  174. package/contracts/generator/GenArt721GeneratorV0.sol +145 -47
  175. package/contracts/helpers/ERC1967ProxyImport.sol +5 -0
  176. package/contracts/interfaces/v0.8.x/IArtblocksRoyaltyOverride.sol +1 -1
  177. package/contracts/interfaces/v0.8.x/IClaimMinter.sol +70 -0
  178. package/contracts/interfaces/v0.8.x/IDependencyRegistryV0.sol +95 -0
  179. package/contracts/interfaces/v0.8.x/IEngineFactoryV0.sol +15 -0
  180. package/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol +21 -2
  181. package/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol +72 -0
  182. package/contracts/interfaces/v0.8.x/IOwnerHistoryTransferHook.sol +119 -0
  183. package/contracts/interfaces/v0.8.x/IPMPV0.sol +5 -3
  184. package/contracts/interfaces/v0.8.x/ISRHooks.sol +229 -0
  185. package/contracts/interfaces/v0.8.x/ISeaDropShimForContract.sol +0 -6
  186. package/contracts/interfaces/v0.8.x/ISharedMinterTieredAllowV0.sol +69 -0
  187. package/contracts/interfaces/v0.8.x/ITransferHook.sol +48 -0
  188. package/contracts/libs/v0.8.x/DependencyRegistryStorageLib.sol +12 -0
  189. package/contracts/libs/v0.8.x/ENSLib.sol +85 -0
  190. package/contracts/libs/v0.8.x/EnumerableSetUint16.sol +429 -0
  191. package/contracts/libs/v0.8.x/FeistelWalkLib.sol +221 -0
  192. package/contracts/libs/v0.8.x/ImmutableUint16Array.sol +201 -0
  193. package/contracts/libs/v0.8.x/V3EngineLib.sol +490 -0
  194. package/contracts/libs/v0.8.x/V3TransferHookLib.sol +251 -0
  195. package/contracts/libs/v0.8.x/minter-libs/OnChainAllowlistLib.sol +165 -0
  196. package/contracts/minter-suite/Minters/MinterSetPriceOnChainAllowV0.sol +632 -0
  197. package/contracts/minter-suite/Minters/MinterSetPriceTieredAllowV1.sol +871 -0
  198. package/contracts/minter-suite/Minters/MinterSlidingScaleV0.sol +493 -0
  199. package/contracts/minter-suite/Minters/one-off/ClaimMinter.sol +595 -0
  200. package/contracts/minter-suite/Minters/one-off/MinterSetPriceTieredOnChainAllowV0.sol +894 -0
  201. package/contracts/minter-suite/Minters/one-off/SeaDropXArtBlocksShim.sol +59 -41
  202. package/contracts/mock/ENSLibMock.sol +25 -0
  203. package/contracts/mock/EnumerableSetUint16Mock.sol +88 -0
  204. package/contracts/mock/FeistelWalkLibMock.sol +81 -0
  205. package/contracts/mock/ImmutableUint16ArrayMock.sol +62 -0
  206. package/contracts/mock/MockGenArt721V3Core.sol +416 -0
  207. package/contracts/mock/MockPMP.sol +287 -0
  208. package/contracts/mock/MockTransferHook.sol +160 -0
  209. package/contracts/mock/MockTransferHookTestHelpers.sol +96 -0
  210. package/contracts/mock/ReentrancyBatchMockShared.sol +53 -0
  211. package/contracts/mock/RelayExecutorTestHelper.sol +44 -0
  212. package/contracts/relay/RelayExecutor.sol +61 -0
  213. package/contracts/relay/RelayExecutorPartialReverts.sol +79 -0
  214. package/contracts/royalty-registry/GenArt721RoyaltyOverrideSplits.sol +130 -0
  215. package/contracts/utils/MintMulticallUtil.sol +103 -0
  216. package/contracts/web3call/PMPV0.sol +92 -98
  217. package/contracts/web3call/PMPV1.sol +1204 -0
  218. package/contracts/web3call/augment-hooks/AbstractPMPAugmentHook.sol +1 -1
  219. package/contracts/web3call/augment-hooks/InjectBaseGasFee.sol +1 -1
  220. package/contracts/web3call/augment-hooks/InjectBlockHeight.sol +57 -0
  221. package/contracts/web3call/augment-hooks/InjectBlockHeightAndArtistProjectOverrides.sol +310 -0
  222. package/contracts/web3call/augment-hooks/InjectBlockHeightAndNumOwnedProjectTokens.sol +141 -0
  223. package/contracts/web3call/augment-hooks/InjectBlockHeightProjectInvocationsAndArtistProjectOverrides.sol +326 -0
  224. package/contracts/web3call/augment-hooks/InjectBlockTimestamp.sol +57 -0
  225. package/contracts/web3call/augment-hooks/InjectBytesPerToken.sol +279 -0
  226. package/contracts/web3call/augment-hooks/InjectIsProjectFullyMinted.sol +1 -1
  227. package/contracts/web3call/augment-hooks/InjectTokenHashSeed.sol +1 -1
  228. package/contracts/web3call/augment-hooks/InjectTokenOwner.sol +1 -1
  229. package/contracts/web3call/augment-hooks/InjectTokenOwnerEthBalance.sol +1 -1
  230. package/contracts/web3call/augment-hooks/SRHooks.sol +1722 -0
  231. package/contracts/web3call/combined-hooks/LiftHooks.sol +304 -0
  232. package/contracts/web3call/configure-hooks/{abstractPMPConfigureHook.sol → AbstractPMPConfigureHook.sol} +1 -1
  233. package/contracts/web3call/configure-hooks/PMPConfigureLockHook.sol +107 -0
  234. package/package.json +40 -25
@@ -0,0 +1,1722 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-only
2
+ // Created By: Art Blocks Inc.
3
+
4
+ pragma solidity 0.8.22;
5
+
6
+ import {AbstractPMPAugmentHook} from "../augment-hooks/AbstractPMPAugmentHook.sol";
7
+
8
+ import {IWeb3Call} from "../../interfaces/v0.8.x/IWeb3Call.sol";
9
+ import {IPMPV0} from "../../interfaces/v0.8.x/IPMPV0.sol";
10
+ import {ISRHooks} from "../../interfaces/v0.8.x/ISRHooks.sol";
11
+ import {Strings} from "@openzeppelin-5.0/contracts/utils/Strings.sol";
12
+ import {IERC721} from "@openzeppelin-5.0/contracts/interfaces/IERC721.sol";
13
+ import {IDelegationRegistry as IDelegationRegistryV1} from "../../interfaces/v0.8.x/IDelegationRegistry.sol";
14
+ import {IDelegateRegistry as IDelegationRegistryV2} from "../../interfaces/v0.8.x/IDelegateRegistry.sol";
15
+ import {IPMPConfigureHook} from "../../interfaces/v0.8.x/IPMPConfigureHook.sol";
16
+ import {IPMPAugmentHook} from "../../interfaces/v0.8.x/IPMPAugmentHook.sol";
17
+
18
+ import {SafeCast} from "@openzeppelin-5.0/contracts/utils/math/SafeCast.sol";
19
+ import {Initializable} from "@openzeppelin-5.0/contracts-upgradeable/proxy/utils/Initializable.sol";
20
+ import {UUPSUpgradeable} from "@openzeppelin-5.0/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
21
+ import {OwnableUpgradeable} from "@openzeppelin-5.0/contracts-upgradeable/access/OwnableUpgradeable.sol";
22
+
23
+ import {ImmutableUint16Array} from "../../libs/v0.8.x/ImmutableUint16Array.sol";
24
+ import {SSTORE2} from "../../libs/v0.8.x/SSTORE2.sol";
25
+ import {ABHelpers} from "../../libs/v0.8.x/ABHelpers.sol";
26
+ import {FeistelWalkLib} from "../../libs/v0.8.x/FeistelWalkLib.sol";
27
+ import {ENSLib} from "../../libs/v0.8.x/ENSLib.sol";
28
+ import {EnumerableSetUint16} from "../../libs/v0.8.x/EnumerableSetUint16.sol";
29
+
30
+ /**
31
+ * @title SRHooks
32
+ * @author Art Blocks Inc.
33
+ * @notice This hook tracks the send and receive states of a token, and the metadata for a token.
34
+ * It implements the primary functionality of the SR project, and accepts most calls directly.
35
+ * It acts as a PMPV0 hook, and therefore derives from AbstractPMPAugmentHook.
36
+ * For clear off-chain provenance indexing and efficiency purposes, it emits events as if it were a
37
+ * PMPV0 contract, so it may be indexed directly by off-chain tools, including the Art Blocks subgraph.
38
+ * Some state emit events that may be custom, or may not be directly related to PMPV0, but may still be useful
39
+ * for off-chain indexing and analysis and frontend development.
40
+ * Implements a Feistel walk for efficient pseudo-random sampling at each block over large sets and arrays. This
41
+ * sampling is considered efficient and effective enough for our use case of streaming pseudorandom live data at each block.
42
+ * @dev Operationally, this contract assumes that the PMPV0 contract will be configured with at least the following PMP values,
43
+ * and that those values will be configured in a way where they are never able to be updated (e.g. auth option is dead address):
44
+ * - SendState
45
+ * - ReceiveState
46
+ * - ActiveSlot
47
+ * - ImageVersionSlot0
48
+ * - SoundVersionSlot0
49
+ * - ImageVersionSlot1
50
+ * - SoundVersionSlot1
51
+ * - ImageVersionSlot2
52
+ * - SoundVersionSlot2
53
+ * - ImageVersionSlot3
54
+ * - SoundVersionSlot3
55
+ * - ImageVersionSlot4
56
+ * - SoundVersionSlot4
57
+ * @dev This contract follows the UUPS (Universal Upgradeable Proxy Standard) pattern.
58
+ * It uses OpenZeppelin's upgradeable contracts and must be deployed behind a proxy.
59
+ * Only the owner can authorize upgrades via the _authorizeUpgrade function, which may
60
+ * be eventually disabled in future versions after to lock project functionality.
61
+ */
62
+ contract SRHooks is
63
+ Initializable,
64
+ AbstractPMPAugmentHook,
65
+ OwnableUpgradeable,
66
+ UUPSUpgradeable,
67
+ ISRHooks
68
+ {
69
+ using EnumerableSetUint16 for EnumerableSetUint16.Uint16Set;
70
+ using ImmutableUint16Array for ImmutableUint16Array.Uint16Array;
71
+ using SafeCast for uint256;
72
+
73
+ address public CORE_CONTRACT_ADDRESS;
74
+
75
+ uint256 public CORE_PROJECT_ID;
76
+
77
+ uint256 public constant MAX_IMAGE_DATA_LENGTH = 1024 * 15; // 15 KB, beyond which is unlikely to represent a 64x64 image
78
+ uint256 public constant MAX_SOUND_DATA_LENGTH = 1024 * 10; // 10 KB, beyond which is unlikely to represent a sound
79
+
80
+ uint256 internal constant MAX_RECEIVE_RATE_PER_BLOCK = 36; // 36 tokens per 12s block, to limit payload size for art script
81
+
82
+ uint256 internal constant MAX_SENDING_TO_LENGTH = 25; // 25 tokens, beyond which gas costs are high, you are over-represented, and if dilution is considered in the future, it would greatly inflate live data iteration time
83
+ uint256 internal constant MAX_RECEIVING_FROM_ARRAY_LENGTH = 1_000; // max receiving from array length is 1k to bound worst case iteration time when getting live data
84
+
85
+ // constant delegation registry pointers and rights
86
+ IDelegationRegistryV2 public constant DELEGATE_V2 =
87
+ IDelegationRegistryV2(0x00000000000000447e69651d841bD8D104Bed493);
88
+ bytes32 public constant DELEGATION_REGISTRY_TOKEN_OWNER_RIGHTS =
89
+ bytes32("postmintparameters");
90
+
91
+ // ------ TOKEN METADATA STATE VARIABLES ------
92
+
93
+ uint256 public constant NUM_METADATA_SLOTS = 5;
94
+
95
+ // struct for the token metadata in storage
96
+ struct TokenMetadata {
97
+ address imageDataAddress; // 20 bytes
98
+ uint16 imageVersion; // 2 bytes - max 65,535 updates per slot
99
+ address soundDataAddress; // 20 bytes
100
+ uint16 soundVersion; // 2 bytes - max 65,535 updates per slot
101
+ }
102
+
103
+ /// @notice mapping of token numbers to slot to metadata
104
+ mapping(uint256 tokenNumber => mapping(uint256 slot => TokenMetadata slotMetadata))
105
+ private _tokensMetadata;
106
+
107
+ struct TokenAuxStateData {
108
+ uint8 activeSlot;
109
+ bool isReceivingTo; // true if the token is only receiving tokens sending to it, false if in any other receive state
110
+ }
111
+
112
+ /// @notice mapping of token numbers to aux state data
113
+ mapping(uint256 tokenNumber => TokenAuxStateData auxStateData)
114
+ private _tokenAuxStateData;
115
+
116
+ // ------ SEND/RECEIVE STATE (GLOBAL) ------
117
+
118
+ /// @notice Set of token numbers that are currently in state SendGeneral
119
+ // @dev Uses custom packed EnumerableSetUint16 to efficiently pack uint16 values into shared storage slots
120
+ // @dev need O(1) access and O(1) insertion/removal for both sending and receiving tokens, so use an EnumerableSet
121
+ EnumerableSetUint16.Uint16Set private _sendGeneralTokens;
122
+
123
+ /// @notice Set of token numbers that are currently in state ReceiveGeneral
124
+ // @dev Uses custom packed EnumerableSetUint16 to efficiently pack uint16 values into shared storage slots
125
+ // @dev need O(1) access and O(1) insertion/removal for both sending and receiving tokens, so use an EnumerableSet
126
+ EnumerableSetUint16.Uint16Set private _receiveGeneralTokens;
127
+
128
+ // ------ SEND/RECEIVE STATE (PER TOKEN) ------
129
+
130
+ /// @notice Set of token numbers that are sending to a specific token
131
+ // @dev Uses custom packed EnumerableSetUint16 to efficiently pack uint16 values into shared storage slots
132
+ // @dev need O(1) access and O(1) insertion/removal for both sending and receiving tokens, so use an EnumerableSet
133
+ mapping(uint256 receivingTokenNumber => EnumerableSetUint16.Uint16Set tokensSendingToMe)
134
+ private _tokensSendingToMe;
135
+
136
+ /// @notice Array of token numbers that a given token is sending to (when in state SendTo)
137
+ // @dev need only O(1) access (not insertion/removal) for sending tokens, so use an ImmutableUint16Array
138
+ mapping(uint256 sendingTokenNumber => ImmutableUint16Array.Uint16Array tokensSendingTo)
139
+ private _tokensSendingTo;
140
+
141
+ /// @notice Array of token numbers that a token is open to receiving from (when in state ReceiveFrom)
142
+ // @dev need only O(1) access (not insertion/removal) for receiving tokens, so use an ImmutableUint16Array
143
+ mapping(uint256 receivingTokenNumber => ImmutableUint16Array.Uint16Array tokensReceivingFrom)
144
+ private _tokensReceivingFrom;
145
+
146
+ /**
147
+ * @notice modifier-like internal function to check if an address is the owner or a valid delegate.xyz V2 of the token owner.
148
+ * @param tokenNumber The token number to check.
149
+ * @param addressToCheck The address to check.
150
+ * @return isOwnerOrDelegate True if the address is the owner or a valid delegate.xyz V2 of the token owner, false otherwise.
151
+ * @return ownerAddress The address of the token owner.
152
+ * @dev This function is used to check if an address is the owner or a valid delegate.xyz V2 of the token owner,
153
+ * and to get the address of the token owner.
154
+ */
155
+ function _isOwnerOrDelegate(
156
+ uint256 tokenNumber,
157
+ address addressToCheck
158
+ ) internal view returns (bool isOwnerOrDelegate, address ownerAddress) {
159
+ uint256 tokenId = ABHelpers.tokenIdFromProjectIdAndTokenNumber({
160
+ projectId: CORE_PROJECT_ID,
161
+ tokenNumber: tokenNumber
162
+ });
163
+ ownerAddress = IERC721(CORE_CONTRACT_ADDRESS).ownerOf(tokenId);
164
+ isOwnerOrDelegate =
165
+ addressToCheck == ownerAddress ||
166
+ DELEGATE_V2.checkDelegateForERC721({
167
+ to: addressToCheck,
168
+ from: ownerAddress,
169
+ contract_: CORE_CONTRACT_ADDRESS,
170
+ tokenId: tokenId,
171
+ rights: DELEGATION_REGISTRY_TOKEN_OWNER_RIGHTS
172
+ });
173
+ }
174
+
175
+ /// disable initialization in deployed implementation contract for clarity
176
+ /// @custom:oz-upgrades-unsafe-allow constructor
177
+ constructor() {
178
+ _disableInitializers();
179
+ }
180
+
181
+ /**
182
+ * @notice Initializes the contract with the PMPV0 address and sets the owner.
183
+ * @dev This function replaces the constructor for upgradeable contracts.
184
+ * Can only be called once due to the initializer modifier.
185
+ * @param _owner The address that will own this contract and authorize upgrades.
186
+ * @param _coreContractAddress The address of the core contract.
187
+ * @param _coreProjectId The project ID of the core contract.
188
+ */
189
+ function initialize(
190
+ address _owner,
191
+ address _coreContractAddress,
192
+ uint256 _coreProjectId
193
+ ) public initializer {
194
+ __Ownable_init(_owner);
195
+ __UUPSUpgradeable_init();
196
+
197
+ CORE_CONTRACT_ADDRESS = _coreContractAddress;
198
+ CORE_PROJECT_ID = _coreProjectId;
199
+
200
+ emit SRHooksInitialized({
201
+ coreContractAddress: _coreContractAddress,
202
+ coreProjectId: _coreProjectId
203
+ });
204
+ // @dev this contract does not emit PMPV0 project configuration events.
205
+ // This decouples indexing of PMP Configure events from the indexing of the project configuration information.
206
+ // Operationally, this contract requires configuration of the PMP project configuration as described in the contract-
207
+ // level documentation.
208
+ }
209
+
210
+ /**
211
+ * @notice Augment the token parameters for a given token.
212
+ * Augments the token parameters by appending the token's active slot's metadata and send/receive states.
213
+ * The following fields are appended:
214
+ * - imageData: the hex string of the compressed image data of the token's active slot
215
+ * - soundData: the hex string of the compressed sound data of the token's active slot
216
+ * - sendState: the string of the send state of the token (SendGeneral, SendTo, Neutral)
217
+ * - receiveState: the string of the receive state of the token (ReceiveGeneral, ReceiveFrom, Neutral)
218
+ * @dev This hook is called when a token's PMPs are read.
219
+ * @dev This must return all desired tokenParams, not just additional data.
220
+ * @param tokenParams The token parameters for the queried token.
221
+ * @return augmentedTokenParams The augmented token parameters.
222
+ */
223
+ function onTokenPMPReadAugmentation(
224
+ address /* coreContract */,
225
+ uint256 tokenId,
226
+ IWeb3Call.TokenParam[] calldata tokenParams
227
+ )
228
+ external
229
+ view
230
+ override
231
+ returns (IWeb3Call.TokenParam[] memory augmentedTokenParams)
232
+ {
233
+ // create a new augmentedTokenParams array with maximum length of
234
+ // input tokenParams + 1 extra element for the squiggle's token hash
235
+ uint256 originalLength = tokenParams.length;
236
+ uint256 augmentedMaxLength = originalLength + 4; // 4 extra elements for the token metadata fields
237
+ augmentedTokenParams = new IWeb3Call.TokenParam[](augmentedMaxLength);
238
+
239
+ // copy original tokenParams to augmentedTokenParams
240
+ for (uint256 i = 0; i < originalLength; i++) {
241
+ augmentedTokenParams[i] = tokenParams[i];
242
+ }
243
+
244
+ // append each token metadata fields (image and sound data addresses) to the augmentedTokenParams array
245
+ // @dev load active slot from storage
246
+ uint256 tokenNumber = ABHelpers.tokenIdToTokenNumber(tokenId);
247
+ uint256 activeSlot = _tokenAuxStateData[tokenNumber].activeSlot;
248
+ TokenMetadata storage tokenMetadataStorage = _tokensMetadata[
249
+ tokenNumber
250
+ ][activeSlot];
251
+ // return the image and sound data as hex strings
252
+ augmentedTokenParams[originalLength] = IWeb3Call.TokenParam({
253
+ key: "imageData",
254
+ value: _getHexStringFromSSTORE2(
255
+ tokenMetadataStorage.imageDataAddress
256
+ )
257
+ });
258
+ augmentedTokenParams[originalLength + 1] = IWeb3Call.TokenParam({
259
+ key: "soundData",
260
+ value: _getHexStringFromSSTORE2(
261
+ tokenMetadataStorage.soundDataAddress
262
+ )
263
+ });
264
+
265
+ // include send and receive states
266
+ augmentedTokenParams[originalLength + 2] = IWeb3Call.TokenParam({
267
+ key: "sendState",
268
+ value: _sendStateToString(_getSendState(tokenNumber))
269
+ });
270
+ augmentedTokenParams[originalLength + 3] = IWeb3Call.TokenParam({
271
+ key: "receiveState",
272
+ value: _receiveStateToString(_getReceiveState(tokenNumber))
273
+ });
274
+
275
+ // return the augmented tokenParams
276
+ return augmentedTokenParams;
277
+ }
278
+
279
+ /**
280
+ * @notice Updates the state and metadata for a given token.
281
+ * Reverts if the token number is invalid or the msg.sender is not owner or valid delegate.xyz V2 of token owner.
282
+ * Reverts if invalid configuration is provided.
283
+ * Includes two boolean flags to update the send and receive states and token metadata separately, in a single function call.
284
+ * @param tokenNumber The token number to update.
285
+ * @param updateSendState Whether to update the send state.
286
+ * @param sendState The new send state. Valid values are SendGeneral, SendTo, Neutral.
287
+ * @param tokensSendingTo Tokens to send this token to. Only non-empty iff updateSendState is true and sendState is SendTo.
288
+ * @param updateReceiveState Whether to update the receive state.
289
+ * @param receiveState The new receive state. Valid values are ReceiveGeneral, ReceiveFrom, Neutral.
290
+ * @param tokensReceivingFrom Tokens this token is open to receive from. Only non-empty iff updateReceiveState is true and receiveState is ReceiveFrom.
291
+ * @param updateTokenMetadata Whether to update the token metadata.
292
+ * @param updatedActiveSlot The new active slot. If updating token metadata, this is the new active slot.
293
+ * @param tokenMetadataCalldata The new token metadata. If updating token metadata, this is the new token metadata at the updated active slot. Only non-empty iff updateTokenMetadata is true.
294
+ */
295
+ function updateTokenStateAndMetadata(
296
+ uint256 tokenNumber,
297
+ bool updateSendState,
298
+ SendStates sendState,
299
+ uint16[] memory tokensSendingTo,
300
+ bool updateReceiveState,
301
+ ReceiveStates receiveState,
302
+ uint16[] memory tokensReceivingFrom,
303
+ bool updateTokenMetadata,
304
+ uint256 updatedActiveSlot,
305
+ TokenMetadataCalldata memory tokenMetadataCalldata
306
+ ) external {
307
+ // CHECKS
308
+ // require token number is valid uint16
309
+ require(tokenNumber < type(uint16).max, "Invalid token number");
310
+ // msg.sender must be owner or valid delegate.xyz V2 of token owner
311
+ // @dev this also checks that the token number is valid (exists and has valid owner)
312
+ (bool isOwnerOrDelegate, address ownerAddress) = _isOwnerOrDelegate(
313
+ tokenNumber,
314
+ msg.sender
315
+ );
316
+ require(
317
+ isOwnerOrDelegate,
318
+ "Only owner or valid delegate.xyz V2 of token owner allowed"
319
+ );
320
+ require(
321
+ updateSendState || updateReceiveState || updateTokenMetadata,
322
+ "At least one update must be provided"
323
+ );
324
+
325
+ // CHECKS-AND-EFFECTS (BRANCHED LOGIC)
326
+ // update token metadata first, prior to any S/R state updates (since you need artwork before you can send/receive)
327
+ if (updateTokenMetadata) {
328
+ // EFFECTS
329
+ // update the token metadata
330
+ _updateTokenMetadata({
331
+ tokenNumber: tokenNumber,
332
+ updatedActiveSlot: updatedActiveSlot,
333
+ tokenMetadataCalldata: tokenMetadataCalldata,
334
+ ownerAddress: ownerAddress
335
+ });
336
+ }
337
+
338
+ // update send/receive states second, after any metadata updates (since you need artwork before you can send/receive)
339
+ if (updateSendState) {
340
+ _updateSendState({
341
+ tokenNumber: tokenNumber,
342
+ sendState: sendState,
343
+ tokensSendingTo: tokensSendingTo,
344
+ ownerAddress: ownerAddress
345
+ });
346
+ }
347
+ if (updateReceiveState) {
348
+ _updateReceiveState({
349
+ tokenNumber: tokenNumber,
350
+ receiveState: receiveState,
351
+ tokensReceivingFrom: tokensReceivingFrom,
352
+ ownerAddress: ownerAddress
353
+ });
354
+ }
355
+ }
356
+
357
+ /**
358
+ * @notice Gets the live data for a given token.
359
+ * @param tokenNumber The token number to get the live data for.
360
+ * @param blockNumber The block number to get the live data for. Must be in latest 256 blocks.
361
+ * @param maxReceive The maximum number of tokens to receive, in each array of receivedTokensGeneral and receivedTokensTo.
362
+ * maxReceive must be less than or equal to MAX_RECEIVE_RATE_PER_BLOCK.
363
+ * Treats block number of 0 as latest completed block.
364
+ * Reverts if block number is in future - need block hash to be defined.
365
+ * NOTE: Each array of receivedTokensGeneral and receivedTokensTo has a maximum length of maxReceive,
366
+ * but their combined length may be greater than maxReceive. The art script should handle this by
367
+ * deterministically shuffling/sampling from the arrays if desired.
368
+ * WARNING: This function is designed for off-chain view calls only and may exceed block gas limits in cases where
369
+ * a token has many senders or is receiving from many tokens. It is not intended to be called within transactions.
370
+ * WARNING: Self-referential SendGeneral and SendTo tokens may be included in the results.
371
+ * @return sendState The send state of the token.
372
+ * @return receiveState The receive state of the token.
373
+ * @return receivedTokensGeneral The received tokens general of the token.
374
+ * @return receivedTokensTo The received tokens to of the token.
375
+ * @return numSendGeneral The number of tokens in the send general pool.
376
+ * @return numReceiveGeneral The number of tokens in the receive general pool.
377
+ * @return numSendingToMe The number of tokens sending to me.
378
+ * @return usedBlockNumber The block number used to generate the random numbers.
379
+ */
380
+ function getLiveData(
381
+ uint256 tokenNumber,
382
+ uint256 blockNumber,
383
+ uint256 maxReceive
384
+ )
385
+ external
386
+ view
387
+ returns (
388
+ SendStates sendState,
389
+ ReceiveStates receiveState,
390
+ TokenLiveData[] memory receivedTokensGeneral,
391
+ TokenLiveData[] memory receivedTokensTo,
392
+ uint256 numSendGeneral,
393
+ uint256 numReceiveGeneral,
394
+ uint256 numSendingToMe,
395
+ uint256 usedBlockNumber
396
+ )
397
+ {
398
+ // CHECKS
399
+ // require token number is valid uint16 using SafeCast
400
+ tokenNumber.toUint16(); // will revert if tokenNumber > type(uint16).max
401
+ // require maxReceive is not too large
402
+ require(
403
+ maxReceive <= MAX_RECEIVE_RATE_PER_BLOCK,
404
+ "maxReceive is too large"
405
+ );
406
+
407
+ // treat block number of 0 as latest completed block
408
+ usedBlockNumber = blockNumber == 0 ? block.number - 1 : blockNumber;
409
+ if (usedBlockNumber > block.number - 1) {
410
+ revert("Block number in future - need block hash to be defined");
411
+ }
412
+ // ensure we get a valid block hash (must be in latest 256 blocks)
413
+ bytes32 blockhash_ = blockhash(usedBlockNumber); // returns zero if not in latest 256 blocks
414
+ require(
415
+ blockhash_ != bytes32(0),
416
+ "block hash not available - must be in lastest 256 blocks"
417
+ );
418
+
419
+ // populate send and receive states
420
+ sendState = _getSendState(tokenNumber);
421
+ receiveState = _getReceiveState(tokenNumber);
422
+
423
+ // populate the returned state variables
424
+ numSendGeneral = _sendGeneralTokens.length();
425
+ numReceiveGeneral = _receiveGeneralTokens.length();
426
+ numSendingToMe = _tokensSendingToMe[tokenNumber].length();
427
+ // used block number already populated
428
+
429
+ // case: neutral receiving state - no tokens received
430
+ if (receiveState == ReceiveStates.Neutral) {
431
+ return (
432
+ sendState,
433
+ receiveState,
434
+ new TokenLiveData[](0),
435
+ new TokenLiveData[](0),
436
+ numSendGeneral,
437
+ numReceiveGeneral,
438
+ numSendingToMe,
439
+ usedBlockNumber
440
+ );
441
+ }
442
+
443
+ // case: receiveGeneral state - all tokens received - sample from general pool
444
+ if (receiveState == ReceiveStates.ReceiveGeneral) {
445
+ receivedTokensGeneral = _sampleReceivedTokensGeneral({
446
+ tokenNumber: tokenNumber,
447
+ blockhash_: blockhash_,
448
+ maxReceive: maxReceive
449
+ });
450
+ receivedTokensTo = _sampleReceivedTokensTo({
451
+ tokenNumber: tokenNumber,
452
+ blockhash_: blockhash_,
453
+ maxReceive: maxReceive
454
+ });
455
+ return (
456
+ sendState,
457
+ receiveState,
458
+ receivedTokensGeneral,
459
+ receivedTokensTo,
460
+ numSendGeneral,
461
+ numReceiveGeneral,
462
+ numSendingToMe,
463
+ usedBlockNumber
464
+ );
465
+ }
466
+
467
+ // case: receiveFrom state - only tokens received from specific tokens
468
+ if (receiveState == ReceiveStates.ReceiveFrom) {
469
+ (
470
+ receivedTokensGeneral,
471
+ receivedTokensTo
472
+ ) = _sampleTokensReceivedFrom({
473
+ tokenNumber: tokenNumber,
474
+ blockhash_: blockhash_,
475
+ maxReceive: maxReceive
476
+ });
477
+ return (
478
+ sendState,
479
+ receiveState,
480
+ receivedTokensGeneral,
481
+ receivedTokensTo,
482
+ numSendGeneral,
483
+ numReceiveGeneral,
484
+ numSendingToMe,
485
+ usedBlockNumber
486
+ );
487
+ }
488
+
489
+ // case: receiveTo state - only tokens received to specific tokens
490
+ if (receiveState == ReceiveStates.ReceiveTo) {
491
+ receivedTokensTo = _sampleReceivedTokensTo({
492
+ tokenNumber: tokenNumber,
493
+ blockhash_: blockhash_,
494
+ maxReceive: maxReceive
495
+ });
496
+ return (
497
+ sendState,
498
+ receiveState,
499
+ new TokenLiveData[](0), // no tokens received from general pool
500
+ receivedTokensTo,
501
+ numSendGeneral,
502
+ numReceiveGeneral,
503
+ numSendingToMe,
504
+ usedBlockNumber
505
+ );
506
+ }
507
+ }
508
+
509
+ /**
510
+ * @notice Samples the received tokens general for a given token.
511
+ * Assumes token is receiving generally.
512
+ * @param tokenNumber The token number to get the received tokens general for.
513
+ * @param blockhash_ The block hash to get the received tokens general for.
514
+ * @param maxReceive The maximum number of tokens to receive.
515
+ * @return receivedTokensGeneral The received tokens general.
516
+ */
517
+ function _sampleReceivedTokensGeneral(
518
+ uint256 tokenNumber,
519
+ bytes32 blockhash_,
520
+ uint256 maxReceive
521
+ ) internal view returns (TokenLiveData[] memory) {
522
+ // calculate the general send pool size (tokens available to receive from)
523
+ uint256 sendGeneralLength = _sendGeneralTokens.length();
524
+ uint256 sampleQuantity = sendGeneralLength > maxReceive
525
+ ? maxReceive
526
+ : sendGeneralLength;
527
+
528
+ // sample from general pool, quantity sampleQuantity
529
+ bytes32 seed = keccak256(abi.encodePacked(blockhash_, tokenNumber));
530
+ return
531
+ _sampleFromSendGeneralPool({seed: seed, quantity: sampleQuantity});
532
+ }
533
+
534
+ /**
535
+ * @notice Samples tokens from the general sending pool using an Feistel walk.
536
+ * @dev Uses FeistelWalkLib for efficient pseudo-random sampling over the EnumerableSet.
537
+ * @param seed The seed for pseudo-randomness.
538
+ * @param quantity The number of tokens to sample.
539
+ * @return result Array of TokenLiveData for sampled tokens.
540
+ */
541
+ function _sampleFromSendGeneralPool(
542
+ bytes32 seed,
543
+ uint256 quantity
544
+ ) internal view returns (TokenLiveData[] memory) {
545
+ // edge case: quantity is 0, return empty array
546
+ // @dev no coverage - this is a weird case
547
+ if (quantity == 0) {
548
+ return new TokenLiveData[](0);
549
+ }
550
+ // calculate the send general pool length to support making a plan
551
+ uint256 sendGeneralLength = _sendGeneralTokens.length();
552
+ // perform Feistel walk to sample token numbers from the set
553
+ FeistelWalkLib.Plan memory plan = FeistelWalkLib.makePlan({
554
+ seed: seed,
555
+ N: sendGeneralLength
556
+ });
557
+ uint256[] memory sampledTokenIndices = FeistelWalkLib.sample({
558
+ p: plan,
559
+ K: quantity
560
+ });
561
+ // populate TokenLiveData for each sampled token
562
+ uint256 sampledCount = sampledTokenIndices.length;
563
+ TokenLiveData[] memory result = new TokenLiveData[](sampledCount);
564
+ // @dev pull project id and core contract address into memory for efficient sload minimization
565
+ uint256 _projectId = CORE_PROJECT_ID;
566
+ address _coreContractAddress = CORE_CONTRACT_ADDRESS;
567
+ for (uint256 i = 0; i < sampledCount; i++) {
568
+ uint256 tokenNumber = _sendGeneralTokens.at(sampledTokenIndices[i]);
569
+ uint256 tokenId = ABHelpers.tokenIdFromProjectIdAndTokenNumber({
570
+ projectId: _projectId,
571
+ tokenNumber: tokenNumber
572
+ });
573
+ result[i] = _getLiveDataForToken({
574
+ tokenNumber: tokenNumber,
575
+ tokenId: tokenId,
576
+ coreContractAddress: _coreContractAddress
577
+ });
578
+ }
579
+ return result;
580
+ }
581
+
582
+ /**
583
+ * @notice Samples the received tokens to for a given token, via a Feistel walk over the tokens sending to me.
584
+ * Assumes token is receiving generally.
585
+ * @param tokenNumber The token number to get the received tokens to for.
586
+ * @param blockhash_ The block hash to get the received tokens to for.
587
+ * @param maxReceive The maximum number of tokens to receive.
588
+ * @return receivedTokensTo The received tokens to.
589
+ */
590
+ function _sampleReceivedTokensTo(
591
+ uint256 tokenNumber,
592
+ bytes32 blockhash_,
593
+ uint256 maxReceive
594
+ ) internal view returns (TokenLiveData[] memory) {
595
+ // we perform a Feistel walk to sample token numbers from the set, and then get the live data for each token.
596
+ uint256 sendingToMeLength = _tokensSendingToMe[tokenNumber].length();
597
+ if (sendingToMeLength == 0) {
598
+ return new TokenLiveData[](0); // no tokens sending to me, return empty array
599
+ }
600
+ // return length will be lower of sendingToMeLength and maxReceive
601
+ uint256 sampleQuantity = sendingToMeLength > maxReceive
602
+ ? maxReceive
603
+ : sendingToMeLength;
604
+ // iterate over the tokens sending to me; perform Feistel walk to sample token numbers from the set
605
+ bytes32 seed = keccak256(abi.encodePacked(blockhash_, tokenNumber));
606
+ FeistelWalkLib.Plan memory plan = FeistelWalkLib.makePlan({
607
+ seed: seed,
608
+ N: sendingToMeLength
609
+ });
610
+ // sample token numbers from the set
611
+ uint256[] memory sampledTokenIndices = FeistelWalkLib.sample({
612
+ p: plan,
613
+ K: sampleQuantity
614
+ });
615
+ // allocate array of TokenLiveData for the selected token numbers
616
+ TokenLiveData[] memory result = new TokenLiveData[](sampleQuantity);
617
+ // @dev pull project id and core contract address into memory for efficient sload minimization
618
+ uint256 _projectId = CORE_PROJECT_ID;
619
+ address _coreContractAddress = CORE_CONTRACT_ADDRESS;
620
+ EnumerableSetUint16.Uint16Set
621
+ storage tokensSendingToMe_ = _tokensSendingToMe[tokenNumber];
622
+ // for each selected token number, get the live data
623
+ for (uint256 i = 0; i < sampleQuantity; i++) {
624
+ uint256 selectedTokenNumber = tokensSendingToMe_.at(
625
+ sampledTokenIndices[i]
626
+ );
627
+ uint256 selectedTokenId = ABHelpers
628
+ .tokenIdFromProjectIdAndTokenNumber({
629
+ projectId: _projectId,
630
+ tokenNumber: selectedTokenNumber
631
+ });
632
+ result[i] = _getLiveDataForToken({
633
+ tokenNumber: selectedTokenNumber,
634
+ tokenId: selectedTokenId,
635
+ coreContractAddress: _coreContractAddress
636
+ });
637
+ }
638
+ return result;
639
+ }
640
+
641
+ /**
642
+ * @notice Samples the tokens received from a given token, via a Feistel walk over the tokens receiving from me.
643
+ * @dev This function automatically deduplicates tokens if the tokensReceivingFrom array contains duplicates,
644
+ * ensuring each token appears at most once in the results. Self-referential tokens are also filtered out.
645
+ * @param tokenNumber The token number to get the tokens received from.
646
+ * @param blockhash_ The blockhash to use for pseudo-randomness.
647
+ * @param maxReceive The maximum number of tokens to receive.
648
+ * @return tokensReceivedFromGeneral The tokens received from the general pool.
649
+ * @return tokensReceivedFromTo The tokens received to the specific tokens.
650
+ */
651
+ function _sampleTokensReceivedFrom(
652
+ uint256 tokenNumber,
653
+ bytes32 blockhash_,
654
+ uint256 maxReceive
655
+ )
656
+ internal
657
+ view
658
+ returns (
659
+ TokenLiveData[] memory tokensReceivedFromGeneral,
660
+ TokenLiveData[] memory tokensReceivedFromTo
661
+ )
662
+ {
663
+ // load the tokens receiving from into memory for efficient SSTORE2 load minimization
664
+ uint16[] memory tokensReceivingFrom = _tokensReceivingFrom[tokenNumber]
665
+ .getAll();
666
+ uint256 receivingFromMeLength = tokensReceivingFrom.length;
667
+ // we will iterate continuously over the tokens receiving from me, and include it if from the general pool,
668
+ // or if sending to me.
669
+ // if the token is not sending to me, or not sending generally, we skip it.
670
+ // we perform a Feistel walk to sample token numbers from the set, and then get the live data for each token.
671
+ // @dev no coverage - we never allow zero-length receiveFrom arrays, but check for redundancy.
672
+ if (receivingFromMeLength == 0) {
673
+ return (new TokenLiveData[](0), new TokenLiveData[](0)); // no tokens receiving from me, return empty arrays
674
+ }
675
+ // iterate over the tokens receiving from me; perform Feistel walk to sample token numbers from the set
676
+ bytes32 seed = keccak256(abi.encodePacked(blockhash_, tokenNumber));
677
+ FeistelWalkLib.Plan memory plan = FeistelWalkLib.makePlan({
678
+ seed: seed,
679
+ N: receivingFromMeLength
680
+ });
681
+
682
+ // SEND TO TOKEN ITERATION
683
+ // sample tokens from SendTo states (in separate function to avoid stack too deep)
684
+ uint256[]
685
+ memory selectedTokenNumbersTo = _sampleTokensReceivedFromSendToPool({
686
+ tokenNumber: tokenNumber,
687
+ tokensReceivingFrom: tokensReceivingFrom,
688
+ receivingFromMeLength: receivingFromMeLength,
689
+ plan: plan,
690
+ maxReceive: maxReceive
691
+ });
692
+ uint256 selectedTokenNumbersToLength = selectedTokenNumbersTo.length;
693
+
694
+ // SEND GENERAL TOKEN ITERATION
695
+ // sample tokens from general send pool (in separate function to avoid stack too deep)
696
+ uint256[]
697
+ memory selectedTokenNumbersGeneral = _sampleTokensReceivedFromGeneralPool({
698
+ tokenNumber: tokenNumber,
699
+ tokensReceivingFrom: tokensReceivingFrom,
700
+ receivingFromMeLength: receivingFromMeLength,
701
+ plan: plan,
702
+ maxReceive: maxReceive
703
+ });
704
+ uint256 selectedTokenNumbersGeneralLength = selectedTokenNumbersGeneral
705
+ .length;
706
+
707
+ // allocate array of TokenLiveData for the selected general token numbers
708
+ tokensReceivedFromGeneral = new TokenLiveData[](
709
+ selectedTokenNumbersGeneralLength
710
+ );
711
+ // @dev pull project id and core contract address into memory for efficient sload minimization
712
+ uint256 _projectId = CORE_PROJECT_ID;
713
+ address _coreContractAddress = CORE_CONTRACT_ADDRESS;
714
+ // for each selected general token number, get the live data
715
+ for (uint256 i = 0; i < selectedTokenNumbersGeneralLength; i++) {
716
+ uint256 selectedTokenNumber = selectedTokenNumbersGeneral[i];
717
+ tokensReceivedFromGeneral[i] = _getLiveDataForToken({
718
+ tokenNumber: selectedTokenNumber,
719
+ tokenId: ABHelpers.tokenIdFromProjectIdAndTokenNumber({
720
+ projectId: _projectId,
721
+ tokenNumber: selectedTokenNumber
722
+ }),
723
+ coreContractAddress: _coreContractAddress
724
+ });
725
+ }
726
+ // allocate array of TokenLiveData for the selected to token numbers
727
+ tokensReceivedFromTo = new TokenLiveData[](
728
+ selectedTokenNumbersToLength
729
+ );
730
+ // for each selected to token number, get the live data
731
+ for (uint256 i = 0; i < selectedTokenNumbersToLength; i++) {
732
+ uint256 selectedTokenNumber = selectedTokenNumbersTo[i];
733
+ tokensReceivedFromTo[i] = _getLiveDataForToken({
734
+ tokenNumber: selectedTokenNumber,
735
+ tokenId: ABHelpers.tokenIdFromProjectIdAndTokenNumber({
736
+ projectId: _projectId,
737
+ tokenNumber: selectedTokenNumber
738
+ }),
739
+ coreContractAddress: _coreContractAddress
740
+ });
741
+ }
742
+ return (tokensReceivedFromGeneral, tokensReceivedFromTo);
743
+ }
744
+
745
+ /**
746
+ * @notice Helper function to sample tokens from the SendTo pool that are in the tokensReceivingFrom array.
747
+ * @dev This function automatically deduplicates tokens if the tokensReceivingFrom array contains duplicates.
748
+ * Self-referential tokens are also filtered out.
749
+ * @param tokenNumber The token number we're checking receives from.
750
+ * @param tokensReceivingFrom The array of tokens that might be sending to this token.
751
+ * @param receivingFromMeLength The length of the tokensReceivingFrom array.
752
+ * @param plan The Feistel walk plan for sampling.
753
+ * @param maxReceive The maximum number of tokens to receive.
754
+ * @return selectedTokenNumbersTo Array of token numbers from SendTo states.
755
+ */
756
+ function _sampleTokensReceivedFromSendToPool(
757
+ uint256 tokenNumber,
758
+ uint16[] memory tokensReceivingFrom,
759
+ uint256 receivingFromMeLength,
760
+ FeistelWalkLib.Plan memory plan,
761
+ uint256 maxReceive
762
+ ) internal view returns (uint256[] memory selectedTokenNumbersTo) {
763
+ // allocate array for selected token numbers
764
+ selectedTokenNumbersTo = new uint256[](maxReceive);
765
+ uint256 selectedTokenNumbersToLength = 0;
766
+
767
+ // deduplication tracking array for tokensReceivingFrom (handles duplicates in user-provided array)
768
+ uint256[] memory seenTokens = new uint256[](maxReceive);
769
+ uint256 seenCount = 0;
770
+
771
+ for (uint256 i = 0; i < receivingFromMeLength; i++) {
772
+ uint256 sampledTokenNumber;
773
+ {
774
+ uint256 sampledTokenIndex = FeistelWalkLib.index(plan, i);
775
+ sampledTokenNumber = tokensReceivingFrom[sampledTokenIndex];
776
+ }
777
+ // skip self-referential (token receiving from itself)
778
+ if (sampledTokenNumber == tokenNumber) {
779
+ continue;
780
+ }
781
+ // check for duplicates in tokensReceivingFrom array
782
+ bool alreadySeen = false;
783
+ for (uint256 j = 0; j < seenCount; j++) {
784
+ if (seenTokens[j] == sampledTokenNumber) {
785
+ alreadySeen = true;
786
+ break;
787
+ }
788
+ }
789
+ if (alreadySeen) {
790
+ continue;
791
+ }
792
+ // if sending to me, include it
793
+ if (
794
+ _tokensSendingToMe[tokenNumber].contains(
795
+ uint16(sampledTokenNumber)
796
+ )
797
+ ) {
798
+ selectedTokenNumbersTo[
799
+ selectedTokenNumbersToLength
800
+ ] = sampledTokenNumber;
801
+ selectedTokenNumbersToLength++;
802
+ // mark as seen to prevent duplicate processing
803
+ seenTokens[seenCount++] = sampledTokenNumber;
804
+ }
805
+ // if we have selected the maximum number of tokens, break
806
+ if (selectedTokenNumbersToLength >= maxReceive) {
807
+ break;
808
+ }
809
+ }
810
+
811
+ // resize array to actual length
812
+ assembly {
813
+ mstore(selectedTokenNumbersTo, selectedTokenNumbersToLength)
814
+ }
815
+ }
816
+
817
+ /**
818
+ * @notice Helper function to sample tokens from the general send pool that are in the tokensReceivingFrom array.
819
+ * @dev This function automatically deduplicates tokens if the tokensReceivingFrom array contains duplicates.
820
+ * Self-referential tokens are also filtered out.
821
+ * @param tokenNumber The token number we're checking receives from.
822
+ * @param tokensReceivingFrom The array of tokens that might be sending to this token.
823
+ * @param receivingFromMeLength The length of the tokensReceivingFrom array.
824
+ * @param plan The Feistel walk plan for sampling.
825
+ * @param maxReceive The maximum number of tokens to receive.
826
+ * @return selectedTokenNumbersGeneral Array of token numbers from the general pool.
827
+ */
828
+ function _sampleTokensReceivedFromGeneralPool(
829
+ uint256 tokenNumber,
830
+ uint16[] memory tokensReceivingFrom,
831
+ uint256 receivingFromMeLength,
832
+ FeistelWalkLib.Plan memory plan,
833
+ uint256 maxReceive
834
+ ) internal view returns (uint256[] memory selectedTokenNumbersGeneral) {
835
+ // allocate array for selected token numbers
836
+ selectedTokenNumbersGeneral = new uint256[](maxReceive);
837
+ uint256 selectedTokenNumbersGeneralLength = 0;
838
+
839
+ // deduplication tracking array for tokensReceivingFrom (handles duplicates in user-provided array)
840
+ uint256[] memory seenTokens = new uint256[](maxReceive);
841
+ uint256 seenCount = 0;
842
+
843
+ for (uint256 i = 0; i < receivingFromMeLength; i++) {
844
+ uint256 sampledTokenNumber;
845
+ {
846
+ uint256 sampledTokenIndex = FeistelWalkLib.index(plan, i);
847
+ sampledTokenNumber = tokensReceivingFrom[sampledTokenIndex];
848
+ }
849
+ // skip self-referential (token receiving from itself)
850
+ if (sampledTokenNumber == tokenNumber) {
851
+ continue;
852
+ }
853
+ // check for duplicates in tokensReceivingFrom array
854
+ bool alreadySeen = false;
855
+ for (uint256 j = 0; j < seenCount; j++) {
856
+ if (seenTokens[j] == sampledTokenNumber) {
857
+ alreadySeen = true;
858
+ break;
859
+ }
860
+ }
861
+ if (alreadySeen) {
862
+ continue;
863
+ }
864
+ // if sending generally, include it for sure
865
+ if (_sendGeneralTokens.contains(uint16(sampledTokenNumber))) {
866
+ selectedTokenNumbersGeneral[
867
+ selectedTokenNumbersGeneralLength
868
+ ] = sampledTokenNumber;
869
+ selectedTokenNumbersGeneralLength++;
870
+ // mark as seen to prevent duplicate processing
871
+ seenTokens[seenCount++] = sampledTokenNumber;
872
+ // if we have selected the maximum number of tokens, break
873
+ if (selectedTokenNumbersGeneralLength >= maxReceive) {
874
+ break;
875
+ }
876
+ }
877
+ }
878
+
879
+ // resize array to actual length
880
+ assembly {
881
+ mstore(
882
+ selectedTokenNumbersGeneral,
883
+ selectedTokenNumbersGeneralLength
884
+ )
885
+ }
886
+ }
887
+
888
+ /**
889
+ * @notice Gets the live data for a specific token.
890
+ * @dev Fetches token owner and metadata from storage.
891
+ * @param tokenNumber The token number to get live data for.
892
+ * @return liveData The TokenLiveData struct for the token.
893
+ */
894
+ function _getLiveDataForToken(
895
+ uint256 tokenNumber,
896
+ uint256 tokenId,
897
+ address coreContractAddress
898
+ ) internal view returns (TokenLiveData memory liveData) {
899
+ // get owner address
900
+ address ownerAddress = IERC721(coreContractAddress).ownerOf(tokenId);
901
+
902
+ // get active slot and metadata
903
+ uint256 activeSlot = _tokenAuxStateData[tokenNumber].activeSlot;
904
+ TokenMetadata storage metadata = _tokensMetadata[tokenNumber][
905
+ activeSlot
906
+ ];
907
+
908
+ // populate live data
909
+ liveData.tokenNumber = tokenNumber;
910
+ liveData.ownerAddress = ownerAddress;
911
+
912
+ // get image and sound data
913
+ if (metadata.imageDataAddress != address(0)) {
914
+ liveData.imageDataCompressed = SSTORE2.read(
915
+ metadata.imageDataAddress
916
+ );
917
+ }
918
+ if (metadata.soundDataAddress != address(0)) {
919
+ liveData.soundDataCompressed = SSTORE2.read(
920
+ metadata.soundDataAddress
921
+ );
922
+ }
923
+
924
+ // get owner ENS name using ENS Universal Resolver
925
+ // @dev try/catch pattern, avoids reverts, returns empty string if reverse lookup not configured
926
+ liveData.ownerEnsName = ENSLib.getEnsName(ownerAddress);
927
+
928
+ return liveData;
929
+ }
930
+
931
+ /**
932
+ * @notice Updates the token metadata for a given token.
933
+ * Internal function - assumes token is valid
934
+ * @param tokenNumber The token number to update.
935
+ * @param updatedActiveSlot The new active slot.
936
+ * @param tokenMetadataCalldata The new token metadata.
937
+ * @param ownerAddress The address of the token owner.
938
+ */
939
+ function _updateTokenMetadata(
940
+ uint256 tokenNumber,
941
+ uint256 updatedActiveSlot,
942
+ TokenMetadataCalldata memory tokenMetadataCalldata,
943
+ address ownerAddress
944
+ ) internal {
945
+ // CHECKS
946
+ // updatedActiveSlot must be valid
947
+ require(updatedActiveSlot < NUM_METADATA_SLOTS, "Invalid active slot");
948
+ bool isUpdatingActiveSlot = _tokenAuxStateData[tokenNumber]
949
+ .activeSlot != updatedActiveSlot;
950
+ TokenMetadata storage tokenMetadataStorage = _tokensMetadata[
951
+ tokenNumber
952
+ ][updatedActiveSlot];
953
+ // require that the new active slot has/will have image metadata if updating the active slot
954
+ // @dev this is to prevent participation in the pools without artwork
955
+ if (isUpdatingActiveSlot) {
956
+ require(
957
+ tokenMetadataCalldata.updateImage ||
958
+ tokenMetadataStorage.imageDataAddress != address(0),
959
+ "New active slot must have image metadata when updating active slot"
960
+ );
961
+ }
962
+
963
+ // CHECKS-AND-EFFECTS (BRANCHED LOGIC)
964
+ uint256 tokenId = ABHelpers.tokenIdFromProjectIdAndTokenNumber({
965
+ projectId: CORE_PROJECT_ID,
966
+ tokenNumber: tokenNumber
967
+ });
968
+ // update the token metadata
969
+ // image data
970
+ if (tokenMetadataCalldata.updateImage) {
971
+ require(
972
+ tokenMetadataCalldata.imageDataCompressed.length > 0,
973
+ "Image data must be provided when updating"
974
+ );
975
+ require(
976
+ tokenMetadataCalldata.imageDataCompressed.length <=
977
+ MAX_IMAGE_DATA_LENGTH,
978
+ "Image data must be less than or equal to MAX_IMAGE_DATA_LENGTH"
979
+ );
980
+ // @dev image data, compressed + use sstore2 for efficient
981
+ tokenMetadataStorage.imageDataAddress = SSTORE2.write(
982
+ tokenMetadataCalldata.imageDataCompressed
983
+ );
984
+ tokenMetadataStorage.imageVersion += 1;
985
+ // emit PMPV0-indexable event for image data update
986
+ emit IPMPV0.TokenParamsConfigured({
987
+ coreContract: CORE_CONTRACT_ADDRESS,
988
+ tokenId: tokenId,
989
+ pmpInputs: _getPmpInputsForImageDataUpdate({
990
+ slot: updatedActiveSlot,
991
+ imageVersion: tokenMetadataStorage.imageVersion
992
+ }),
993
+ authAddresses: _getSingleElementAddressArray(ownerAddress)
994
+ });
995
+ } else {
996
+ require(
997
+ tokenMetadataCalldata.imageDataCompressed.length == 0,
998
+ "Image data must be empty when not updating"
999
+ );
1000
+ }
1001
+ // sound data
1002
+ if (tokenMetadataCalldata.updateSound) {
1003
+ // allow "clearing" the sound data by providing an empty bytes array
1004
+ if (tokenMetadataCalldata.soundDataCompressed.length == 0) {
1005
+ tokenMetadataStorage.soundDataAddress = address(0);
1006
+ tokenMetadataStorage.soundVersion += 1;
1007
+ // emit PMPV0-indexable event for sound data update
1008
+ emit IPMPV0.TokenParamsConfigured({
1009
+ coreContract: CORE_CONTRACT_ADDRESS,
1010
+ tokenId: tokenId,
1011
+ pmpInputs: _getPmpInputsForSoundDataUpdate({
1012
+ slot: updatedActiveSlot,
1013
+ soundVersion: tokenMetadataStorage.soundVersion
1014
+ }),
1015
+ authAddresses: _getSingleElementAddressArray(ownerAddress)
1016
+ });
1017
+ } else {
1018
+ require(
1019
+ tokenMetadataCalldata.soundDataCompressed.length <=
1020
+ MAX_SOUND_DATA_LENGTH,
1021
+ "Sound data must be less than or equal to MAX_SOUND_DATA_LENGTH"
1022
+ );
1023
+ // @dev sound data, compressed + use sstore2 for efficient
1024
+ tokenMetadataStorage.soundDataAddress = SSTORE2.write(
1025
+ tokenMetadataCalldata.soundDataCompressed
1026
+ );
1027
+ tokenMetadataStorage.soundVersion += 1;
1028
+ // emit PMPV0-indexable event for sound data update
1029
+ emit IPMPV0.TokenParamsConfigured({
1030
+ coreContract: CORE_CONTRACT_ADDRESS,
1031
+ tokenId: tokenId,
1032
+ pmpInputs: _getPmpInputsForSoundDataUpdate({
1033
+ slot: updatedActiveSlot,
1034
+ soundVersion: tokenMetadataStorage.soundVersion
1035
+ }),
1036
+ authAddresses: _getSingleElementAddressArray(ownerAddress)
1037
+ });
1038
+ }
1039
+ } else {
1040
+ require(
1041
+ tokenMetadataCalldata.soundDataCompressed.length == 0,
1042
+ "Sound data must be empty when not updating"
1043
+ );
1044
+ }
1045
+ // update the token's active slot if it has changed
1046
+ if (_tokenAuxStateData[tokenNumber].activeSlot != updatedActiveSlot) {
1047
+ // @dev we already checked that the new active slot has/will have image metadata if updating the active slot
1048
+ // update value and emit PMPV0-indexable event for active slot update
1049
+ _tokenAuxStateData[tokenNumber].activeSlot = updatedActiveSlot
1050
+ .toUint8();
1051
+ emit IPMPV0.TokenParamsConfigured({
1052
+ coreContract: CORE_CONTRACT_ADDRESS,
1053
+ tokenId: tokenId,
1054
+ pmpInputs: _getPmpInputsForActiveSlotUpdate(updatedActiveSlot),
1055
+ authAddresses: _getSingleElementAddressArray(ownerAddress)
1056
+ });
1057
+ }
1058
+ }
1059
+
1060
+ /**
1061
+ * @notice Updates the send state for a given token.
1062
+ * Internal function - assumes token is valid
1063
+ * Assumes any interactions with send state and receive state are handled by the parent function.
1064
+ * @param tokenNumber The token number to update.
1065
+ * @param sendState The new send state. Valid values are SendGeneral, SendTo, Neutral.
1066
+ * @param tokensSendingTo Tokens to send this token to. Only non-empty iff sendState is SendTo.
1067
+ * @param ownerAddress The address of the token owner.
1068
+ */
1069
+ function _updateSendState(
1070
+ uint256 tokenNumber,
1071
+ SendStates sendState,
1072
+ uint16[] memory tokensSendingTo,
1073
+ address ownerAddress
1074
+ ) internal {
1075
+ // CHECKS
1076
+ // require that the token has image metadata at the active slot
1077
+ require(
1078
+ _tokensMetadata[tokenNumber][
1079
+ _tokenAuxStateData[tokenNumber].activeSlot
1080
+ ].imageDataAddress != address(0),
1081
+ "Token must have image metadata at active slot when particpating"
1082
+ );
1083
+ // enforce SendTo arrays length
1084
+ (sendState == SendStates.SendTo)
1085
+ ? require(
1086
+ tokensSendingTo.length > 0,
1087
+ "tokensSendingTo must be non-empty"
1088
+ )
1089
+ : require(
1090
+ tokensSendingTo.length == 0,
1091
+ "tokensSendingTo must be empty"
1092
+ );
1093
+ // enforce tokensSendingTo length is not too long
1094
+ require(
1095
+ tokensSendingTo.length <= MAX_SENDING_TO_LENGTH,
1096
+ "tokensSendingTo must be less than or equal to MAX_SENDING_TO_LENGTH"
1097
+ );
1098
+
1099
+ // EFFECTS
1100
+ uint256 tokenId = ABHelpers.tokenIdFromProjectIdAndTokenNumber({
1101
+ projectId: CORE_PROJECT_ID,
1102
+ tokenNumber: tokenNumber
1103
+ });
1104
+
1105
+ // Step 1. clear previous send state, based on storage's send state
1106
+ SendStates previousSendState = _getSendState(tokenNumber);
1107
+ if (previousSendState == SendStates.SendGeneral) {
1108
+ // simply remove the token from the send general set
1109
+ _sendGeneralTokens.remove(uint16(tokenNumber));
1110
+ } else if (previousSendState == SendStates.SendTo) {
1111
+ // pop from every previous token's "sending to me" set, which is a O(n) operation for n tokens previously sent to
1112
+ // @dev pull into memory for efficient sload minimization
1113
+ uint16[] memory previousTokensSendingTo = _tokensSendingTo[
1114
+ tokenNumber
1115
+ ].getAll();
1116
+ uint256 previousTokensSendingToLength = previousTokensSendingTo
1117
+ .length;
1118
+ for (uint256 i = 0; i < previousTokensSendingToLength; i++) {
1119
+ uint256 sendingToTokenNumber = previousTokensSendingTo[i];
1120
+ _tokensSendingToMe[sendingToTokenNumber].remove(
1121
+ uint16(tokenNumber)
1122
+ );
1123
+ }
1124
+ // clear my previous send to array
1125
+ _tokensSendingTo[tokenNumber].clear();
1126
+ // emit custom event for indexing of send-to if applicable
1127
+ // @dev acknowledge that this may cost gas for large arrays, but prefer strong indexing behavior, and
1128
+ // still minor relative to the cost of the SSTORE operations when storing the send-to array
1129
+ emit ISRHooks.TokenSendingToUpdated({
1130
+ coreContract: CORE_CONTRACT_ADDRESS,
1131
+ tokenId: tokenId,
1132
+ tokensSendingTo: new uint16[](0)
1133
+ });
1134
+ }
1135
+ // case: neutral state - no-op
1136
+
1137
+ // Step 2. populate the new send state
1138
+ if (sendState == SendStates.SendGeneral) {
1139
+ _sendGeneralTokens.add(uint16(tokenNumber));
1140
+ } else if (sendState == SendStates.SendTo) {
1141
+ // push the tokens to my send to array
1142
+ _tokensSendingTo[tokenNumber].store(tokensSendingTo);
1143
+ // push me into every token's "sending to me" set
1144
+ // @dev no validation of target tokens needed - if they don't exist or aren't participating,
1145
+ // they simply won't appear in getLiveData results due to state set membership checks
1146
+ uint256 tokensSendingToLength = tokensSendingTo.length;
1147
+ for (uint256 i = 0; i < tokensSendingToLength; i++) {
1148
+ uint256 sendingToTokenNumber = tokensSendingTo[i];
1149
+ // add the token to the "sending to me" Set
1150
+ _tokensSendingToMe[sendingToTokenNumber].add(
1151
+ uint16(tokenNumber)
1152
+ );
1153
+ }
1154
+ }
1155
+ // case: neutral state - no-op
1156
+
1157
+ // emit PMPV0-indexable event for send state update
1158
+ emit IPMPV0.TokenParamsConfigured({
1159
+ coreContract: CORE_CONTRACT_ADDRESS,
1160
+ tokenId: tokenId,
1161
+ pmpInputs: _getPmpInputsForSendStateUpdate(sendState),
1162
+ authAddresses: _getSingleElementAddressArray(ownerAddress)
1163
+ });
1164
+
1165
+ // emit custom event for indexing of send-to if applicable
1166
+ // @dev acknowledge that this may cost gas for large arrays, but prefer strong indexing behavior, and
1167
+ // still minor relative to the cost of the SSTORE operations when storing the send-to array
1168
+ if (sendState == SendStates.SendTo) {
1169
+ emit ISRHooks.TokenSendingToUpdated({
1170
+ coreContract: CORE_CONTRACT_ADDRESS,
1171
+ tokenId: tokenId,
1172
+ tokensSendingTo: tokensSendingTo
1173
+ });
1174
+ }
1175
+ }
1176
+
1177
+ /**
1178
+ * @notice Updates the receive state for a given token.
1179
+ * Internal function - assumes token is valid
1180
+ * Assumes any interactions with send state and receive state are handled by the parent function.
1181
+ * @param tokenNumber The token number to update.
1182
+ * @param receiveState The new receive state. Valid values are ReceiveGeneral, ReceiveFrom, Neutral.
1183
+ * @param tokensReceivingFrom Tokens this token is open to receive from. Only non-empty iff receiveState is ReceiveFrom.
1184
+ * @param ownerAddress The address of the token owner.
1185
+ */
1186
+ function _updateReceiveState(
1187
+ uint256 tokenNumber,
1188
+ ReceiveStates receiveState,
1189
+ uint16[] memory tokensReceivingFrom,
1190
+ address ownerAddress
1191
+ ) internal {
1192
+ // CHECKS
1193
+ // require that the token has image metadata at the active slot
1194
+ require(
1195
+ _tokensMetadata[tokenNumber][
1196
+ _tokenAuxStateData[tokenNumber].activeSlot
1197
+ ].imageDataAddress != address(0),
1198
+ "Token must have image metadata at active slot when particpating"
1199
+ );
1200
+ // enforce ReceiveFrom arrays length
1201
+ (receiveState == ReceiveStates.ReceiveFrom)
1202
+ ? require(
1203
+ tokensReceivingFrom.length > 0,
1204
+ "tokensReceivingFrom must be non-empty"
1205
+ )
1206
+ : require(
1207
+ tokensReceivingFrom.length == 0,
1208
+ "tokensReceivingFrom must be empty"
1209
+ );
1210
+ // enforce tokensReceivingFrom length is not too long
1211
+ require(
1212
+ tokensReceivingFrom.length <= MAX_RECEIVING_FROM_ARRAY_LENGTH,
1213
+ "tokensReceivingFrom must be less than or equal to MAX_RECEIVING_FROM_ARRAY_LENGTH"
1214
+ );
1215
+
1216
+ // EFFECTS
1217
+ uint256 tokenId = ABHelpers.tokenIdFromProjectIdAndTokenNumber({
1218
+ projectId: CORE_PROJECT_ID,
1219
+ tokenNumber: tokenNumber
1220
+ });
1221
+
1222
+ // Step 1. clear previous receive state, based on storage's receive state
1223
+ ReceiveStates previousReceiveState = _getReceiveState(tokenNumber);
1224
+ if (previousReceiveState == ReceiveStates.ReceiveGeneral) {
1225
+ // simply remove the token from the receive general set
1226
+ _receiveGeneralTokens.remove(uint16(tokenNumber));
1227
+ } else if (previousReceiveState == ReceiveStates.ReceiveFrom) {
1228
+ // simple removal of my receive from array - we don't have a reverse mapping of the array across other tokens
1229
+ _tokensReceivingFrom[tokenNumber].clear();
1230
+
1231
+ // emit custom event for indexing of receive-from if applicable
1232
+ // @dev acknowledge that this may cost gas for large arrays, but prefer strong indexing behavior, and
1233
+ // still minor relative to the cost of the SSTORE operations when storing the receive-from array
1234
+ emit ISRHooks.TokenReceivingFromUpdated({
1235
+ coreContract: CORE_CONTRACT_ADDRESS,
1236
+ tokenId: tokenId,
1237
+ tokensReceivingFrom: new uint16[](0)
1238
+ });
1239
+ } else if (previousReceiveState == ReceiveStates.ReceiveTo) {
1240
+ // set isReceivingTo to false
1241
+ _tokenAuxStateData[tokenNumber].isReceivingTo = false;
1242
+ }
1243
+ // case: neutral state - no-op
1244
+
1245
+ // Step 2. populate the new receive state
1246
+ if (receiveState == ReceiveStates.ReceiveGeneral) {
1247
+ _receiveGeneralTokens.add(uint16(tokenNumber));
1248
+ } else if (receiveState == ReceiveStates.ReceiveFrom) {
1249
+ // @dev no validation of target tokens needed - if they don't exist or aren't participating,
1250
+ // they simply won't appear in getLiveData results due to state set membership checks
1251
+ // (tokens must be in _sendGeneralTokens or _tokensSendingToMe to be included)
1252
+ _tokensReceivingFrom[tokenNumber].store(tokensReceivingFrom);
1253
+ } else if (receiveState == ReceiveStates.ReceiveTo) {
1254
+ // set isReceivingTo to true
1255
+ _tokenAuxStateData[tokenNumber].isReceivingTo = true;
1256
+ }
1257
+ // case: neutral state - no-op
1258
+
1259
+ // emit PMPV0-indexable event for receive state update
1260
+ emit IPMPV0.TokenParamsConfigured({
1261
+ coreContract: CORE_CONTRACT_ADDRESS,
1262
+ tokenId: tokenId,
1263
+ pmpInputs: _getPmpInputsForReceiveStateUpdate(receiveState),
1264
+ authAddresses: _getSingleElementAddressArray(ownerAddress)
1265
+ });
1266
+
1267
+ // emit custom event for indexing of receive-from if applicable
1268
+ // @dev acknowledge that this may cost gas for large arrays, but prefer strong indexing behavior, and
1269
+ // still minor relative to the cost of the SSTORE operations when storing the receive-from array
1270
+ if (receiveState == ReceiveStates.ReceiveFrom) {
1271
+ emit ISRHooks.TokenReceivingFromUpdated({
1272
+ coreContract: CORE_CONTRACT_ADDRESS,
1273
+ tokenId: tokenId,
1274
+ tokensReceivingFrom: tokensReceivingFrom
1275
+ });
1276
+ }
1277
+ }
1278
+
1279
+ /**
1280
+ * @notice Gets the state of the general pools.
1281
+ * @return sendGeneralTokensLength The length of the send general tokens.
1282
+ * @return receiveGeneralTokensLength The length of the receive general tokens.
1283
+ */
1284
+ function getGeneralPoolState()
1285
+ external
1286
+ view
1287
+ returns (
1288
+ uint256 sendGeneralTokensLength,
1289
+ uint256 receiveGeneralTokensLength
1290
+ )
1291
+ {
1292
+ return (_sendGeneralTokens.length(), _receiveGeneralTokens.length());
1293
+ }
1294
+
1295
+ /**
1296
+ * @notice Gets the state of a given token.
1297
+ * @param tokenNumber The token number to get the state for.
1298
+ * @return sendState The send state.
1299
+ * @return receiveState The receive state.
1300
+ * @return tokensSendingTo The tokens sending to the token.
1301
+ * @return tokensReceivingFrom The tokens receiving from the token.
1302
+ * @return activeSlot The active slot.
1303
+ * @return activeSlotTokenMetadata The token metadata at the active slot.
1304
+ * @return ownerAddress The address of the token owner.
1305
+ */
1306
+ function getTokenState(
1307
+ uint256 tokenNumber
1308
+ )
1309
+ external
1310
+ view
1311
+ returns (
1312
+ SendStates sendState,
1313
+ ReceiveStates receiveState,
1314
+ uint16[] memory tokensSendingTo,
1315
+ uint16[] memory tokensReceivingFrom,
1316
+ uint256 activeSlot,
1317
+ TokenMetadataView memory activeSlotTokenMetadata,
1318
+ address ownerAddress
1319
+ )
1320
+ {
1321
+ // require token number is valid uint16 using SafeCast
1322
+ tokenNumber.toUint16(); // will revert if tokenNumber > type(uint16).max
1323
+
1324
+ sendState = _getSendState(tokenNumber);
1325
+ receiveState = _getReceiveState(tokenNumber);
1326
+ tokensSendingTo = ImmutableUint16Array.getAll({
1327
+ storageArray: _tokensSendingTo[tokenNumber]
1328
+ });
1329
+ tokensReceivingFrom = ImmutableUint16Array.getAll({
1330
+ storageArray: _tokensReceivingFrom[tokenNumber]
1331
+ });
1332
+ activeSlot = _tokenAuxStateData[tokenNumber].activeSlot;
1333
+ TokenMetadata storage tokenMetadataStorage = _tokensMetadata[
1334
+ tokenNumber
1335
+ ][activeSlot];
1336
+ activeSlotTokenMetadata.imageDataCompressed = tokenMetadataStorage
1337
+ .imageDataAddress != address(0)
1338
+ ? SSTORE2.read(tokenMetadataStorage.imageDataAddress)
1339
+ : bytes("");
1340
+ activeSlotTokenMetadata.imageVersion = tokenMetadataStorage
1341
+ .imageVersion;
1342
+ activeSlotTokenMetadata.soundDataCompressed = tokenMetadataStorage
1343
+ .soundDataAddress != address(0)
1344
+ ? SSTORE2.read(tokenMetadataStorage.soundDataAddress)
1345
+ : bytes("");
1346
+ activeSlotTokenMetadata.soundVersion = tokenMetadataStorage
1347
+ .soundVersion;
1348
+ uint256 tokenId = ABHelpers.tokenIdFromProjectIdAndTokenNumber({
1349
+ projectId: CORE_PROJECT_ID,
1350
+ tokenNumber: tokenNumber
1351
+ });
1352
+ ownerAddress = IERC721(CORE_CONTRACT_ADDRESS).ownerOf(tokenId);
1353
+ return (
1354
+ sendState,
1355
+ receiveState,
1356
+ tokensSendingTo,
1357
+ tokensReceivingFrom,
1358
+ activeSlot,
1359
+ activeSlotTokenMetadata,
1360
+ ownerAddress
1361
+ );
1362
+ }
1363
+
1364
+ /**
1365
+ * @notice Gets the token metadata at a given slot.
1366
+ * @param tokenNumber The token number to get the token metadata for.
1367
+ * @param slot The slot to get the token metadata for.
1368
+ * @return tokenMetadata The token metadata at the given slot.
1369
+ */
1370
+ function getTokenMetadataAtSlot(
1371
+ uint256 tokenNumber,
1372
+ uint256 slot
1373
+ ) external view returns (TokenMetadataView memory tokenMetadata) {
1374
+ // require token number is valid uint16 using SafeCast
1375
+ tokenNumber.toUint16(); // will revert if tokenNumber > type(uint16).max
1376
+ // require slot is valid
1377
+ require(slot < NUM_METADATA_SLOTS, "Invalid slot");
1378
+
1379
+ TokenMetadata storage tokenMetadataStorage = _tokensMetadata[
1380
+ tokenNumber
1381
+ ][slot];
1382
+ tokenMetadata.imageDataCompressed = tokenMetadataStorage
1383
+ .imageDataAddress != address(0)
1384
+ ? SSTORE2.read(tokenMetadataStorage.imageDataAddress)
1385
+ : bytes("");
1386
+ tokenMetadata.imageVersion = tokenMetadataStorage.imageVersion;
1387
+ tokenMetadata.soundDataCompressed = tokenMetadataStorage
1388
+ .soundDataAddress != address(0)
1389
+ ? SSTORE2.read(tokenMetadataStorage.soundDataAddress)
1390
+ : bytes("");
1391
+ tokenMetadata.soundVersion = tokenMetadataStorage.soundVersion;
1392
+ }
1393
+
1394
+ /**
1395
+ * @notice Gets the tokens sending to a given token.
1396
+ * WARNING: This function is unbounded in gas cost, and is designed to be used by view accessors only. Use with caution.
1397
+ * @param tokenNumber The token number to get the tokens sending to for.
1398
+ * @return tokensSendingTo The token numbers sending to the given token.
1399
+ */
1400
+ function getTokensSendingToToken(
1401
+ uint256 tokenNumber
1402
+ ) external view returns (uint256[] memory tokensSendingTo) {
1403
+ // require token number is valid uint16 using SafeCast
1404
+ tokenNumber.toUint16(); // will revert if tokenNumber > type(uint16).max
1405
+
1406
+ return _tokensSendingToMe[tokenNumber].values();
1407
+ }
1408
+
1409
+ /**
1410
+ * @notice Gets the send state for a given token.
1411
+ * Internal function - assumes token is valid
1412
+ * @dev uses derived state to prefer SLOAD over SSTORE for efficiency
1413
+ * @param tokenNumber The token number to get the send state for.
1414
+ * @return sendState The send state.
1415
+ */
1416
+ function _getSendState(
1417
+ uint256 tokenNumber
1418
+ ) internal view returns (SendStates) {
1419
+ // check for existence in send general set
1420
+ if (_sendGeneralTokens.contains(uint16(tokenNumber))) {
1421
+ return SendStates.SendGeneral;
1422
+ }
1423
+ // check for non-empty send to array
1424
+ if (!ImmutableUint16Array.isEmpty(_tokensSendingTo[tokenNumber])) {
1425
+ return SendStates.SendTo;
1426
+ }
1427
+ // must be in neutral state
1428
+ return SendStates.Neutral;
1429
+ }
1430
+
1431
+ /**
1432
+ * @notice Converts a send state to a string.
1433
+ * Internal function - assumes send state is valid
1434
+ * @param sendState The send state to convert to a string.
1435
+ * @return string The string representation of the send state.
1436
+ */
1437
+ function _sendStateToString(
1438
+ SendStates sendState
1439
+ ) internal pure returns (string memory) {
1440
+ return
1441
+ sendState == SendStates.SendGeneral
1442
+ ? "SendGeneral"
1443
+ : sendState == SendStates.SendTo
1444
+ ? "SendTo"
1445
+ : "Neutral";
1446
+ }
1447
+
1448
+ /**
1449
+ * @notice Gets the receive state for a given token.
1450
+ * Internal function - assumes token is valid
1451
+ * @dev uses derived state to prefer SLOAD over SSTORE for efficiency
1452
+ * @param tokenNumber The token number to get the receive state for.
1453
+ * @return receiveState The receive state.
1454
+ */
1455
+ function _getReceiveState(
1456
+ uint256 tokenNumber
1457
+ ) internal view returns (ReceiveStates) {
1458
+ // check for existence in receive general set
1459
+ if (_receiveGeneralTokens.contains(uint16(tokenNumber))) {
1460
+ return ReceiveStates.ReceiveGeneral;
1461
+ }
1462
+ // check for non-empty receive from array
1463
+ if (!ImmutableUint16Array.isEmpty(_tokensReceivingFrom[tokenNumber])) {
1464
+ return ReceiveStates.ReceiveFrom;
1465
+ }
1466
+ // check for isReceivingTo
1467
+ if (_tokenAuxStateData[tokenNumber].isReceivingTo) {
1468
+ return ReceiveStates.ReceiveTo;
1469
+ }
1470
+ // must be in neutral state
1471
+ return ReceiveStates.Neutral;
1472
+ }
1473
+
1474
+ /**
1475
+ * @notice Converts a receive state to a string.
1476
+ * Internal function - assumes receive state is valid
1477
+ * @param receiveState The receive state to convert to a string.
1478
+ * @return string The string representation of the receive state.
1479
+ */
1480
+ function _receiveStateToString(
1481
+ ReceiveStates receiveState
1482
+ ) internal pure returns (string memory) {
1483
+ if (receiveState == ReceiveStates.Neutral) {
1484
+ return "Neutral";
1485
+ }
1486
+ if (receiveState == ReceiveStates.ReceiveGeneral) {
1487
+ return "ReceiveGeneral";
1488
+ }
1489
+ if (receiveState == ReceiveStates.ReceiveFrom) {
1490
+ return "ReceiveFrom";
1491
+ }
1492
+ if (receiveState == ReceiveStates.ReceiveTo) {
1493
+ return "ReceiveTo";
1494
+ }
1495
+ // case: invalid receive state (no coverage)
1496
+ revert("Invalid receive state");
1497
+ }
1498
+
1499
+ function _getHexStringFromSSTORE2(
1500
+ address sstore2Address
1501
+ ) internal view returns (string memory) {
1502
+ // case: empty sstore2 address
1503
+ if (sstore2Address == address(0)) {
1504
+ return "";
1505
+ }
1506
+ // case: non-empty sstore2 address
1507
+ return toHexString(SSTORE2.read(sstore2Address));
1508
+ }
1509
+
1510
+ /**
1511
+ * @notice Checks if the contract supports an interface.
1512
+ * @dev This function is required by the ERC165 interface detection pattern.
1513
+ * @param interfaceId The interface identifier to check.
1514
+ * @return bool True if the contract supports the interface, false otherwise.
1515
+ */
1516
+ function supportsInterface(
1517
+ bytes4 interfaceId
1518
+ ) public view override(AbstractPMPAugmentHook) returns (bool) {
1519
+ return
1520
+ interfaceId == type(IPMPAugmentHook).interfaceId ||
1521
+ super.supportsInterface(interfaceId);
1522
+ }
1523
+
1524
+ /**
1525
+ * @notice Gets the PMP inputs for the receive state update.
1526
+ * @param receiveState The receive state to update.
1527
+ * @return pmpInputs The PMP inputs for the receive state update.
1528
+ * @dev This function is used to get the PMP inputs for the receive state update, based on what this hook uses.
1529
+ */
1530
+ function _getPmpInputsForReceiveStateUpdate(
1531
+ ReceiveStates receiveState
1532
+ ) internal pure returns (IPMPV0.PMPInput[] memory) {
1533
+ // build PMPInputs for the receive state update, based on what this hook uses
1534
+ IPMPV0.PMPInput[] memory pmpInputs = new IPMPV0.PMPInput[](1);
1535
+ pmpInputs[0] = IPMPV0.PMPInput({
1536
+ key: "ReceiveState",
1537
+ configuredParamType: IPMPV0.ParamType.Select,
1538
+ configuredValue: bytes32(uint256(receiveState)), // @dev enums are aligned with select options
1539
+ configuringArtistString: false,
1540
+ configuredValueString: ""
1541
+ });
1542
+ return pmpInputs;
1543
+ }
1544
+
1545
+ /**
1546
+ * @notice Gets the PMP inputs for the send state update.
1547
+ * @param sendState The send state to update.
1548
+ * @return pmpInputs The PMP inputs for the send state update.
1549
+ * @dev This function is used to get the PMP inputs for the send state update, based on what this hook uses.
1550
+ */
1551
+ function _getPmpInputsForSendStateUpdate(
1552
+ SendStates sendState
1553
+ ) internal pure returns (IPMPV0.PMPInput[] memory) {
1554
+ // build PMPInputs for the send state update, based on what this hook uses
1555
+ IPMPV0.PMPInput[] memory pmpInputs = new IPMPV0.PMPInput[](1);
1556
+ pmpInputs[0] = IPMPV0.PMPInput({
1557
+ key: "SendState",
1558
+ configuredParamType: IPMPV0.ParamType.Select,
1559
+ configuredValue: bytes32(uint256(sendState)), // @dev enums are aligned with select options
1560
+ configuringArtistString: false,
1561
+ configuredValueString: ""
1562
+ });
1563
+ return pmpInputs;
1564
+ }
1565
+
1566
+ /**
1567
+ * @notice Gets the PMP inputs for the active slot update.
1568
+ * @param activeSlot The active slot to update.
1569
+ * @return pmpInputs The PMP inputs for the active slot update.
1570
+ * @dev This function is used to get the PMP inputs for the active slot update, based on what this hook uses.
1571
+ */
1572
+ function _getPmpInputsForActiveSlotUpdate(
1573
+ uint256 activeSlot
1574
+ ) internal pure returns (IPMPV0.PMPInput[] memory) {
1575
+ IPMPV0.PMPInput[] memory pmpInputs = new IPMPV0.PMPInput[](1);
1576
+ pmpInputs[0] = IPMPV0.PMPInput({
1577
+ key: "ActiveSlot",
1578
+ configuredParamType: IPMPV0.ParamType.Uint256Range,
1579
+ configuredValue: bytes32(uint256(activeSlot)),
1580
+ configuringArtistString: false,
1581
+ configuredValueString: ""
1582
+ });
1583
+ return pmpInputs;
1584
+ }
1585
+
1586
+ /**
1587
+ * @notice Gets the PMP inputs for the image data update.
1588
+ * @param slot The slot to update.
1589
+ * @param imageVersion The image version to update.
1590
+ * @return pmpInputs The PMP inputs for the image data update.
1591
+ * @dev This function is used to get the PMP inputs for the image data update, based on what this hook uses.
1592
+ */
1593
+ function _getPmpInputsForImageDataUpdate(
1594
+ uint256 slot,
1595
+ uint16 imageVersion
1596
+ ) internal pure returns (IPMPV0.PMPInput[] memory) {
1597
+ string memory key = string(
1598
+ bytes.concat(
1599
+ bytes("ImageVersionSlot"),
1600
+ bytes(Strings.toString(slot))
1601
+ )
1602
+ );
1603
+ // build PMPInputs for the image data update, based on what this hook uses
1604
+ IPMPV0.PMPInput[] memory pmpInputs = new IPMPV0.PMPInput[](1);
1605
+ pmpInputs[0] = IPMPV0.PMPInput({
1606
+ key: key,
1607
+ configuredParamType: IPMPV0.ParamType.Uint256Range,
1608
+ configuredValue: bytes32(uint256(imageVersion)),
1609
+ configuringArtistString: false,
1610
+ configuredValueString: ""
1611
+ });
1612
+ return pmpInputs;
1613
+ }
1614
+
1615
+ /**
1616
+ * @notice Gets the PMP inputs for the sound data update.
1617
+ * @param slot The slot to update.
1618
+ * @param soundVersion The sound version to update.
1619
+ * @return pmpInputs The PMP inputs for the sound data update.
1620
+ * @dev This function is used to get the PMP inputs for the sound data update, based on what this hook uses.
1621
+ */
1622
+ function _getPmpInputsForSoundDataUpdate(
1623
+ uint256 slot,
1624
+ uint16 soundVersion
1625
+ ) internal pure returns (IPMPV0.PMPInput[] memory) {
1626
+ string memory key = string(
1627
+ bytes.concat(
1628
+ bytes("SoundVersionSlot"),
1629
+ bytes(Strings.toString(slot))
1630
+ )
1631
+ );
1632
+ // build PMPInputs for the sound data update, based on what this hook uses
1633
+ IPMPV0.PMPInput[] memory pmpInputs = new IPMPV0.PMPInput[](1);
1634
+ pmpInputs[0] = IPMPV0.PMPInput({
1635
+ key: key,
1636
+ configuredParamType: IPMPV0.ParamType.Uint256Range,
1637
+ configuredValue: bytes32(uint256(soundVersion)),
1638
+ configuringArtistString: false,
1639
+ configuredValueString: ""
1640
+ });
1641
+ return pmpInputs;
1642
+ }
1643
+
1644
+ /**
1645
+ * @notice Gets a single element address array.
1646
+ * @param address_ The address to include in the array.
1647
+ * @return addressArray The single element address array containing the address.
1648
+ */
1649
+ function _getSingleElementAddressArray(
1650
+ address address_
1651
+ ) internal pure returns (address[] memory) {
1652
+ address[] memory addressArray = new address[](1);
1653
+ addressArray[0] = address_;
1654
+ return addressArray;
1655
+ }
1656
+
1657
+ /**
1658
+ * @notice Authorizes an upgrade to a new implementation.
1659
+ * @dev This function is required by the UUPS pattern and can only be called by the owner.
1660
+ *
1661
+ * UPGRADE SAFETY REQUIREMENTS:
1662
+ * - All existing state variables MUST remain in their current storage slots
1663
+ * - New state variables MUST be appended to the end (never inserted between existing variables)
1664
+ * - Never modify the type or order of existing state variables
1665
+ * - Never remove existing state variables (they can be deprecated but must remain)
1666
+ * - The storage layout must be append-only to maintain compatibility
1667
+ *
1668
+ * @param newImplementation The address of the new implementation contract.
1669
+ */
1670
+ function _authorizeUpgrade(
1671
+ address newImplementation
1672
+ ) internal override onlyOwner {
1673
+ // this version allows the owner to upgrade the contract to a new implementation
1674
+ // in future versions, we may choose to disable this functionality and lock project functionality permanently
1675
+ }
1676
+
1677
+ // --- HELPER FUNCTIONS FROM SOLADY https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol ---
1678
+
1679
+ /// @dev Returns the hex encoded string from the raw bytes.
1680
+ /// The output is encoded using 2 hexadecimal digits per byte.
1681
+ function toHexString(
1682
+ bytes memory raw
1683
+ ) internal pure returns (string memory result) {
1684
+ result = toHexStringNoPrefix(raw);
1685
+ /// @solidity memory-safe-assembly
1686
+ assembly {
1687
+ let n := add(mload(result), 2) // Compute the length.
1688
+ mstore(result, 0x3078) // Store the "0x" prefix.
1689
+ result := sub(result, 2) // Move the pointer.
1690
+ mstore(result, n) // Store the length.
1691
+ }
1692
+ }
1693
+
1694
+ /// @dev Returns the hex encoded string from the raw bytes.
1695
+ /// The output is encoded using 2 hexadecimal digits per byte.
1696
+ function toHexStringNoPrefix(
1697
+ bytes memory raw
1698
+ ) internal pure returns (string memory result) {
1699
+ /// @solidity memory-safe-assembly
1700
+ assembly {
1701
+ let n := mload(raw)
1702
+ result := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix.
1703
+ mstore(result, add(n, n)) // Store the length of the output.
1704
+
1705
+ mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
1706
+ let o := add(result, 0x20)
1707
+ let end := add(raw, n)
1708
+ for {
1709
+
1710
+ } iszero(eq(raw, end)) {
1711
+
1712
+ } {
1713
+ raw := add(raw, 1)
1714
+ mstore8(add(o, 1), mload(and(mload(raw), 15)))
1715
+ mstore8(o, mload(and(shr(4, mload(raw)), 15)))
1716
+ o := add(o, 2)
1717
+ }
1718
+ mstore(o, 0) // Zeroize the slot after the string.
1719
+ mstore(0x40, add(o, 0x20)) // Allocate memory.
1720
+ }
1721
+ }
1722
+ }