Skip to content

Commit 4dbd5c9

Browse files
Merge pull request #133 from ethscriptions-protocol/improve_reading
Refactor Ethscription structure and retrieval methods
2 parents 9bcb6e3 + 7b46837 commit 4dbd5c9

File tree

8 files changed

+368
-171
lines changed

8 files changed

+368
-171
lines changed

contracts/script/L2Genesis.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ contract GenesisEthscriptions is Ethscriptions {
4343
firstEthscriptionByContentUri[params.contentUriHash] = params.ethscriptionId;
4444

4545
// Set all values including genesis-specific ones
46-
ethscriptions[params.ethscriptionId] = Ethscription({
46+
ethscriptions[params.ethscriptionId] = EthscriptionStorage({
4747
// Fixed-size fields
4848
contentUriHash: params.contentUriHash,
4949
contentSha: contentSha,

contracts/src/Ethscriptions.sol

Lines changed: 120 additions & 54 deletions
Large diffs are not rendered by default.

contracts/src/EthscriptionsProver.sol

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,10 @@ contract EthscriptionsProver {
136136
/// @param ethscriptionId The Ethscription ID (L1 tx hash)
137137
/// @param proofInfo The queued proof info containing block data
138138
function _createAndSendProof(bytes32 ethscriptionId, QueuedProof memory proofInfo) internal {
139-
// Get ethscription data including previous owner
140-
Ethscriptions.Ethscription memory ethscription = ethscriptions.getEthscription(ethscriptionId);
141-
address currentOwner = ethscriptions.ownerOf(ethscriptionId);
139+
// Get ethscription data including previous owner (without content for gas efficiency)
140+
Ethscriptions.Ethscription memory ethscription = ethscriptions.getEthscription(ethscriptionId, false);
141+
// currentOwner is already in the struct now
142+
address currentOwner = ethscription.currentOwner;
142143

143144
// Create proof struct with all ethscription data
144145
EthscriptionDataProof memory proof = EthscriptionDataProof({
@@ -150,7 +151,7 @@ contract EthscriptionsProver {
150151
currentOwner: currentOwner,
151152
previousOwner: ethscription.previousOwner,
152153
esip6: ethscription.esip6,
153-
ethscriptionNumber: ethscription.ethscriptionNumber,
154+
ethscriptionNumber: uint48(ethscription.ethscriptionNumber),
154155
l1BlockNumber: proofInfo.l1BlockNumber,
155156
l2BlockNumber: proofInfo.l2BlockNumber,
156157
l2Timestamp: proofInfo.l2BlockTimestamp

contracts/src/libraries/EthscriptionsRendererLib.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ library EthscriptionsRendererLib {
1515
/// @param etsc Storage pointer to the ethscription
1616
/// @param ethscriptionId The ethscription ID (L1 tx hash)
1717
/// @return JSON string of attributes array
18-
function buildAttributes(Ethscriptions.Ethscription storage etsc, bytes32 ethscriptionId)
18+
function buildAttributes(Ethscriptions.EthscriptionStorage storage etsc, bytes32 ethscriptionId)
1919
internal
2020
view
2121
returns (string memory)
@@ -56,7 +56,7 @@ library EthscriptionsRendererLib {
5656
/// @param content The content bytes
5757
/// @return mediaType Either "image" or "animation_url"
5858
/// @return mediaUri The data URI for the media
59-
function getMediaUri(Ethscriptions.Ethscription storage etsc, bytes memory content)
59+
function getMediaUri(Ethscriptions.EthscriptionStorage storage etsc, bytes memory content)
6060
internal
6161
view
6262
returns (string memory mediaType, string memory mediaUri)
@@ -88,7 +88,7 @@ library EthscriptionsRendererLib {
8888
/// @param content The content bytes
8989
/// @return The complete base64-encoded data URI
9090
function buildTokenURI(
91-
Ethscriptions.Ethscription storage etsc,
91+
Ethscriptions.EthscriptionStorage storage etsc,
9292
bytes32 ethscriptionId,
9393
bytes memory content
9494
) internal view returns (string memory) {

contracts/test/EthscriptionsWithContent.t.sol

Lines changed: 132 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import "./TestSetup.sol";
55

66
contract EthscriptionsWithContentTest is TestSetup {
77

8-
function testGetEthscriptionWithContent() public {
8+
function testGetEthscription() public {
99
// Create a test ethscription first
1010
bytes32 txHash = bytes32(uint256(12345));
1111
address creator = address(0x1);
@@ -30,59 +30,93 @@ contract EthscriptionsWithContentTest is TestSetup {
3030

3131
uint256 tokenId = ethscriptions.createEthscription(params);
3232

33-
// Test the new combined method
34-
(Ethscriptions.Ethscription memory ethscription, bytes memory content) = ethscriptions.getEthscriptionWithContent(txHash);
33+
// Test the new getEthscription method that returns Ethscription
34+
Ethscriptions.Ethscription memory complete = ethscriptions.getEthscription(txHash);
3535

3636
// Verify ethscription data
37-
assertEq(ethscription.creator, creator);
38-
assertEq(ethscription.initialOwner, initialOwner);
39-
assertEq(ethscription.previousOwner, creator);
40-
assertEq(ethscription.ethscriptionNumber, tokenId);
41-
assertEq(ethscription.mimetype, "text/plain");
42-
assertEq(ethscription.esip6, false);
37+
assertEq(complete.ethscriptionId, txHash);
38+
assertEq(complete.ethscriptionNumber, tokenId);
39+
assertEq(complete.creator, creator);
40+
assertEq(complete.initialOwner, initialOwner);
41+
assertEq(complete.previousOwner, creator);
42+
assertEq(complete.currentOwner, initialOwner);
43+
assertEq(complete.mimetype, "text/plain");
44+
assertEq(complete.esip6, false);
4345

4446
// Verify content
45-
assertEq(content, bytes(testContent));
46-
47-
// Compare with individual method calls to ensure they return the same data
48-
Ethscriptions.Ethscription memory ethscriptionSeparate = ethscriptions.getEthscription(txHash);
49-
bytes memory contentSeparate = ethscriptions.getEthscriptionContent(txHash);
50-
51-
// Compare structs (we'll compare individual fields since struct comparison isn't directly supported)
52-
assertEq(ethscription.creator, ethscriptionSeparate.creator);
53-
assertEq(ethscription.initialOwner, ethscriptionSeparate.initialOwner);
54-
assertEq(ethscription.previousOwner, ethscriptionSeparate.previousOwner);
55-
assertEq(ethscription.ethscriptionNumber, ethscriptionSeparate.ethscriptionNumber);
56-
assertEq(ethscription.createdAt, ethscriptionSeparate.createdAt);
57-
assertEq(ethscription.l1BlockNumber, ethscriptionSeparate.l1BlockNumber);
58-
assertEq(ethscription.l2BlockNumber, ethscriptionSeparate.l2BlockNumber);
59-
assertEq(ethscription.l1BlockHash, ethscriptionSeparate.l1BlockHash);
60-
61-
// Compare content fields
62-
assertEq(ethscription.contentUriHash, ethscriptionSeparate.contentUriHash);
63-
assertEq(ethscription.contentSha, ethscriptionSeparate.contentSha);
64-
assertEq(ethscription.mimetype, ethscriptionSeparate.mimetype);
65-
assertEq(ethscription.esip6, ethscriptionSeparate.esip6);
66-
67-
// Compare content
68-
assertEq(content, contentSeparate);
47+
assertEq(complete.content, bytes(testContent));
48+
49+
// Test the version without content using the overloaded function
50+
Ethscriptions.Ethscription memory withoutContent = ethscriptions.getEthscription(txHash, false);
51+
52+
// Verify same metadata but empty content
53+
assertEq(withoutContent.ethscriptionId, txHash);
54+
assertEq(withoutContent.ethscriptionNumber, tokenId);
55+
assertEq(withoutContent.creator, creator);
56+
assertEq(withoutContent.currentOwner, initialOwner);
57+
assertEq(withoutContent.content.length, 0, "Content should be empty");
58+
}
59+
60+
function testGetEthscriptionByTokenId() public {
61+
// Create a test ethscription first
62+
bytes32 txHash = bytes32(uint256(67890));
63+
address creator = address(0x5);
64+
address initialOwner = address(0x6);
65+
string memory testContent = "Test by token ID";
66+
67+
// Create the ethscription
68+
vm.prank(creator);
69+
Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({
70+
ethscriptionId: txHash,
71+
contentUriHash: keccak256(bytes("data:text/plain,Test by token ID")),
72+
initialOwner: initialOwner,
73+
content: bytes(testContent),
74+
mimetype: "text/plain",
75+
esip6: true,
76+
protocolParams: Ethscriptions.ProtocolParams({
77+
protocolName: "",
78+
operation: "",
79+
data: ""
80+
})
81+
});
82+
83+
uint256 tokenId = ethscriptions.createEthscription(params);
84+
85+
// Test getting by token ID
86+
Ethscriptions.Ethscription memory complete = ethscriptions.getEthscription(tokenId);
87+
88+
// Verify ethscription data
89+
assertEq(complete.ethscriptionId, txHash, "Ethscription ID should match");
90+
assertEq(complete.ethscriptionNumber, tokenId, "Token ID should match");
91+
assertEq(complete.creator, creator);
92+
assertEq(complete.currentOwner, initialOwner);
93+
assertEq(complete.content, bytes(testContent));
94+
95+
// Test without content version by token ID using the overloaded function
96+
Ethscriptions.Ethscription memory withoutContent = ethscriptions.getEthscription(tokenId, false);
97+
assertEq(withoutContent.ethscriptionId, txHash);
98+
assertEq(withoutContent.content.length, 0, "Content should be empty");
6999
}
70100

71-
function testGetEthscriptionWithContentNonExistent() public {
101+
function testGetEthscriptionNonExistent() public {
72102
bytes32 nonExistentTxHash = bytes32(uint256(99999));
73103

74104
// Should revert with EthscriptionDoesNotExist
75105
vm.expectRevert(Ethscriptions.EthscriptionDoesNotExist.selector);
76-
ethscriptions.getEthscriptionWithContent(nonExistentTxHash);
106+
ethscriptions.getEthscription(nonExistentTxHash);
107+
108+
// Same for without content version using the overloaded function
109+
vm.expectRevert(Ethscriptions.EthscriptionDoesNotExist.selector);
110+
ethscriptions.getEthscription(nonExistentTxHash, false);
77111
}
78112

79-
function testGetEthscriptionWithContentLargeContent() public {
80-
// Test with content that requires multiple SSTORE2 chunks
113+
function testGetEthscriptionWithLargeContent() public {
114+
// Test with content that's large (testing SSTORE2Unlimited)
81115
bytes32 txHash = bytes32(uint256(54321));
82116
address creator = address(0x3);
83117
address initialOwner = address(0x4);
84118

85-
// Create content larger than CHUNK_SIZE (24575 bytes)
119+
// Create content larger than inline storage (>31 bytes)
86120
bytes memory largeContent = new bytes(30000);
87121
for (uint256 i = 0; i < 30000; i++) {
88122
largeContent[i] = bytes1(uint8(i % 256));
@@ -106,15 +140,68 @@ contract EthscriptionsWithContentTest is TestSetup {
106140

107141
ethscriptions.createEthscription(params);
108142

109-
// Test the combined method with large content
110-
(Ethscriptions.Ethscription memory ethscription, bytes memory content) = ethscriptions.getEthscriptionWithContent(txHash);
143+
// Test the getEthscription method with large content
144+
Ethscriptions.Ethscription memory complete = ethscriptions.getEthscription(txHash);
111145

112146
// Verify content is correct
113-
assertEq(content.length, 30000);
114-
assertEq(content, largeContent);
147+
assertEq(complete.content.length, 30000);
148+
assertEq(complete.content, largeContent);
115149

116150
// Verify ethscription data
117-
assertEq(ethscription.creator, creator);
118-
assertEq(ethscription.initialOwner, initialOwner);
151+
assertEq(complete.creator, creator);
152+
assertEq(complete.initialOwner, initialOwner);
153+
assertEq(complete.currentOwner, initialOwner);
154+
155+
// Test without content - should have zero-length content using the overloaded function
156+
Ethscriptions.Ethscription memory withoutContent = ethscriptions.getEthscription(txHash, false);
157+
assertEq(withoutContent.content.length, 0, "Content should be empty");
158+
assertEq(withoutContent.creator, creator);
159+
assertEq(withoutContent.currentOwner, initialOwner);
160+
}
161+
162+
function testGetEthscriptionWithSmallContent() public {
163+
// Test with content that fits inline (≤31 bytes)
164+
bytes32 txHash = bytes32(uint256(11111));
165+
address creator = address(0x7);
166+
address initialOwner = address(0x8);
167+
168+
// Create small content (10 bytes)
169+
bytes memory smallContent = hex"48656c6c6f576f726c64"; // "HelloWorld"
170+
171+
// Create the ethscription
172+
vm.prank(creator);
173+
Ethscriptions.CreateEthscriptionParams memory params = Ethscriptions.CreateEthscriptionParams({
174+
ethscriptionId: txHash,
175+
contentUriHash: keccak256(bytes("data:text/plain,HelloWorld")),
176+
initialOwner: initialOwner,
177+
content: smallContent,
178+
mimetype: "text/plain",
179+
esip6: false,
180+
protocolParams: Ethscriptions.ProtocolParams({
181+
protocolName: "",
182+
operation: "",
183+
data: ""
184+
})
185+
});
186+
187+
uint256 tokenId = ethscriptions.createEthscription(params);
188+
189+
// Test the getEthscription method with small inline content
190+
Ethscriptions.Ethscription memory complete = ethscriptions.getEthscription(txHash);
191+
192+
// Verify content is correct
193+
assertEq(complete.content, smallContent);
194+
assertEq(complete.content.length, 10);
195+
196+
// Verify ownership chain
197+
assertEq(complete.creator, creator);
198+
assertEq(complete.initialOwner, initialOwner);
199+
assertEq(complete.currentOwner, initialOwner);
200+
assertEq(complete.previousOwner, creator);
201+
202+
// Test getting by token ID too
203+
Ethscriptions.Ethscription memory byTokenId = ethscriptions.getEthscription(tokenId);
204+
assertEq(byTokenId.ethscriptionId, txHash);
205+
assertEq(byTokenId.content, smallContent);
119206
}
120-
}
207+
}

contracts/test/EthscriptionsWithTestFunctions.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@ contract EthscriptionsWithTestFunctions is Ethscriptions {
1414
/// @notice Check if content is stored for an ethscription
1515
/// @dev Test-only function to check if content exists
1616
function hasContent(bytes32 ethscriptionId) external view returns (bool) {
17-
Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId);
17+
EthscriptionStorage storage ethscription = _getEthscriptionOrRevert(ethscriptionId);
1818
return contentStorageBySha[ethscription.contentSha] != bytes32(0);
1919
}
2020

2121
/// @notice Get the content storage value for an ethscription
2222
/// @dev Test-only function to inspect storage (either packed bytes or SSTORE2 address)
2323
function getContentStorage(bytes32 ethscriptionId) external view returns (bytes32) {
24-
Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId);
24+
EthscriptionStorage storage ethscription = _getEthscriptionOrRevert(ethscriptionId);
2525
return contentStorageBySha[ethscription.contentSha];
2626
}
2727

2828
/// @notice Get the content pointer for an ethscription (only for SSTORE2 stored content)
2929
/// @dev Test-only function to inspect SSTORE2 address
3030
function getContentPointer(bytes32 ethscriptionId) external view returns (address) {
31-
Ethscription storage ethscription = _getEthscriptionOrRevert(ethscriptionId);
31+
EthscriptionStorage storage ethscription = _getEthscriptionOrRevert(ethscriptionId);
3232
bytes32 stored = contentStorageBySha[ethscription.contentSha];
3333

3434
// Check if it's inline content using BytePackLib
@@ -46,6 +46,6 @@ contract EthscriptionsWithTestFunctions is Ethscriptions {
4646
/// @param ethscriptionId The ethscription ID (L1 tx hash)
4747
/// @return The content data
4848
function readContent(bytes32 ethscriptionId) external view returns (bytes memory) {
49-
return getEthscriptionContent(ethscriptionId);
49+
return _getEthscriptionContent(ethscriptionId);
5050
}
5151
}

contracts/test/SSTORE2ContentSizes.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ contract SSTORE2ContentSizesTest is TestSetup {
130130
// Test content retrieval via getEthscriptionContent
131131
g0 = gasleft();
132132
vm.resumeGasMetering();
133-
bytes memory retrievedContent = eth.getEthscriptionContent(txHash);
133+
bytes memory retrievedContent = eth.readContent(txHash);
134134
vm.pauseGasMetering();
135135
uint256 retrievalGas = g0 - gasleft();
136136

@@ -284,8 +284,8 @@ contract SSTORE2ContentSizesTest is TestSetup {
284284
assertEq(pointer1, pointer2, "Pointers should be identical");
285285

286286
// Verify content retrieval works for both
287-
bytes memory content1 = eth.getEthscriptionContent(txHash1);
288-
bytes memory content2 = eth.getEthscriptionContent(txHash2);
287+
bytes memory content1 = eth.readContent(txHash1);
288+
bytes memory content2 = eth.readContent(txHash2);
289289
assertEq(keccak256(content1), keccak256(content2), "Content should be identical");
290290
assertEq(keccak256(content1), keccak256(content), "Retrieved content should match original");
291291

0 commit comments

Comments
 (0)