Skip to content

Commit b7f39cd

Browse files
Merge pull request #112 from ethscriptions-protocol/improve_prover
Improve prover setup
2 parents 23f1c6f + f159468 commit b7f39cd

File tree

6 files changed

+201
-230
lines changed

6 files changed

+201
-230
lines changed

contracts/src/Ethscriptions.sol

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,6 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable {
131131
bytes revertData
132132
);
133133

134-
/// @notice Emitted when a prover operation fails but ethscription continues
135-
event ProverFailed(
136-
bytes32 indexed transactionHash,
137-
bytes revertData
138-
);
139134

140135

141136
/// @notice Modifier to emit pending genesis events on first real creation
@@ -407,12 +402,8 @@ contract Ethscriptions is ERC721EthscriptionsUpgradeable {
407402
}
408403
}
409404

410-
// Use try-catch to prevent prover failures from reverting operations
411-
try prover.proveEthscriptionData(txHash) {} catch (bytes memory revertData) {
412-
// Proving failed, but the operation should continue
413-
// The prover can be called again later if needed
414-
emit ProverFailed(txHash, revertData);
415-
}
405+
// Queue ethscription for batch proving at block boundary
406+
prover.queueEthscription(txHash);
416407
}
417408

418409
/// @notice Get ethscription details (returns struct to avoid stack too deep)

contracts/src/EthscriptionsERC20.sol

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@ pragma solidity 0.8.24;
44
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
55
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20CappedUpgradeable.sol";
66
import "./libraries/Predeploys.sol";
7-
import "./EthscriptionsProver.sol";
87

