Skip to content

Commit d970ae2

Browse files
Merge pull request #123 from ethscriptions-protocol/optimize
Optimize contracts
2 parents fe5c2b5 + 33c8891 commit d970ae2

25 files changed

+129
-293
lines changed

app/models/ethscription_transaction.rb

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class EthscriptionTransaction < T::Struct
3030
# Debug info (can be removed if not needed)
3131
prop :ethscription_operation, T.nilable(String) # 'create', 'transfer', 'transfer_with_previous_owner'
3232

33+
MAX_MIMETYPE_LENGTH = 1000
3334
DEPOSIT_TX_TYPE = 0x7D
3435
MINT = 0
3536
VALUE = 0
@@ -107,7 +108,7 @@ def self.transfer_multiple_ethscriptions(
107108
def function_selector
108109
function_signature = case ethscription_operation
109110
when 'create'
110-
'createEthscription((bytes32,bytes32,address,bytes,string,string,string,bool,(string,string,bytes)))'
111+
'createEthscription((bytes32,bytes32,address,bytes,string,bool,(string,string,bytes)))'
111112
when 'transfer'
112113
if transfer_ids && transfer_ids.any?
113114
'transferMultipleEthscriptions(bytes32[],address)'
@@ -218,9 +219,7 @@ def build_create_calldata
218219
# Both input and event-based creates use data URI format
219220
# Events are "equivalent of an EOA hex-encoding contentURI and putting it in the calldata"
220221
data_uri = DataUri.new(content_uri)
221-
mimetype = data_uri.mimetype.to_s
222-
media_type = mimetype&.split('/')&.first
223-
mime_subtype = mimetype&.split('/')&.last
222+
mimetype = data_uri.mimetype.to_s.first(MAX_MIMETYPE_LENGTH)
224223
raw_content = data_uri.decoded_data.b
225224
esip6 = DataUri.esip6?(content_uri) || false
226225

@@ -249,14 +248,12 @@ def build_create_calldata
249248
owner_bin, # address
250249
raw_content, # bytes content
251250
mimetype.b, # string
252-
media_type.to_s.b, # string
253-
mime_subtype.to_s.b, # string
254251
esip6, # bool esip6
255252
protocol_params # ProtocolParams tuple
256253
]
257254

258255
encoded = Eth::Abi.encode(
259-
['(bytes32,bytes32,address,bytes,string,string,string,bool,(string,string,bytes))'],
256+
['(bytes32,bytes32,address,bytes,string,bool,(string,string,bytes))'],
260257
[params]
261258
)
262259

