Skip to content

Commit a1620eb

Browse files
decofedankrad
andauthored
spec: add deposit counter for transparent deposit confirmation (#355)
* spec: add deposit counter for transparent deposit confirmation Add a monotonic deposit counter alongside the existing hash chain so that checking whether a deposit was processed is a simple integer comparison (lastProcessedDepositNumber >= myDepositNumber) instead of requiring hash chain reconstruction. - Add depositCount and lastProcessedDepositNumber storage to ZonePortal - Add prevDepositNumber/nextDepositNumber to DepositQueueTransition - Emit depositNumber in DepositMade, EncryptedDepositMade, BounceBack events - Emit lastProcessedDepositNumber in BatchSubmitted and TempoAdvanced events - Add processedDepositNumber storage to ZoneInbox - Document deposit counter and status checking in overview.md Made-with: Cursor * style: fix forge fmt formatting in test files Made-with: Cursor * fix: update Rust ABI bindings and zone monitor for deposit counter Align Rust code with the updated Solidity spec: - Add prevDepositNumber/nextDepositNumber to DepositQueueTransition struct - Add depositNumber to DepositMade, EncryptedDepositMade, BounceBack events - Add lastProcessedDepositNumber to BatchSubmitted and TempoAdvanced events - Add depositCount, lastProcessedDepositNumber views to ZonePortal ABI - Add processedDepositNumber view to ZoneInbox ABI - Track prev_processed_deposit_number in ZoneMonitor and BatchData - Read processedDepositNumber in fetch_block_snapshot Co-Authored-By: dankrad <[email protected]> * fix: update ZoneInbox bytecode in zone-test-genesis Recompile ZoneInbox with the deposit counter changes so the embedded TempoAdvanced event signature matches the updated ABI. Co-Authored-By: dankrad <[email protected]> * fix: set config and tempoState immutables in ZoneInbox genesis bytecode The raw forge deployedBytecode has all immutables zeroed. The genesis patching code replaces 32-byte zero needles with the portal address, so config and tempoState must be pre-set to avoid being overwritten. Co-Authored-By: dankrad <[email protected]> * fix: recompile ZoneInbox bytecode in zone-test-genesis The genesis bytecode was compiled from an older version of ZoneInbox.sol that didn't include the deposit counter changes, causing a function selector mismatch. The Rust ABI generates selector 0xd01e8d31 for advanceTempo, but the old genesis bytecode didn't have that selector, so all advanceTempo calls hit the fallback and reverted with empty output. Also fixes the advance_tempo test's local sol! declaration which was missing the EnabledToken[] parameter. Co-Authored-By: dankrad <[email protected]> Amp-Thread-ID: https://ampcode.com/threads/T-019d5485-9b88-71df-8d85-a0bafe2f6815 --------- Co-authored-by: Dankrad Feist <[email protected]> Co-authored-by: dankrad <[email protected]>
1 parent 5b9a137 commit a1620eb

13 files changed

Lines changed: 512 additions & 67 deletions

File tree

crates/primitives/src/abi.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ macro_rules! define_abi {
8484
struct DepositQueueTransition {
8585
bytes32 prevProcessedHash;
8686
bytes32 nextProcessedHash;
87+
uint64 prevDepositNumber;
88+
uint64 nextDepositNumber;
8789
}
8890

8991
#[derive(Debug)]
@@ -120,7 +122,8 @@ macro_rules! define_abi {
120122
address to,
121123
uint128 netAmount,
122124
uint128 fee,
123-
bytes32 memo
125+
bytes32 memo,
126+
uint64 depositNumber
124127
);
125128

126129
#[derive(Debug)]
@@ -135,7 +138,8 @@ macro_rules! define_abi {
135138
uint8 ephemeralPubkeyYParity,
136139
bytes ciphertext,
137140
bytes12 nonce,
138-
bytes16 tag
141+
bytes16 tag,
142+
uint64 depositNumber
139143
);
140144

141145
/// Event emitted when a new TIP-20 token is enabled for bridging.
@@ -148,7 +152,8 @@ macro_rules! define_abi {
148152
uint64 indexed withdrawalBatchIndex,
149153
bytes32 nextProcessedDepositQueueHash,
150154
bytes32 nextBlockHash,
151-
bytes32 withdrawalQueueHash
155+
bytes32 withdrawalQueueHash,
156+
uint64 lastProcessedDepositNumber
152157
);
153158

154159
#[derive(Debug)]
@@ -159,7 +164,8 @@ macro_rules! define_abi {
159164
bytes32 indexed newCurrentDepositQueueHash,
160165
address indexed fallbackRecipient,
161166
address token,
162-
uint128 amount
167+
uint128 amount,
168+
uint64 depositNumber
163169
);
164170

165171
#[derive(Debug)]
@@ -201,6 +207,8 @@ macro_rules! define_abi {
201207
function withdrawalQueueSlot(uint256 slot) external view returns (bytes32);
202208
function genesisTempoBlockNumber() external view returns (uint64);
203209
function calculateDepositFee() external view returns (uint128 fee);
210+
function depositCount() external view returns (uint64);
211+
function lastProcessedDepositNumber() external view returns (uint64);
204212

205213
// -- State-changing functions --
206214

@@ -448,7 +456,8 @@ macro_rules! define_abi {
448456
bytes32 indexed tempoBlockHash,
449457
uint64 indexed tempoBlockNumber,
450458
uint256 depositsProcessed,
451-
bytes32 newProcessedDepositQueueHash
459+
bytes32 newProcessedDepositQueueHash,
460+
uint64 lastProcessedDepositNumber
452461
);
453462

454463
#[derive(Debug)]
@@ -489,6 +498,7 @@ macro_rules! define_abi {
489498
error ExtraDecryptionData();
490499
error InvalidSharedSecretProof();
491500
function processedDepositQueueHash() external view returns (bytes32);
501+
function processedDepositNumber() external view returns (uint64);
492502
function tempoPortal() external view returns (address);
493503
function tempoState() external view returns (address);
494504
function config() external view returns (address);

crates/tempo-zone/src/batch.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ pub struct BatchData {
7878
pub prev_processed_deposit_hash: B256,
7979
/// Deposit queue: where the zone processed up to.
8080
pub next_processed_deposit_hash: B256,
81+
/// Deposit counter at the start of processing.
82+
pub prev_deposit_number: u64,
83+
/// Deposit counter after processing.
84+
pub next_deposit_number: u64,
8185
/// Withdrawal queue hash for this batch (`B256::ZERO` if no withdrawals).
8286
pub withdrawal_queue_hash: B256,
8387
}
@@ -167,6 +171,8 @@ impl BatchSubmitter {
167171
let deposit_transition = DepositQueueTransition {
168172
prevProcessedHash: batch.prev_processed_deposit_hash,
169173
nextProcessedHash: batch.next_processed_deposit_hash,
174+
prevDepositNumber: batch.prev_deposit_number,
175+
nextDepositNumber: batch.next_deposit_number,
170176
};
171177

172178
let anchor_mode = self.resolve_anchor_mode(batch.tempo_block_number).await?;
@@ -1003,6 +1009,8 @@ pub(crate) struct ZoneBlockSnapshot {
10031009
pub tempo_block_number: u64,
10041010
/// Cumulative hash of all deposits processed by the zone up to this block.
10051011
pub processed_deposit_hash: B256,
1012+
/// Total number of deposits processed by the zone up to this block.
1013+
pub processed_deposit_number: u64,
10061014
/// Zone L2 block hash.
10071015
pub block_hash: B256,
10081016
}
@@ -1103,6 +1111,7 @@ mod tests {
11031111
nextProcessedDepositQueueHash: B256::ZERO,
11041112
nextBlockHash: B256::ZERO,
11051113
withdrawalQueueHash: withdrawal_queue_hash,
1114+
lastProcessedDepositNumber: 0,
11061115
}
11071116
}
11081117

crates/tempo-zone/src/l1.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,6 +1997,7 @@ mod tests {
19971997
fallbackRecipient: fallback_recipient,
19981998
token,
19991999
amount: 123_456,
2000+
depositNumber: 1,
20002001
};
20012002
let log = Log {
20022003
inner: alloy_primitives::Log {

crates/tempo-zone/src/zonemonitor.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ pub struct ZoneMonitor {
112112
/// Deposit queue hash from the previous block, used to construct the
113113
/// [`DepositQueueTransition`](crate::abi::DepositQueueTransition) for each batch.
114114
prev_processed_deposit_hash: B256,
115+
/// Deposit counter from the previous batch, used to construct the
116+
/// [`DepositQueueTransition`](crate::abi::DepositQueueTransition) for each batch.
117+
prev_processed_deposit_number: u64,
115118
/// Previous zone block hash, used as `prev_block_hash` in [`BatchData`].
116119
/// Initialized from the portal's on-chain `blockHash()` at startup.
117120
prev_zone_block_hash: B256,
@@ -208,11 +211,14 @@ impl ZoneMonitor {
208211
B256::ZERO,
209212
)
210213
.await;
214+
let prev_processed_deposit_number =
215+
Self::read_processed_deposit_number_at_block(&inbox, last_submitted_zone_block).await;
211216

212217
info!(
213218
last_submitted_zone_block,
214219
%prev_zone_block_hash,
215220
%prev_processed_deposit_hash,
221+
prev_processed_deposit_number,
216222
portal_withdrawal_queue_tail,
217223
"Initialized from portal state"
218224
);
@@ -238,6 +244,7 @@ impl ZoneMonitor {
238244
repair_notify,
239245
last_submitted_zone_block,
240246
prev_processed_deposit_hash,
247+
prev_processed_deposit_number,
241248
prev_zone_block_hash,
242249
portal_withdrawal_queue_tail,
243250
latest_observed_zone_block: last_submitted_zone_block,
@@ -486,6 +493,8 @@ impl ZoneMonitor {
486493
next_block_hash: end_state.block_hash,
487494
prev_processed_deposit_hash: self.prev_processed_deposit_hash,
488495
next_processed_deposit_hash: end_state.processed_deposit_hash,
496+
prev_deposit_number: self.prev_processed_deposit_number,
497+
next_deposit_number: end_state.processed_deposit_number,
489498
withdrawal_queue_hash,
490499
};
491500

@@ -560,6 +569,8 @@ impl ZoneMonitor {
560569
next_block_hash: step_state.block_hash,
561570
prev_processed_deposit_hash: self.prev_processed_deposit_hash,
562571
next_processed_deposit_hash: step_state.processed_deposit_hash,
572+
prev_deposit_number: self.prev_processed_deposit_number,
573+
next_deposit_number: step_state.processed_deposit_number,
563574
withdrawal_queue_hash,
564575
};
565576

@@ -586,14 +597,19 @@ impl ZoneMonitor {
586597
async fn fetch_block_snapshot(&self, to: u64) -> Result<ZoneBlockSnapshot> {
587598
let tempo_call = self.tempo_state.tempoBlockNumber().block(to.into());
588599
let deposit_call = self.inbox.processedDepositQueueHash().block(to.into());
600+
let deposit_number_call = self.inbox.processedDepositNumber().block(to.into());
589601
let block_fut = async {
590602
self.provider
591603
.get_block_by_number(to.into())
592604
.await
593605
.map_err(Into::into)
594606
};
595-
let (tempo_block_number, processed_deposit_hash, block) =
596-
tokio::try_join!(tempo_call.call(), deposit_call.call(), block_fut,)?;
607+
let (tempo_block_number, processed_deposit_hash, processed_deposit_number, block) = tokio::try_join!(
608+
tempo_call.call(),
609+
deposit_call.call(),
610+
deposit_number_call.call(),
611+
block_fut,
612+
)?;
597613

598614
let block_hash = block
599615
.ok_or_else(|| eyre::eyre!("zone block {to} not found"))?
@@ -603,6 +619,7 @@ impl ZoneMonitor {
603619
Ok(ZoneBlockSnapshot {
604620
tempo_block_number,
605621
processed_deposit_hash,
622+
processed_deposit_number,
606623
block_hash,
607624
})
608625
}
@@ -673,6 +690,7 @@ impl ZoneMonitor {
673690
// Only advance local state on success.
674691
self.prev_zone_block_hash = batch_data.next_block_hash;
675692
self.prev_processed_deposit_hash = batch_data.next_processed_deposit_hash;
693+
self.prev_processed_deposit_number = batch_data.next_deposit_number;
676694
self.last_submitted_zone_block = last_zone_block;
677695
self.metrics
678696
.latest_zone_block_submitted_to_l1
@@ -772,6 +790,11 @@ impl ZoneMonitor {
772790
self.prev_processed_deposit_hash,
773791
)
774792
.await;
793+
let deposit_number = Self::read_processed_deposit_number_at_block(
794+
&self.inbox,
795+
last_submitted_zone_block,
796+
)
797+
.await;
775798

776799
warn!(
777800
old_prev_block_hash = %old_hash,
@@ -784,12 +807,14 @@ impl ZoneMonitor {
784807
store_first_slot_before_resync,
785808
store_last_slot_before_resync,
786809
%deposit_hash,
810+
deposit_number,
787811
"Resynced from portal and zone state"
788812
);
789813
self.prev_zone_block_hash = portal_hash;
790814
self.portal_withdrawal_queue_tail = portal_tail;
791815
self.last_submitted_zone_block = last_submitted_zone_block;
792816
self.prev_processed_deposit_hash = deposit_hash;
817+
self.prev_processed_deposit_number = deposit_number;
793818
self.metrics
794819
.latest_zone_block_submitted_to_l1
795820
.set(last_submitted_zone_block as f64);
@@ -875,6 +900,32 @@ impl ZoneMonitor {
875900
}
876901
}
877902

903+
async fn read_processed_deposit_number_at_block(
904+
inbox: &ZoneInbox::ZoneInboxInstance<DynProvider<TempoNetwork>, TempoNetwork>,
905+
zone_block_number: u64,
906+
) -> u64 {
907+
if zone_block_number == 0 {
908+
return 0;
909+
}
910+
911+
match inbox
912+
.processedDepositNumber()
913+
.block(zone_block_number.into())
914+
.call()
915+
.await
916+
{
917+
Ok(n) => n,
918+
Err(e) => {
919+
warn!(
920+
zone_block_number,
921+
error = %e,
922+
"Failed to read processedDepositNumber at portal-confirmed zone block"
923+
);
924+
0
925+
}
926+
}
927+
}
928+
878929
fn record_observed_zone_block(&mut self, latest_zone_block: u64) {
879930
self.latest_observed_zone_block = latest_zone_block;
880931
self.metrics
@@ -1016,6 +1067,7 @@ mod tests {
10161067
repair_notify: Arc::new(Notify::new()),
10171068
last_submitted_zone_block: 10,
10181069
prev_processed_deposit_hash: B256::repeat_byte(0xaa),
1070+
prev_processed_deposit_number: 0,
10191071
prev_zone_block_hash: B256::repeat_byte(0xbb),
10201072
portal_withdrawal_queue_tail: 3,
10211073
latest_observed_zone_block: 50,
@@ -1205,6 +1257,8 @@ mod tests {
12051257
next_block_hash: B256::repeat_byte(0x55),
12061258
prev_processed_deposit_hash: B256::repeat_byte(0x77),
12071259
next_processed_deposit_hash: B256::repeat_byte(0x66),
1260+
prev_deposit_number: 0,
1261+
next_deposit_number: 0,
12081262
withdrawal_queue_hash: B256::ZERO,
12091263
};
12101264

crates/tempo-zone/tests/advance_tempo.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const ZONE_CONFIG_ADDRESS: Address = address!("0x1c00000000000000000000000000000
2323
const DEPLOYER: Address = address!("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
2424

2525
sol! {
26-
function advanceTempo(bytes calldata header, QueuedDeposit[] calldata deposits, DecryptionData[] calldata decryptions);
26+
function advanceTempo(bytes calldata header, QueuedDeposit[] calldata deposits, DecryptionData[] calldata decryptions, EnabledToken[] calldata enabledTokens);
2727
function config() external view returns (address);
2828
function tempoBlockHash() external view returns (bytes32);
2929

@@ -42,6 +42,12 @@ sol! {
4242
bytes32 memo;
4343
ChaumPedersenProof cpProof;
4444
}
45+
struct EnabledToken {
46+
address token;
47+
string name;
48+
string symbol;
49+
string currency;
50+
}
4551
}
4652

4753
/// Load a Foundry artifact's creation bytecode from the specs output directory.
@@ -370,6 +376,7 @@ fn advance_tempo_repro() {
370376
header: Bytes::from(next_header_rlp),
371377
deposits: vec![],
372378
decryptions: vec![],
379+
enabledTokens: vec![],
373380
}
374381
.abi_encode();
375382

crates/tempo-zone/tests/assets/zone-test-genesis.json

Lines changed: 194 additions & 1 deletion
Large diffs are not rendered by default.

docs/specs/src/zone/IZone.sol

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,13 @@ struct BlockTransition {
4545
/// @dev The proof reads currentDepositQueueHash from Tempo state to validate
4646
/// that nextProcessedHash is an ancestor of (or equal to) currentDepositQueueHash.
4747
/// This allows partial deposit processing.
48+
/// The deposit numbers mirror the hash chain for easy status checking:
49+
/// a deposit with number N is confirmed once lastProcessedDepositNumber >= N.
4850
struct DepositQueueTransition {
4951
bytes32 prevProcessedHash; // where proof starts (verified against zone state)
5052
bytes32 nextProcessedHash; // where zone processed up to (proof output)
53+
uint64 prevDepositNumber; // deposit counter at prevProcessedHash
54+
uint64 nextDepositNumber; // deposit counter at nextProcessedHash
5155
}
5256

5357
/// @notice Deposit type discriminator for the unified deposit queue
@@ -495,14 +499,16 @@ interface IZonePortal {
495499
address to,
496500
uint128 netAmount,
497501
uint128 fee,
498-
bytes32 memo
502+
bytes32 memo,
503+
uint64 depositNumber
499504
);
500505

501506
event BatchSubmitted(
502507
uint64 indexed withdrawalBatchIndex,
503508
bytes32 nextProcessedDepositQueueHash,
504509
bytes32 nextBlockHash,
505-
bytes32 withdrawalQueueHash
510+
bytes32 withdrawalQueueHash,
511+
uint64 lastProcessedDepositNumber
506512
);
507513

508514
event WithdrawalProcessed(
@@ -513,7 +519,8 @@ interface IZonePortal {
513519
bytes32 indexed newCurrentDepositQueueHash,
514520
address indexed fallbackRecipient,
515521
address token,
516-
uint128 amount
522+
uint128 amount,
523+
uint64 depositNumber
517524
);
518525

519526
event SequencerTransferStarted(
@@ -533,7 +540,8 @@ interface IZonePortal {
533540
uint8 ephemeralPubkeyYParity,
534541
bytes ciphertext,
535542
bytes12 nonce,
536-
bytes16 tag
543+
bytes16 tag,
544+
uint64 depositNumber
537545
);
538546

539547
/// @notice Emitted when sequencer updates their encryption key
@@ -850,7 +858,8 @@ interface IZoneInbox {
850858
bytes32 indexed tempoBlockHash,
851859
uint64 indexed tempoBlockNumber,
852860
uint256 depositsProcessed,
853-
bytes32 newProcessedDepositQueueHash
861+
bytes32 newProcessedDepositQueueHash,
862+
uint64 lastProcessedDepositNumber
854863
);
855864

856865
event DepositProcessed(

0 commit comments

Comments
 (0)