98
contract EthscriptionsERC20 is ERC20Upgradeable, ERC20CappedUpgradeable {
109
address public constant tokenManager = Predeploys.TOKEN_MANAGER;
11-
EthscriptionsProver public constant prover = EthscriptionsProver(Predeploys.ETHSCRIPTIONS_PROVER);
12-
10+
1311
bytes32 public deployTxHash; // The ethscription hash that deployed this token
1412

1513
function initialize(
@@ -61,21 +59,13 @@ contract EthscriptionsERC20 is ERC20Upgradeable, ERC20CappedUpgradeable {
6159
}
6260

6361
// Required overrides for multiple inheritance
64-
function _update(address from, address to, uint256 value)
65-
internal
66-
override(ERC20Upgradeable, ERC20CappedUpgradeable)
62+
function _update(address from, address to, uint256 value)
63+
internal
64+
override(ERC20Upgradeable, ERC20CappedUpgradeable)
6765
{
6866
super._update(from, to, value);
69-
70-
// Automatically prove token balances after any update including burns
71-
// For transfers: prove both from and to
72-
// For mints (from == address(0)): only prove to
73-
// For burns (to == address(0)): only prove from
74-
if (from != address(0)) {
75-
prover.proveTokenBalance(from, deployTxHash);
76-
}
77-
if (to != address(0)) {
78-
prover.proveTokenBalance(to, deployTxHash);
79-
}
67+
68+
// Token balance proving has been removed in favor of ethscription-only proving
69+
// Token balances can be derived from ethscription ownership and transfer history
8070
}
8171
}

contracts/src/EthscriptionsProver.sol

Lines changed: 80 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,128 +2,133 @@
22
pragma solidity 0.8.24;
33

44
import "./Ethscriptions.sol";
5-
import "./TokenManager.sol";
6-
import "./EthscriptionsERC20.sol";
75
import "./L2/L2ToL1MessagePasser.sol";
6+
import "./L2/L1Block.sol";
87
import "./libraries/Predeploys.sol";
8+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
99

1010
/// @title EthscriptionsProver
1111
/// @notice Proves Ethscription ownership and token balances to L1 via OP Stack
1212
/// @dev Uses L2ToL1MessagePasser to send provable messages to L1
1313
contract EthscriptionsProver {
14+
using EnumerableSet for EnumerableSet.Bytes32Set;
15+
16+
/// @notice Info stored when an ethscription is queued for proving
17+
struct QueuedProof {
18+
uint256 l2BlockNumber;
19+
uint256 l2BlockTimestamp;
20+
bytes32 l1BlockHash;
21+
uint256 l1BlockNumber;
22+
}
23+
24+
/// @notice Set of all ethscription transaction hashes queued for proving
25+
EnumerableSet.Bytes32Set private queuedEthscriptions;
26+
27+
/// @notice Mapping from ethscription tx hash to its queued proof info
28+
mapping(bytes32 => QueuedProof) private queuedProofInfo;
29+
30+
/// @notice L1Block contract address for access control
31+
address public constant L1_BLOCK = Predeploys.L1_BLOCK_ATTRIBUTES;
1432
/// @notice L2ToL1MessagePasser predeploy address on OP Stack
1533
L2ToL1MessagePasser constant L2_TO_L1_MESSAGE_PASSER =
1634
L2ToL1MessagePasser(Predeploys.L2_TO_L1_MESSAGE_PASSER);
1735

1836
/// @notice The Ethscriptions contract (pre-deployed at known address)
1937
Ethscriptions public constant ethscriptions = Ethscriptions(Predeploys.ETHSCRIPTIONS);
2038

21-
/// @notice The TokenManager contract (pre-deployed at known address)
22-
TokenManager public constant tokenManager = TokenManager(Predeploys.TOKEN_MANAGER);
23-
24-
/// @notice Struct for token balance proof data
25-
struct TokenBalanceProof {
26-
address holder;
27-
string protocol;
28-
string tick;
29-
uint256 balance;
30-
uint256 l2BlockNumber;
31-
uint256 l2Timestamp;
32-
// TODO: Add l1BlockNumber once we have L2->L1 block mapping
33-
}
34-
3539
/// @notice Struct for ethscription data proof
3640
struct EthscriptionDataProof {
3741
bytes32 ethscriptionTxHash;
3842
bytes32 contentSha;
43+
bytes32 contentUriHash;
3944
address creator;
4045
address currentOwner;
4146
address previousOwner;
4247
uint256 ethscriptionNumber;
43-
bool isToken;
44-
uint256 tokenAmount;
48+
bool esip6;
49+
bytes32 l1BlockHash;
50+
uint256 l1BlockNumber;
4551
uint256 l2BlockNumber;
4652
uint256 l2Timestamp;
47-
// TODO: Add l1BlockNumber once we have L2->L1 block mapping
4853
}
4954

5055
/// @notice Events for tracking proofs
51-
event TokenBalanceProofSent(
52-
address indexed holder,
53-
string tick,
54-
uint256 indexed l2BlockNumber,
55-
uint256 l2Timestamp
56-
);
57-
5856
event EthscriptionDataProofSent(
5957
bytes32 indexed ethscriptionTxHash,
6058
uint256 indexed l2BlockNumber,
6159
uint256 l2Timestamp
6260
);
63-
64-
/// @notice Prove token balance for an address
65-
/// @param holder The address to prove balance for
66-
/// @param deployTxHash The deploy transaction hash (identifies the token type)
67-
function proveTokenBalance(
68-
address holder,
69-
bytes32 deployTxHash
70-
) external {
71-
// Get token info from TokenManager
72-
TokenManager.TokenInfo memory tokenInfo = tokenManager.getTokenInfo(deployTxHash);
73-
74-
// Get balance
75-
EthscriptionsERC20 token = EthscriptionsERC20(tokenInfo.tokenContract);
76-
uint256 balance = token.balanceOf(holder);
77-
78-
// Create proof struct
79-
TokenBalanceProof memory proof = TokenBalanceProof({
80-
holder: holder,
81-
protocol: tokenInfo.protocol,
82-
tick: tokenInfo.tick,
83-
balance: balance,
84-
l2BlockNumber: block.number,
85-
l2Timestamp: block.timestamp
86-
});
87-
88-
// Encode and send to L1 with zero address and gas (only for state recording)
89-
bytes memory proofData = abi.encode(proof);
90-
L2_TO_L1_MESSAGE_PASSER.initiateWithdrawal(address(0), 0, proofData);
91-
92-
emit TokenBalanceProofSent(holder, tokenInfo.tick, block.number, block.timestamp);
61+
62+
/// @notice Queue an ethscription for proving
63+
/// @dev Only callable by the Ethscriptions contract
64+
/// @param txHash The transaction hash of the ethscription
65+
function queueEthscription(bytes32 txHash) external virtual {
66+
require(msg.sender == address(ethscriptions), "Only Ethscriptions contract can queue");
67+
68+
// Add to the set (deduplicates automatically)
69+
if (queuedEthscriptions.add(txHash)) {
70+
// Only store info if this is the first time we're queueing this txHash
71+
// Capture the L1 block hash and number at the time of queuing
72+
L1Block l1Block = L1Block(L1_BLOCK);
73+
queuedProofInfo[txHash] = QueuedProof({
74+
l2BlockNumber: block.number,
75+
l2BlockTimestamp: block.timestamp,
76+
l1BlockHash: l1Block.hash(),
77+
l1BlockNumber: l1Block.number()
78+
});
79+
}
9380
}
94-
95-
/// @notice Prove ethscription existence and metadata
81+
82+
/// @notice Flush all queued proofs
83+
/// @dev Only callable by the L1Block contract at the start of each new block
84+
function flushAllProofs() external {
85+
require(msg.sender == L1_BLOCK, "Only L1Block can flush");
86+
87+
uint256 count = queuedEthscriptions.length();
88+
89+
// Process and remove each ethscription from the set
90+
// We iterate backwards to avoid index shifting during removal
91+
for (uint256 i = count; i > 0; i--) {
92+
bytes32 txHash = queuedEthscriptions.at(i - 1);
93+
94+
// Create and send proof for current state with stored block info
95+
_createAndSendProof(txHash, queuedProofInfo[txHash]);
96+
97+
// Clean up: remove from set and delete the proof info
98+
queuedEthscriptions.remove(txHash);
99+
delete queuedProofInfo[txHash];
100+
}
101+
}
102+
103+
/// @notice Internal function to create and send proof for an ethscription
96104
/// @param ethscriptionTxHash The transaction hash of the ethscription
97-
function proveEthscriptionData(bytes32 ethscriptionTxHash) external virtual {
105+
/// @param proofInfo The queued proof info containing block data
106+
function _createAndSendProof(bytes32 ethscriptionTxHash, QueuedProof memory proofInfo) internal {
98107
// Get ethscription data including previous owner
99108
Ethscriptions.Ethscription memory etsc = ethscriptions.getEthscription(ethscriptionTxHash);
100109
address currentOwner = ethscriptions.currentOwner(ethscriptionTxHash);
101-
102-
// Check if it's a token item
103-
bool isToken = tokenManager.isTokenItem(ethscriptionTxHash);
104-
uint256 tokenAmount = 0;
105-
if (isToken) {
106-
tokenAmount = tokenManager.getTokenAmount(ethscriptionTxHash);
107-
}
108-
109-
// Create proof struct with previous owner
110+
111+
// Create proof struct with all ethscription data
110112
EthscriptionDataProof memory proof = EthscriptionDataProof({
111113
ethscriptionTxHash: ethscriptionTxHash,
112114
contentSha: etsc.content.contentSha,
115+
contentUriHash: etsc.content.contentUriHash,
113116
creator: etsc.creator,
114117
currentOwner: currentOwner,
115118
previousOwner: etsc.previousOwner,
116119
ethscriptionNumber: etsc.ethscriptionNumber,
117-
isToken: isToken,
118-
tokenAmount: tokenAmount,
119-
l2BlockNumber: block.number,
120-
l2Timestamp: block.timestamp
120+
esip6: etsc.content.esip6,
121+
l1BlockHash: proofInfo.l1BlockHash,
122+
l1BlockNumber: proofInfo.l1BlockNumber,
123+
l2BlockNumber: proofInfo.l2BlockNumber,
124+
l2Timestamp: proofInfo.l2BlockTimestamp
121125
});
122-
126+
123127
// Encode and send to L1 with zero address and gas (only for state recording)
124128
bytes memory proofData = abi.encode(proof);
125129
L2_TO_L1_MESSAGE_PASSER.initiateWithdrawal(address(0), 0, proofData);
126-
127-
emit EthscriptionDataProofSent(ethscriptionTxHash, block.number, block.timestamp);
130+
131+
emit EthscriptionDataProofSent(ethscriptionTxHash, proofInfo.l2BlockNumber, proofInfo.l2BlockTimestamp);
128132
}
133+
129134
}

contracts/src/L2/L1Block.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
pragma solidity 0.8.24;
33

44
import { Constants } from "../libraries/Constants.sol";
5+
import { Predeploys } from "../libraries/Predeploys.sol";
6+
7+
interface IEthscriptionsProver {
8+
function flushAllProofs() external;
9+
}
510

611
/// @custom:proxied
712
/// @custom:predeploy 0x4200000000000000000000000000000000000015
@@ -80,5 +85,9 @@ contract L1Block {
8085
sstore(hash.slot, calldataload(100)) // bytes32
8186
sstore(batcherHash.slot, calldataload(132)) // bytes32
8287
}
88+
89+
// Flush all queued ethscription proofs after updating block values
90+
// Each proof includes its own block number and timestamp from when it was queued
91+
IEthscriptionsProver(Predeploys.ETHSCRIPTIONS_PROVER).flushAllProofs();
8392
}
8493
}

contracts/test/EthscriptionsFailureHandling.t.sol

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,9 @@ contract FailingProver is EthscriptionsProver {
5454
failMessage = _message;
5555
}
5656

57-
function proveEthscriptionData(bytes32 transactionHash) external override {
58-
if (shouldFail) {
59-
revert(failMessage);
60-
}
61-
// Otherwise do nothing
57+
function queueEthscription(bytes32 txHash) external override {
58+
// For testing, always succeed
59+
// In the new design, queueing doesn't fail and doesn't emit events
6260
}
6361
}
6462

@@ -72,11 +70,6 @@ contract EthscriptionsFailureHandlingTest is TestSetup {
7270
bytes revertData
7371
);
7472

75-
event ProverFailed(
76-
bytes32 indexed transactionHash,
77-
bytes revertData
78-
);
79-
8073
function setUp() public override {
8174
super.setUp();
8275

@@ -138,12 +131,12 @@ contract EthscriptionsFailureHandlingTest is TestSetup {
138131
}
139132

140133
function testCreateEthscriptionWithProverFailure() public {
141-
// Configure Prover to fail
142-
FailingProver(Predeploys.ETHSCRIPTIONS_PROVER).setShouldFail(true);
143-
FailingProver(Predeploys.ETHSCRIPTIONS_PROVER).setFailMessage("Proving failed");
134+
// Note: With the new batched proving design, the prover doesn't fail immediately
135+
// during creation. Instead, ethscriptions are queued for batch proving.
136+
// This test now verifies that creation succeeds and the ethscription is queued.
144137

145138
bytes32 txHash = keccak256("test_tx_2");
146-
string memory dataUri = "data:,Hello World with failing prover";
139+
string memory dataUri = "data:,Hello World with batched prover";
147140

148141
Ethscriptions.CreateEthscriptionParams memory params = createTestParams(
149142
txHash,
@@ -152,14 +145,7 @@ contract EthscriptionsFailureHandlingTest is TestSetup {
152145
false
153146
);
154147

155-
// Expect the ProverFailed event
156-
vm.expectEmit(true, false, false, true);
157-
emit ProverFailed(
158-
txHash,
159-
abi.encodeWithSignature("Error(string)", "Proving failed")
160-
);
161-
162-
// Create ethscription - should succeed despite Prover failure
148+
// Create ethscription - should succeed and queue for proving silently (no event)
163149
uint256 tokenId = ethscriptions.createEthscription(params);
164150

165151
// Verify the ethscription was created successfully

0 commit comments

Comments
 (0)