contracts/script/L2Genesis.s.sol

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,23 @@ contract GenesisEthscriptions is Ethscriptions {
4444

4545
// Set all values including genesis-specific ones
4646
ethscriptions[params.transactionHash] = Ethscription({
47-
content: ContentInfo({
48-
contentUriHash: params.contentUriHash,
49-
contentSha: contentSha,
50-
mimetype: params.mimetype,
51-
mediaType: params.mediaType,
52-
mimeSubtype: params.mimeSubtype,
53-
esip6: params.esip6
54-
}),
47+
// Fixed-size fields
48+
contentUriHash: params.contentUriHash,
49+
contentSha: contentSha,
50+
l1BlockHash: l1BlockHash,
51+
// Packed slot 3
5552
creator: creator,
53+
createdAt: uint48(createdAt),
54+
l1BlockNumber: uint48(l1BlockNumber),
55+
// Dynamic field
56+
mimetype: params.mimetype,
57+
// Packed slot N
5658
initialOwner: params.initialOwner,
59+
ethscriptionNumber: uint48(totalSupply()),
60+
esip6: params.esip6,
61+
// Packed slot N+1
5762
previousOwner: creator,
58-
ethscriptionNumber: totalSupply(),
59-
createdAt: createdAt,
60-
l1BlockNumber: l1BlockNumber,
61-
l2BlockNumber: 0, // Genesis ethscriptions have no L2 block
62-
l1BlockHash: l1BlockHash
63+
l2BlockNumber: 0 // Genesis ethscriptions have no L2 block
6364
});
6465

6566
// Use ethscription number as token ID
@@ -337,8 +338,6 @@ contract L2Genesis is Script {
337338
params.initialOwner = initialOwner;
338339
params.content = vm.parseJsonBytes(json, string.concat(basePath, ".content"));
339340
params.mimetype = vm.parseJsonString(json, string.concat(basePath, ".mimetype"));
340-
params.mediaType = vm.parseJsonString(json, string.concat(basePath, ".media_type"));
341-
params.mimeSubtype = vm.parseJsonString(json, string.concat(basePath, ".mime_subtype"));
342341
params.esip6 = vm.parseJsonBool(json, string.concat(basePath, ".esip6"));
343342
params.protocolParams = Ethscriptions.ProtocolParams({
344343
protocolName: "",

contracts/script/TestTokenUri.s.sol

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ contract TestTokenUri is Script {
2222
initialOwner: address(0x1111),
2323
content: bytes("Hello World!"),
2424
mimetype: "text/plain",
25-
mediaType: "text",
26-
mimeSubtype: "plain",
2725
esip6: false,
2826
protocolParams: Ethscriptions.ProtocolParams("", "", "")
2927
}));
@@ -36,8 +34,6 @@ contract TestTokenUri is Script {
3634
initialOwner: address(0x2222),
3735
content: bytes('{"p":"erc-20","op":"mint","tick":"test","amt":"1000"}'),
3836
mimetype: "application/json",
39-
mediaType: "application",
40-
mimeSubtype: "json",
4137
esip6: false,
4238
protocolParams: Ethscriptions.ProtocolParams("", "", "")
4339
}));
@@ -50,8 +46,6 @@ contract TestTokenUri is Script {
5046
initialOwner: address(0x3333),
5147
content: bytes('<html><body style="background:linear-gradient(45deg,#ff006e,#8338ec);color:white;font-family:monospace;display:flex;align-items:center;justify-content:center;height:100vh;margin:0"><h1>Ethscriptions Rule!</h1></body></html>'),
5248
mimetype: "text/html",
53-
mediaType: "text",
54-
mimeSubtype: "html",
5549
esip6: false,
5650
protocolParams: Ethscriptions.ProtocolParams("", "", "")
5751
}));
@@ -65,8 +59,6 @@ contract TestTokenUri is Script {
6559
initialOwner: address(0x4444),
6660
content: redPixelPng,
6761
mimetype: "image/png",
68-
mediaType: "image",
69-
mimeSubtype: "png",
7062
esip6: false,
7163
protocolParams: Ethscriptions.ProtocolParams("", "", "")
7264
}));
@@ -79,8 +71,6 @@ contract TestTokenUri is Script {
7971
initialOwner: address(0x5555),
8072
content: bytes("body { background: #000; color: #0f0; font-family: 'Courier New'; }"),
8173
mimetype: "text/css",
82-
mediaType: "text",
83-
mimeSubtype: "css",
8474
esip6: false,
8575
protocolParams: Ethscriptions.ProtocolParams("", "", "")
8676
}));

