@@ -3,13 +3,17 @@ pragma solidity 0.8.24;
33
44import "@openzeppelin/contracts/utils/Create2.sol " ;
55import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol " ;
6+ import {LibString} from "solady/utils/LibString.sol " ;
67import "./ERC721EthscriptionsCollection.sol " ;
78import "./libraries/Proxy.sol " ;
9+ import "./libraries/DedupedBlobStore.sol " ;
810import "./Ethscriptions.sol " ;
911import "./libraries/Predeploys.sol " ;
1012import "./interfaces/IProtocolHandler.sol " ;
1113
1214contract ERC721EthscriptionsCollectionManager is IProtocolHandler {
15+ using LibString for * ;
16+
1317 struct Attribute {
1418 string traitType;
1519 string value;
@@ -30,6 +34,23 @@ contract ERC721EthscriptionsCollectionManager is IProtocolHandler {
3034 }
3135
3236 struct CollectionRecord {
37+ address collectionContract;
38+ bool locked;
39+ bytes32 nameRef; // DedupedBlobStore reference
40+ bytes32 symbolRef; // DedupedBlobStore reference
41+ uint256 maxSupply;
42+ bytes32 descriptionRef; // DedupedBlobStore reference
43+ bytes32 logoImageRef; // DedupedBlobStore reference
44+ bytes32 bannerImageRef; // DedupedBlobStore reference
45+ bytes32 backgroundColorRef; // DedupedBlobStore reference
46+ bytes32 websiteLinkRef; // DedupedBlobStore reference
47+ bytes32 twitterLinkRef; // DedupedBlobStore reference
48+ bytes32 discordLinkRef; // DedupedBlobStore reference
49+ bytes32 merkleRoot;
50+ }
51+
52+ /// @notice View struct for external consumption with decoded strings
53+ struct CollectionMetadata {
3354 address collectionContract;
3455 bool locked;
3556 string name;
@@ -108,10 +129,13 @@ contract ERC721EthscriptionsCollectionManager is IProtocolHandler {
108129 Ethscriptions public constant ethscriptions = Ethscriptions (Predeploys.ETHSCRIPTIONS);
109130 string public constant protocolName = "erc-721-ethscriptions-collection " ;
110131
111- mapping (bytes32 => CollectionRecord) private collectionStore;
112- mapping (bytes32 => mapping (uint256 => CollectionItem)) public collectionItems;
132+ mapping (bytes32 => CollectionRecord) internal collectionStore;
133+ mapping (bytes32 => mapping (uint256 => CollectionItem)) internal collectionItems;
113134 mapping (bytes32 => Membership) public membershipOfEthscription;
114- mapping (address => bytes32 ) private collectionAddressToId;
135+ mapping (address => bytes32 ) internal collectionAddressToId;
136+
137+ /// @dev Deduplicated storage for collection string fields (name, description, URIs, etc.)
138+ mapping (bytes32 => bytes32 ) internal collectionBlobStorage;
115139
116140 bytes32 [] public collectionIds;
117141
@@ -195,13 +219,14 @@ contract ERC721EthscriptionsCollectionManager is IProtocolHandler {
195219 require (! collection.locked, "Collection is locked " );
196220 _requireCollectionOwner (ethscriptionId, editOp.collectionId, "Only collection owner can edit " );
197221
198- if (bytes (editOp.description).length > 0 ) collection.description = editOp.description;
199- if (bytes (editOp.logoImageUri).length > 0 ) collection.logoImageUri = editOp.logoImageUri;
200- if (bytes (editOp.bannerImageUri).length > 0 ) collection.bannerImageUri = editOp.bannerImageUri;
201- if (bytes (editOp.backgroundColor).length > 0 ) collection.backgroundColor = editOp.backgroundColor;
202- if (bytes (editOp.websiteLink).length > 0 ) collection.websiteLink = editOp.websiteLink;
203- if (bytes (editOp.twitterLink).length > 0 ) collection.twitterLink = editOp.twitterLink;
204- if (bytes (editOp.discordLink).length > 0 ) collection.discordLink = editOp.discordLink;
222+ // Update fields (empty strings allowed to clear fields)
223+ (, collection.descriptionRef) = DedupedBlobStore.storeMemory (bytes (editOp.description), collectionBlobStorage);
224+ (, collection.logoImageRef) = DedupedBlobStore.storeMemory (bytes (editOp.logoImageUri), collectionBlobStorage);
225+ (, collection.bannerImageRef) = DedupedBlobStore.storeMemory (bytes (editOp.bannerImageUri), collectionBlobStorage);
226+ (, collection.backgroundColorRef) = DedupedBlobStore.storeMemory (bytes (editOp.backgroundColor), collectionBlobStorage);
227+ (, collection.websiteLinkRef) = DedupedBlobStore.storeMemory (bytes (editOp.websiteLink), collectionBlobStorage);
228+ (, collection.twitterLinkRef) = DedupedBlobStore.storeMemory (bytes (editOp.twitterLink), collectionBlobStorage);
229+ (, collection.discordLinkRef) = DedupedBlobStore.storeMemory (bytes (editOp.discordLink), collectionBlobStorage);
205230 collection.merkleRoot = editOp.merkleRoot;
206231
207232 emit CollectionEdited (editOp.collectionId);
@@ -263,8 +288,25 @@ contract ERC721EthscriptionsCollectionManager is IProtocolHandler {
263288 return collectionStore[collectionId].collectionContract;
264289 }
265290
266- function getCollection (bytes32 collectionId ) external view returns (CollectionRecord memory ) {
267- return collectionStore[collectionId];
291+ function getCollection (bytes32 collectionId ) public view returns (CollectionMetadata memory ) {
292+ CollectionRecord storage record = collectionStore[collectionId];
293+ require (record.collectionContract != address (0 ), "Collection does not exist " );
294+
295+ return CollectionMetadata ({
296+ collectionContract: record.collectionContract,
297+ locked: record.locked,
298+ name: DedupedBlobStore.readString (record.nameRef),
299+ symbol: DedupedBlobStore.readString (record.symbolRef),
300+ maxSupply: record.maxSupply,
301+ description: DedupedBlobStore.readString (record.descriptionRef),
302+ logoImageUri: DedupedBlobStore.readString (record.logoImageRef),
303+ bannerImageUri: DedupedBlobStore.readString (record.bannerImageRef),
304+ backgroundColor: DedupedBlobStore.readString (record.backgroundColorRef),
305+ websiteLink: DedupedBlobStore.readString (record.websiteLinkRef),
306+ twitterLink: DedupedBlobStore.readString (record.twitterLinkRef),
307+ discordLink: DedupedBlobStore.readString (record.discordLinkRef),
308+ merkleRoot: record.merkleRoot
309+ });
268310 }
269311
270312 function getCollectionItem (bytes32 collectionId , uint256 itemIndex ) external view returns (CollectionItem memory ) {
@@ -300,29 +342,40 @@ contract ERC721EthscriptionsCollectionManager is IProtocolHandler {
300342 require (! collectionExists (collectionId), "Collection already exists " );
301343
302344 Proxy collectionProxy = new Proxy {salt: collectionId}(address (this ));
303- bytes memory initCalldata = abi.encodeWithSelector (
345+
346+ collectionProxy.upgradeToAndCall (collectionsImplementation, abi.encodeWithSelector (
304347 ERC721EthscriptionsCollection .initialize.selector ,
305348 metadata.name,
306349 metadata.symbol,
307350 ethscriptions.ownerOf (collectionId)
308- );
351+ )) ;
309352
310- collectionProxy.upgradeToAndCall (collectionsImplementation, initCalldata);
311353 collectionProxy.changeAdmin (Predeploys.PROXY_ADMIN);
312354
355+ // Store string fields using DedupedBlobStore
356+ (, bytes32 nameRef ) = DedupedBlobStore.storeMemory (bytes (metadata.name), collectionBlobStorage);
357+ (, bytes32 symbolRef ) = DedupedBlobStore.storeMemory (bytes (metadata.symbol), collectionBlobStorage);
358+ (, bytes32 descriptionRef ) = DedupedBlobStore.storeMemory (bytes (metadata.description), collectionBlobStorage);
359+ (, bytes32 logoImageRef ) = DedupedBlobStore.storeMemory (bytes (metadata.logoImageUri), collectionBlobStorage);
360+ (, bytes32 bannerImageRef ) = DedupedBlobStore.storeMemory (bytes (metadata.bannerImageUri), collectionBlobStorage);
361+ (, bytes32 backgroundColorRef ) = DedupedBlobStore.storeMemory (bytes (metadata.backgroundColor), collectionBlobStorage);
362+ (, bytes32 websiteLinkRef ) = DedupedBlobStore.storeMemory (bytes (metadata.websiteLink), collectionBlobStorage);
363+ (, bytes32 twitterLinkRef ) = DedupedBlobStore.storeMemory (bytes (metadata.twitterLink), collectionBlobStorage);
364+ (, bytes32 discordLinkRef ) = DedupedBlobStore.storeMemory (bytes (metadata.discordLink), collectionBlobStorage);
365+
313366 collectionStore[collectionId] = CollectionRecord ({
314367 collectionContract: address (collectionProxy),
315368 locked: false ,
316- name: metadata.name ,
317- symbol: metadata.symbol ,
369+ nameRef: nameRef ,
370+ symbolRef: symbolRef ,
318371 maxSupply: metadata.maxSupply,
319- description: metadata.description ,
320- logoImageUri: metadata.logoImageUri ,
321- bannerImageUri: metadata.bannerImageUri ,
322- backgroundColor: metadata.backgroundColor ,
323- websiteLink: metadata.websiteLink ,
324- twitterLink: metadata.twitterLink ,
325- discordLink: metadata.discordLink ,
372+ descriptionRef: descriptionRef ,
373+ logoImageRef: logoImageRef ,
374+ bannerImageRef: bannerImageRef ,
375+ backgroundColorRef: backgroundColorRef ,
376+ websiteLinkRef: websiteLinkRef ,
377+ twitterLinkRef: twitterLinkRef ,
378+ discordLinkRef: discordLinkRef ,
326379 merkleRoot: metadata.merkleRoot
327380 });
328381
@@ -406,4 +459,18 @@ contract ERC721EthscriptionsCollectionManager is IProtocolHandler {
406459 function _inImportMode () private view returns (bool ) {
407460 return block .timestamp < Constants.historicalBackfillApproxDoneAt;
408461 }
462+
463+ function getMembershipOfEthscription (bytes32 ethscriptionId ) external view returns (Membership memory ) {
464+ return membershipOfEthscription[ethscriptionId];
465+ }
466+
467+ /// @notice Get collection metadata by address
468+ /// @param collectionAddress The collection contract address
469+ /// @return metadata The collection metadata with decoded strings
470+ function getCollectionByAddress (address collectionAddress ) external view returns (CollectionMetadata memory ) {
471+ bytes32 collectionId = collectionAddressToId[collectionAddress];
472+ require (collectionId != bytes32 (0 ), "Collection not found " );
473+ return getCollection (collectionId);
474+ }
409475}
476+
0 commit comments