-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathEthscriptionsProver.sol
More file actions
165 lines (136 loc) · 6.71 KB
/
Copy pathEthscriptionsProver.sol
File metadata and controls
165 lines (136 loc) · 6.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "./Ethscriptions.sol";
import "./L2/L2ToL1MessagePasser.sol";
import "./L2/L1Block.sol";
import "./libraries/Predeploys.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/// @title EthscriptionsProver
/// @notice Proves Ethscription ownership and token balances to L1 via OP Stack
/// @dev Uses L2ToL1MessagePasser to send provable messages to L1
contract EthscriptionsProver {
using EnumerableSet for EnumerableSet.Bytes32Set;
// =============================================================
// STRUCTS
// =============================================================
/// @notice Info stored when an ethscription is queued for proving
struct QueuedProof {
bytes32 l1BlockHash;
uint48 l2BlockNumber;
uint48 l2BlockTimestamp;
uint48 l1BlockNumber;
}
/// @notice Struct for ethscription data proof
struct EthscriptionDataProof {
bytes32 ethscriptionTxHash;
bytes32 contentSha;
bytes32 contentUriHash;
bytes32 l1BlockHash;
address creator;
address currentOwner;
address previousOwner;
bool esip6;
uint48 ethscriptionNumber;
uint48 l1BlockNumber;
uint48 l2BlockNumber;
uint48 l2Timestamp;
}
// =============================================================
// CONSTANTS
// =============================================================
/// @notice L1Block contract address for access control
address constant L1_BLOCK = Predeploys.L1_BLOCK_ATTRIBUTES;
/// @notice L2ToL1MessagePasser predeploy address on OP Stack
L2ToL1MessagePasser constant L2_TO_L1_MESSAGE_PASSER =
L2ToL1MessagePasser(Predeploys.L2_TO_L1_MESSAGE_PASSER);
/// @notice The Ethscriptions contract (pre-deployed at known address)
Ethscriptions constant ethscriptions = Ethscriptions(Predeploys.ETHSCRIPTIONS);
// =============================================================
// STATE VARIABLES
// =============================================================
/// @notice Set of all ethscription transaction hashes queued for proving
EnumerableSet.Bytes32Set private queuedEthscriptions;
/// @notice Mapping from ethscription tx hash to its queued proof info
mapping(bytes32 => QueuedProof) private queuedProofInfo;
// =============================================================
// CUSTOM ERRORS
// =============================================================
error OnlyEthscriptions();
error OnlyL1Block();
// =============================================================
// EVENTS
// =============================================================
/// @notice Emitted when an ethscription data proof is sent to L1
event EthscriptionDataProofSent(
bytes32 indexed ethscriptionTxHash,
uint256 indexed l2BlockNumber,
uint256 l2Timestamp
);
// =============================================================
// EXTERNAL FUNCTIONS
// =============================================================
/// @notice Queue an ethscription for proving
/// @dev Only callable by the Ethscriptions contract
/// @param txHash The transaction hash of the ethscription
function queueEthscription(bytes32 txHash) external virtual {
if (msg.sender != address(ethscriptions)) revert OnlyEthscriptions();
// Add to the set (deduplicates automatically)
if (queuedEthscriptions.add(txHash)) {
// Only store info if this is the first time we're queueing this txHash
// Capture the L1 block hash and number at the time of queuing
L1Block l1Block = L1Block(L1_BLOCK);
queuedProofInfo[txHash] = QueuedProof({
l1BlockHash: l1Block.hash(),
l2BlockNumber: uint48(block.number),
l2BlockTimestamp: uint48(block.timestamp),
l1BlockNumber: uint48(l1Block.number())
});
}
}
/// @notice Flush all queued proofs
/// @dev Only callable by the L1Block contract at the start of each new block
function flushAllProofs() external {
if (msg.sender != L1_BLOCK) revert OnlyL1Block();
uint256 count = queuedEthscriptions.length();
// Process and remove each ethscription from the set
// We iterate backwards to avoid index shifting during removal
for (uint256 i = count; i > 0; i--) {
bytes32 txHash = queuedEthscriptions.at(i - 1);
// Create and send proof for current state with stored block info
_createAndSendProof(txHash, queuedProofInfo[txHash]);
// Clean up: remove from set and delete the proof info
queuedEthscriptions.remove(txHash);
delete queuedProofInfo[txHash];
}
}
// =============================================================
// INTERNAL FUNCTIONS
// =============================================================
/// @notice Internal function to create and send proof for an ethscription
/// @param ethscriptionTxHash The transaction hash of the ethscription
/// @param proofInfo The queued proof info containing block data
function _createAndSendProof(bytes32 ethscriptionTxHash, QueuedProof memory proofInfo) internal {
// Get ethscription data including previous owner
Ethscriptions.Ethscription memory etsc = ethscriptions.getEthscription(ethscriptionTxHash);
address currentOwner = ethscriptions.ownerOf(ethscriptionTxHash);
// Create proof struct with all ethscription data
EthscriptionDataProof memory proof = EthscriptionDataProof({
ethscriptionTxHash: ethscriptionTxHash,
contentSha: etsc.contentSha,
contentUriHash: etsc.contentUriHash,
l1BlockHash: proofInfo.l1BlockHash,
creator: etsc.creator,
currentOwner: currentOwner,
previousOwner: etsc.previousOwner,
esip6: etsc.esip6,
ethscriptionNumber: etsc.ethscriptionNumber,
l1BlockNumber: proofInfo.l1BlockNumber,
l2BlockNumber: proofInfo.l2BlockNumber,
l2Timestamp: proofInfo.l2BlockTimestamp
});
// Encode and send to L1 with zero address and gas (only for state recording)
bytes memory proofData = abi.encode(proof);
L2_TO_L1_MESSAGE_PASSER.initiateWithdrawal(address(0), 0, proofData);
emit EthscriptionDataProofSent(ethscriptionTxHash, proofInfo.l2BlockNumber, proofInfo.l2BlockTimestamp);
}
}