contracts/src/Ethscriptions.sol

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,24 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable {
2323
// STRUCTS
2424
// =============================================================
2525

26-
struct ContentInfo {
27-
bytes32 contentUriHash; // SHA256 of raw content URI string (for protocol uniqueness)
28-
bytes32 contentSha; // SHA256 of decoded raw bytes (for storage reference)
29-
string mimetype; // Full MIME type (e.g., "text/plain")
30-
string mediaType; // e.g., "text", "image"
31-
string mimeSubtype; // e.g., "plain", "png"
32-
bool esip6;
33-
}
34-
3526
struct Ethscription {
36-
ContentInfo content;
27+
// Full slots
28+
bytes32 contentUriHash;
29+
bytes32 contentSha;
30+
bytes32 l1BlockHash;
31+
// Packed slot (32 bytes)
3732
address creator;
33+
uint48 createdAt;
34+
uint48 l1BlockNumber;
35+
// Dynamic
36+
string mimetype;
37+
// Packed slot (27 bytes used, 5 free)
3838
address initialOwner;
39+
uint48 ethscriptionNumber;
40+
bool esip6;
41+
// Packed slot (26 bytes used, 6 free)
3942
address previousOwner;
40-
uint256 ethscriptionNumber;
41-
uint256 createdAt; // Timestamp when created
42-
uint64 l1BlockNumber; // L1 block number when created
43-
uint64 l2BlockNumber; // L2 block number when created
44-
bytes32 l1BlockHash; // L1 block hash when created
43+
uint48 l2BlockNumber;
4544
}
4645

4746
struct ProtocolParams {
@@ -56,8 +55,6 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable {
5655
address initialOwner;
5756
bytes content; // Raw decoded bytes (not Base64)
5857
string mimetype;
59-
string mediaType;
60-
string mimeSubtype;
6158
bool esip6;
6259
ProtocolParams protocolParams; // Protocol operation data (optional)
6360
}
@@ -216,22 +213,18 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable {
216213
bytes32 contentSha = _storeContent(params.content);
217214

218215
ethscriptions[params.transactionHash] = Ethscription({
219-
content: ContentInfo({
220-
contentUriHash: params.contentUriHash,
221-
contentSha: contentSha,
222-
mimetype: params.mimetype,
223-
mediaType: params.mediaType,
224-
mimeSubtype: params.mimeSubtype,
225-
esip6: params.esip6
226-
}),
216+
contentUriHash: params.contentUriHash,
217+
contentSha: contentSha,
218+
l1BlockHash: l1Block.hash(),
227219
creator: creator,
220+
createdAt: uint48(block.timestamp),
221+
l1BlockNumber: uint48(l1Block.number()),
222+
mimetype: params.mimetype,
228223
initialOwner: params.initialOwner,
229-
previousOwner: creator, // Initially same as creator
230-
ethscriptionNumber: totalSupply(),
231-
createdAt: block.timestamp,
232-
l1BlockNumber: l1Block.number(),
233-
l2BlockNumber: uint64(block.number),
234-
l1BlockHash: l1Block.hash()
224+
ethscriptionNumber: uint48(totalSupply()),
225+
esip6: params.esip6,
226+
previousOwner: creator,
227+
l2BlockNumber: uint48(block.number)
235228
});
236229

237230
// Use ethscription number as token ID
@@ -377,7 +370,7 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable {
377370
/// @notice Get content for an ethscription
378371
function getEthscriptionContent(bytes32 txHash) public view requireExists(txHash) returns (bytes memory) {
379372
Ethscription storage etsc = ethscriptions[txHash];
380-
address[] storage pointers = contentPointersBySha[etsc.content.contentSha];
373+
address[] storage pointers = contentPointersBySha[etsc.contentSha];
381374
// Empty content is valid - returns "" for empty pointers array
382375
return pointers.read();
383376
}
@@ -388,7 +381,7 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable {
388381
/// @return content The content bytes
389382
function getEthscriptionWithContent(bytes32 txHash) external view requireExists(txHash) returns (Ethscription memory ethscription, bytes memory content) {
390383
ethscription = ethscriptions[txHash];
391-
address[] storage pointers = contentPointersBySha[ethscription.content.contentSha];
384+
address[] storage pointers = contentPointersBySha[ethscription.contentSha];
392385
// Empty content is valid - returns "" for empty pointers array
393386
content = pointers.read();
394387
}
@@ -620,8 +613,8 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable {
620613
txHash,
621614
etsc.creator,
622615
etsc.initialOwner,
623-
etsc.content.contentUriHash,
624-
etsc.content.contentSha,
616+
etsc.contentUriHash,
617+
etsc.contentSha,
625618
etsc.ethscriptionNumber
626619
);
627620
}

contracts/src/EthscriptionsProver.sol

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,26 @@ contract EthscriptionsProver {
1919

2020
/// @notice Info stored when an ethscription is queued for proving
2121
struct QueuedProof {
22-
uint256 l2BlockNumber;
23-
uint256 l2BlockTimestamp;
2422
bytes32 l1BlockHash;
25-
uint256 l1BlockNumber;
23+
uint48 l2BlockNumber;
24+
uint48 l2BlockTimestamp;
25+
uint48 l1BlockNumber;
2626
}
2727

2828
/// @notice Struct for ethscription data proof
2929
struct EthscriptionDataProof {
3030
bytes32 ethscriptionTxHash;
3131
bytes32 contentSha;
3232
bytes32 contentUriHash;
33+
bytes32 l1BlockHash;
3334
address creator;
3435
address currentOwner;
3536
address previousOwner;
36-
uint256 ethscriptionNumber;
3737
bool esip6;
38-
bytes32 l1BlockHash;
39-
uint256 l1BlockNumber;
40-
uint256 l2BlockNumber;
41-
uint256 l2Timestamp;
38+
uint48 ethscriptionNumber;
39+
uint48 l1BlockNumber;
40+
uint48 l2BlockNumber;
41+
uint48 l2Timestamp;
4242
}
4343

4444
// =============================================================
@@ -99,10 +99,10 @@ contract EthscriptionsProver {
9999
// Capture the L1 block hash and number at the time of queuing
100100
L1Block l1Block = L1Block(L1_BLOCK);
101101
queuedProofInfo[txHash] = QueuedProof({
102-
l2BlockNumber: block.number,
103-
l2BlockTimestamp: block.timestamp,
104102
l1BlockHash: l1Block.hash(),
105-
l1BlockNumber: l1Block.number()
103+
l2BlockNumber: uint48(block.number),
104+
l2BlockTimestamp: uint48(block.timestamp),
105+
l1BlockNumber: uint48(l1Block.number())
106106
});
107107
}
108108
}
@@ -143,14 +143,14 @@ contract EthscriptionsProver {
143143
// Create proof struct with all ethscription data
144144
EthscriptionDataProof memory proof = EthscriptionDataProof({
145145
ethscriptionTxHash: ethscriptionTxHash,
146-
contentSha: etsc.content.contentSha,
147-
contentUriHash: etsc.content.contentUriHash,
146+
contentSha: etsc.contentSha,
147+
contentUriHash: etsc.contentUriHash,
148+
l1BlockHash: proofInfo.l1BlockHash,
148149
creator: etsc.creator,
149150
currentOwner: currentOwner,
150151
previousOwner: etsc.previousOwner,
152+
esip6: etsc.esip6,
151153
ethscriptionNumber: etsc.ethscriptionNumber,
152-
esip6: etsc.content.esip6,
153-
l1BlockHash: proofInfo.l1BlockHash,
154154
l1BlockNumber: proofInfo.l1BlockNumber,
155155
l2BlockNumber: proofInfo.l2BlockNumber,
156156
l2Timestamp: proofInfo.l2BlockTimestamp

contracts/src/libraries/EthscriptionsRendererLib.sol

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,11 @@ library EthscriptionsRendererLib {
3434

3535
string memory part2 = string.concat(
3636
'"},{"trait_type":"Content SHA","value":"',
37-
uint256(etsc.content.contentSha).toHexString(),
37+
uint256(etsc.contentSha).toHexString(),
3838
'"},{"trait_type":"MIME Type","value":"',
39-
etsc.content.mimetype.escapeJSON(),
40-
'"},{"trait_type":"Media Type","value":"',
41-
etsc.content.mediaType.escapeJSON(),
42-
'"},{"trait_type":"MIME Subtype","value":"',
43-
etsc.content.mimeSubtype.escapeJSON()
44-
);
45-
46-
string memory part3 = string.concat(
39+
etsc.mimetype.escapeJSON(),
4740
'"},{"trait_type":"ESIP-6","value":"',
48-
etsc.content.esip6 ? "true" : "false",
41+
etsc.esip6 ? "true" : "false",
4942
'"},{"trait_type":"L1 Block Number","display_type":"number","value":',
5043
uint256(etsc.l1BlockNumber).toString(),
5144
'},{"trait_type":"L2 Block Number","display_type":"number","value":',
@@ -55,7 +48,7 @@ library EthscriptionsRendererLib {
5548
'}]'
5649
);
5750

58-
return string.concat(part1, part2, part3);
51+
return string.concat(part1, part2);
5952
}
6053

6154
/// @notice Generate the media URI for an ethscription
@@ -68,22 +61,22 @@ library EthscriptionsRendererLib {
6861
view
6962
returns (string memory mediaType, string memory mediaUri)
7063
{
71-
if (etsc.content.mimetype.startsWith("image/")) {
64+
if (etsc.mimetype.startsWith("image/")) {
7265
// Image content: wrap in SVG for pixel-perfect rendering
73-
string memory imageDataUri = constructDataURI(etsc.content.mimetype, content);
66+
string memory imageDataUri = constructDataURI(etsc.mimetype, content);
7467
string memory svg = wrapImageInSVG(imageDataUri);
7568
mediaUri = constructDataURI("image/svg+xml", bytes(svg));
7669
return ("image", mediaUri);
7770
} else {
7871
// Non-image content: use animation_url
79-
if (etsc.content.mimetype.startsWith("video/") ||
80-
etsc.content.mimetype.startsWith("audio/") ||
81-
etsc.content.mimetype.eq("text/html")) {
72+
if (etsc.mimetype.startsWith("video/") ||
73+
etsc.mimetype.startsWith("audio/") ||
74+
etsc.mimetype.eq("text/html")) {
8275
// Video, audio, and HTML pass through directly as data URIs
83-
mediaUri = constructDataURI(etsc.content.mimetype, content);
76+
mediaUri = constructDataURI(etsc.mimetype, content);
8477
} else {
8578
// Everything else (text/plain, application/json, etc.) uses the HTML viewer
86-
mediaUri = createTextViewerDataURI(etsc.content.mimetype, content);
79+
mediaUri = createTextViewerDataURI(etsc.mimetype, content);
8780
}
8881
return ("animation_url", mediaUri);
8982
}

0 commit comments

Comments
 (0)