@aztec/ethereum 0.0.1-commit.d3ec352c → 0.0.1-commit.d58ff9d0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/dest/client.d.ts +25 -3
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +60 -4
  4. package/dest/config.d.ts +45 -73
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +114 -386
  7. package/dest/contracts/chain_state_override.d.ts +78 -0
  8. package/dest/contracts/chain_state_override.d.ts.map +1 -0
  9. package/dest/contracts/chain_state_override.js +137 -0
  10. package/dest/contracts/empire_base.d.ts +2 -1
  11. package/dest/contracts/empire_base.d.ts.map +1 -1
  12. package/dest/contracts/fee_asset_handler.d.ts +6 -5
  13. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  14. package/dest/contracts/fee_asset_handler.js +11 -9
  15. package/dest/contracts/fee_asset_price_oracle.d.ts +105 -0
  16. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  17. package/dest/contracts/fee_asset_price_oracle.js +656 -0
  18. package/dest/contracts/governance.d.ts +110 -33
  19. package/dest/contracts/governance.d.ts.map +1 -1
  20. package/dest/contracts/governance.js +207 -16
  21. package/dest/contracts/governance_proposer.d.ts +34 -1
  22. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  23. package/dest/contracts/governance_proposer.js +447 -10
  24. package/dest/contracts/gse.d.ts +4 -2
  25. package/dest/contracts/gse.d.ts.map +1 -1
  26. package/dest/contracts/gse.js +2 -2
  27. package/dest/contracts/inbox.d.ts +24 -3
  28. package/dest/contracts/inbox.d.ts.map +1 -1
  29. package/dest/contracts/inbox.js +42 -2
  30. package/dest/contracts/index.d.ts +6 -3
  31. package/dest/contracts/index.d.ts.map +1 -1
  32. package/dest/contracts/index.js +5 -2
  33. package/dest/contracts/log.d.ts +13 -0
  34. package/dest/contracts/log.d.ts.map +1 -0
  35. package/dest/contracts/log.js +1 -0
  36. package/dest/contracts/multicall.d.ts +130 -13
  37. package/dest/contracts/multicall.d.ts.map +1 -1
  38. package/dest/contracts/multicall.js +203 -93
  39. package/dest/contracts/outbox.d.ts +64 -0
  40. package/dest/contracts/outbox.d.ts.map +1 -0
  41. package/dest/contracts/outbox.js +113 -0
  42. package/dest/contracts/registry.d.ts +3 -1
  43. package/dest/contracts/registry.d.ts.map +1 -1
  44. package/dest/contracts/registry.js +30 -1
  45. package/dest/contracts/rollup.d.ts +5571 -102
  46. package/dest/contracts/rollup.d.ts.map +1 -1
  47. package/dest/contracts/rollup.js +1076 -192
  48. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +5 -5
  49. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  50. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +18 -13
  51. package/dest/deploy_aztec_l1_contracts.d.ts +267 -0
  52. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -0
  53. package/dest/deploy_aztec_l1_contracts.js +424 -0
  54. package/dest/deploy_l1_contract.d.ts +68 -0
  55. package/dest/deploy_l1_contract.d.ts.map +1 -0
  56. package/dest/deploy_l1_contract.js +312 -0
  57. package/dest/forwarder_proxy.d.ts +32 -0
  58. package/dest/forwarder_proxy.d.ts.map +1 -0
  59. package/dest/forwarder_proxy.js +93 -0
  60. package/dest/foundry_binary.d.ts +13 -0
  61. package/dest/foundry_binary.d.ts.map +1 -0
  62. package/dest/foundry_binary.js +52 -0
  63. package/dest/l1_artifacts.d.ts +19674 -16606
  64. package/dest/l1_artifacts.d.ts.map +1 -1
  65. package/dest/l1_artifacts.js +9 -24
  66. package/dest/l1_contract_addresses.d.ts +61 -65
  67. package/dest/l1_contract_addresses.d.ts.map +1 -1
  68. package/dest/l1_contract_addresses.js +99 -80
  69. package/dest/l1_reader.d.ts +7 -5
  70. package/dest/l1_reader.d.ts.map +1 -1
  71. package/dest/l1_reader.js +15 -7
  72. package/dest/l1_tx_utils/config.d.ts +9 -3
  73. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  74. package/dest/l1_tx_utils/config.js +34 -7
  75. package/dest/l1_tx_utils/constants.d.ts +8 -2
  76. package/dest/l1_tx_utils/constants.d.ts.map +1 -1
  77. package/dest/l1_tx_utils/constants.js +27 -2
  78. package/dest/l1_tx_utils/factory.d.ts +18 -10
  79. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  80. package/dest/l1_tx_utils/factory.js +17 -7
  81. package/dest/l1_tx_utils/fee-strategies/index.d.ts +10 -0
  82. package/dest/l1_tx_utils/fee-strategies/index.d.ts.map +1 -0
  83. package/dest/l1_tx_utils/fee-strategies/index.js +12 -0
  84. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts +8 -0
  85. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts.map +1 -0
  86. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +129 -0
  87. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts +23 -0
  88. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts.map +1 -0
  89. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +191 -0
  90. package/dest/l1_tx_utils/fee-strategies/types.d.ts +51 -0
  91. package/dest/l1_tx_utils/fee-strategies/types.d.ts.map +1 -0
  92. package/dest/l1_tx_utils/fee-strategies/types.js +3 -0
  93. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +41 -0
  94. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -0
  95. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +42 -0
  96. package/dest/l1_tx_utils/index-blobs.d.ts +3 -0
  97. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -0
  98. package/dest/l1_tx_utils/index-blobs.js +2 -0
  99. package/dest/l1_tx_utils/index.d.ts +4 -1
  100. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  101. package/dest/l1_tx_utils/index.js +3 -0
  102. package/dest/l1_tx_utils/interfaces.d.ts +2 -2
  103. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -1
  104. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +252 -0
  105. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -0
  106. package/dest/l1_tx_utils/l1_fee_analyzer.js +568 -0
  107. package/dest/l1_tx_utils/l1_tx_utils.d.ts +26 -7
  108. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  109. package/dest/l1_tx_utils/l1_tx_utils.js +202 -92
  110. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +4 -15
  111. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  112. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +63 -165
  113. package/dest/l1_tx_utils/tx_delayer.d.ts +58 -0
  114. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  115. package/dest/{test → l1_tx_utils}/tx_delayer.js +70 -39
  116. package/dest/l1_tx_utils/types.d.ts +27 -1
  117. package/dest/l1_tx_utils/types.d.ts.map +1 -1
  118. package/dest/l1_tx_utils/types.js +10 -0
  119. package/dest/publisher_manager.d.ts +34 -7
  120. package/dest/publisher_manager.d.ts.map +1 -1
  121. package/dest/publisher_manager.js +119 -8
  122. package/dest/queries.d.ts +14 -3
  123. package/dest/queries.d.ts.map +1 -1
  124. package/dest/queries.js +80 -7
  125. package/dest/test/blob_kzg_warmup.d.ts +19 -0
  126. package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
  127. package/dest/test/blob_kzg_warmup.js +64 -0
  128. package/dest/test/chain_monitor.d.ts +55 -4
  129. package/dest/test/chain_monitor.d.ts.map +1 -1
  130. package/dest/test/chain_monitor.js +105 -5
  131. package/dest/test/eth_cheat_codes.d.ts +49 -6
  132. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  133. package/dest/test/eth_cheat_codes.js +93 -43
  134. package/dest/test/index.d.ts +2 -3
  135. package/dest/test/index.d.ts.map +1 -1
  136. package/dest/test/index.js +1 -2
  137. package/dest/test/rollup_cheat_codes.d.ts +64 -4
  138. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  139. package/dest/test/rollup_cheat_codes.js +109 -8
  140. package/dest/test/start_anvil.d.ts +24 -2
  141. package/dest/test/start_anvil.d.ts.map +1 -1
  142. package/dest/test/start_anvil.js +182 -32
  143. package/dest/test/upgrade_utils.js +2 -2
  144. package/dest/types.d.ts +57 -2
  145. package/dest/types.d.ts.map +1 -1
  146. package/dest/utils.d.ts +24 -4
  147. package/dest/utils.d.ts.map +1 -1
  148. package/dest/utils.js +112 -16
  149. package/package.json +31 -15
  150. package/src/client.ts +66 -2
  151. package/src/config.ts +153 -469
  152. package/src/contracts/README.md +157 -0
  153. package/src/contracts/chain_state_override.ts +200 -0
  154. package/src/contracts/empire_base.ts +1 -1
  155. package/src/contracts/fee_asset_handler.ts +10 -7
  156. package/src/contracts/fee_asset_price_oracle.ts +285 -0
  157. package/src/contracts/governance.ts +279 -13
  158. package/src/contracts/governance_proposer.ts +64 -3
  159. package/src/contracts/gse.ts +7 -2
  160. package/src/contracts/inbox.ts +64 -4
  161. package/src/contracts/index.ts +5 -2
  162. package/src/contracts/log.ts +13 -0
  163. package/src/contracts/multicall.ts +247 -103
  164. package/src/contracts/outbox.ts +132 -0
  165. package/src/contracts/registry.ts +31 -1
  166. package/src/contracts/rollup.ts +789 -154
  167. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +18 -14
  168. package/src/deploy_aztec_l1_contracts.ts +664 -0
  169. package/src/deploy_l1_contract.ts +362 -0
  170. package/src/forwarder_proxy.ts +108 -0
  171. package/src/foundry_binary.ts +57 -0
  172. package/src/l1_artifacts.ts +12 -35
  173. package/src/l1_contract_addresses.ts +120 -92
  174. package/src/l1_reader.ts +25 -10
  175. package/src/l1_tx_utils/config.ts +47 -9
  176. package/src/l1_tx_utils/constants.ts +13 -2
  177. package/src/l1_tx_utils/factory.ts +31 -31
  178. package/src/l1_tx_utils/fee-strategies/index.ts +22 -0
  179. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +163 -0
  180. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +245 -0
  181. package/src/l1_tx_utils/fee-strategies/types.ts +56 -0
  182. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +108 -0
  183. package/src/l1_tx_utils/index-blobs.ts +2 -0
  184. package/src/l1_tx_utils/index.ts +3 -0
  185. package/src/l1_tx_utils/interfaces.ts +1 -1
  186. package/src/l1_tx_utils/l1_fee_analyzer.ts +875 -0
  187. package/src/l1_tx_utils/l1_tx_utils.ts +194 -72
  188. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +77 -211
  189. package/src/{test → l1_tx_utils}/tx_delayer.ts +97 -58
  190. package/src/l1_tx_utils/types.ts +32 -0
  191. package/src/publisher_manager.ts +145 -12
  192. package/src/queries.ts +87 -7
  193. package/src/test/blob_kzg_warmup.ts +65 -0
  194. package/src/test/chain_monitor.ts +150 -4
  195. package/src/test/eth_cheat_codes.ts +84 -47
  196. package/src/test/index.ts +1 -2
  197. package/src/test/rollup_cheat_codes.ts +136 -8
  198. package/src/test/start_anvil.ts +213 -31
  199. package/src/test/upgrade_utils.ts +2 -2
  200. package/src/types.ts +62 -0
  201. package/src/utils.ts +128 -19
  202. package/dest/contracts/empire_slashing_proposer.d.ts +0 -66
  203. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  204. package/dest/contracts/empire_slashing_proposer.js +0 -200
  205. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  206. package/dest/deploy_l1_contracts.d.ts +0 -673
  207. package/dest/deploy_l1_contracts.d.ts.map +0 -1
  208. package/dest/deploy_l1_contracts.js +0 -1491
  209. package/dest/index.d.ts +0 -18
  210. package/dest/index.d.ts.map +0 -1
  211. package/dest/index.js +0 -17
  212. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  213. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  214. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  215. package/dest/test/delayed_tx_utils.d.ts +0 -13
  216. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  217. package/dest/test/delayed_tx_utils.js +0 -28
  218. package/dest/test/tx_delayer.d.ts +0 -36
  219. package/dest/test/tx_delayer.d.ts.map +0 -1
  220. package/src/contracts/empire_slashing_proposer.ts +0 -265
  221. package/src/deploy_l1_contracts.ts +0 -1869
  222. package/src/index.ts +0 -17
  223. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  224. package/src/test/delayed_tx_utils.ts +0 -52
@@ -1,17 +1,28 @@
1
1
  import { CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
+ import { Buffer32 } from '@aztec/foundation/buffer';
3
+ import { Fr } from '@aztec/foundation/curves/bn254';
2
4
  import { memoize } from '@aztec/foundation/decorators';
3
5
  import { EthAddress } from '@aztec/foundation/eth-address';
4
6
  import type { ViemSignature } from '@aztec/foundation/eth-signature';
7
+ import { createLogger } from '@aztec/foundation/log';
8
+ import { makeBackoff, retry } from '@aztec/foundation/retry';
9
+ import { getErrorCause } from '@aztec/foundation/types';
10
+ import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
5
11
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
6
12
  import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
7
13
 
8
14
  import chunk from 'lodash.chunk';
9
15
  import {
10
16
  type Account,
17
+ ContractFunctionRevertedError,
11
18
  type GetContractReturnType,
12
19
  type Hex,
20
+ type Log,
21
+ RpcRequestError,
13
22
  type StateOverride,
14
23
  type WatchContractEventReturnType,
24
+ decodeErrorResult,
25
+ encodeAbiParameters,
15
26
  encodeFunctionData,
16
27
  getContract,
17
28
  hexToBigInt,
@@ -19,16 +30,16 @@ import {
19
30
  } from 'viem';
20
31
 
21
32
  import { getPublicClient } from '../client.js';
22
- import type { DeployL1ContractsReturnType } from '../deploy_l1_contracts.js';
33
+ import type { DeployAztecL1ContractsReturnType } from '../deploy_aztec_l1_contracts.js';
23
34
  import type { L1ContractAddresses } from '../l1_contract_addresses.js';
24
35
  import type { L1ReaderConfig } from '../l1_reader.js';
25
36
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
26
37
  import type { ViemClient } from '../types.js';
27
38
  import { formatViemError } from '../utils.js';
28
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
29
39
  import { GSEContract } from './gse.js';
40
+ import type { L1EventLog } from './log.js';
30
41
  import { SlasherContract } from './slasher_contract.js';
31
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
42
+ import { SlashingProposerContract } from './slashing_proposer.js';
32
43
  import { checkBlockTag } from './utils.js';
33
44
 
34
45
  export type ViemCommitteeAttestation = {
@@ -50,32 +61,29 @@ export type L1RollupContractAddresses = Pick<
50
61
  | 'feeJuiceAddress'
51
62
  | 'stakingAssetAddress'
52
63
  | 'rewardDistributorAddress'
53
- | 'slashFactoryAddress'
54
64
  | 'gseAddress'
55
65
  >;
56
66
 
57
67
  export type EpochProofPublicInputArgs = {
58
68
  previousArchive: `0x${string}`;
59
69
  endArchive: `0x${string}`;
70
+ outHash: `0x${string}`;
60
71
  proverId: `0x${string}`;
61
72
  };
62
73
 
63
74
  export type ViemHeader = {
64
75
  lastArchiveRoot: `0x${string}`;
65
76
  blockHeadersHash: `0x${string}`;
66
- contentCommitment: ViemContentCommitment;
77
+ blobsHash: `0x${string}`;
78
+ inHash: `0x${string}`;
79
+ outHash: `0x${string}`;
67
80
  slotNumber: bigint;
68
81
  timestamp: bigint;
69
82
  coinbase: `0x${string}`;
70
83
  feeRecipient: `0x${string}`;
71
84
  gasFees: ViemGasFees;
72
85
  totalManaUsed: bigint;
73
- };
74
-
75
- export type ViemContentCommitment = {
76
- blobsHash: `0x${string}`;
77
- inHash: `0x${string}`;
78
- outHash: `0x${string}`;
86
+ accumulatedFees: bigint;
79
87
  };
80
88
 
81
89
  export type ViemGasFees = {
@@ -83,16 +91,185 @@ export type ViemGasFees = {
83
91
  feePerL2Gas: bigint;
84
92
  };
85
93
 
86
- export enum SlashingProposerType {
87
- None = 0,
88
- Tally = 1,
89
- Empire = 2,
94
+ /**
95
+ * Status of a validator/attester in the staking system.
96
+ * Matches the Status enum in StakingLib.sol
97
+ */
98
+ export enum AttesterStatus {
99
+ NONE = 0,
100
+ VALIDATING = 1,
101
+ ZOMBIE = 2,
102
+ EXITING = 3,
103
+ }
104
+
105
+ /**
106
+ * Fee header data for a checkpoint
107
+ */
108
+ export type FeeHeader = {
109
+ excessMana: bigint;
110
+ manaUsed: bigint;
111
+ ethPerFeeAsset: bigint;
112
+ congestionCost: bigint;
113
+ proverCost: bigint;
114
+ };
115
+
116
+ /**
117
+ * Checkpoint log data returned from the rollup contract
118
+ */
119
+ export type CheckpointLog = {
120
+ archive: Fr;
121
+ headerHash: Buffer32;
122
+ blobCommitmentsHash: Buffer32;
123
+ attestationsHash: Buffer32;
124
+ payloadDigest: Buffer32;
125
+ slotNumber: SlotNumber;
126
+ feeHeader: FeeHeader;
127
+ };
128
+
129
+ /**
130
+ * L1 fee data (base fee and blob fee)
131
+ */
132
+ export type L1FeeData = {
133
+ baseFee: bigint;
134
+ blobFee: bigint;
135
+ };
136
+
137
+ /** Field offsets within the CompressedTempCheckpointLog struct in Solidity storage. */
138
+ export enum TempCheckpointLogField {
139
+ HeaderHash = 0,
140
+ BlobCommitmentsHash = 1,
141
+ OutHash = 2,
142
+ AttestationsHash = 3,
143
+ PayloadDigest = 4,
144
+ SlotNumber = 5,
145
+ FeeHeader = 6,
146
+ }
147
+
148
+ /**
149
+ * Field-level override input for `tempCheckpointLogs[checkpointNumber]`. Covers the fields the
150
+ * `propose()` path actually reads back. `payloadDigest` is `Buffer32` because it carries an
151
+ * arbitrary `bytes32` value rather than a BN254 scalar. `slotNumber` carries the uint32 portion
152
+ * of the on-chain `CompressedSlot`.
153
+ */
154
+ export type TempCheckpointLogOverrideFields = {
155
+ headerHash?: Fr;
156
+ outHash?: Fr;
157
+ payloadDigest?: Buffer32;
158
+ slotNumber?: SlotNumber;
159
+ feeHeader?: FeeHeader;
160
+ };
161
+
162
+ /** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
163
+ export type ManaMinFeeComponents = {
164
+ sequencerCost: bigint;
165
+ proverCost: bigint;
166
+ congestionCost: bigint;
167
+ congestionMultiplier: bigint;
168
+ };
169
+
170
+ /**
171
+ * Reward configuration for the rollup
172
+ */
173
+ export type RewardConfig = {
174
+ rewardDistributor: EthAddress;
175
+ sequencerBps: bigint;
176
+ booster: EthAddress;
177
+ checkpointReward: bigint;
178
+ };
179
+
180
+ /**
181
+ * Exit information for a validator
182
+ */
183
+ export type Exit = {
184
+ withdrawalId: bigint;
185
+ amount: bigint;
186
+ exitableAt: bigint;
187
+ recipientOrWithdrawer: EthAddress;
188
+ isRecipient: boolean;
189
+ exists: boolean;
190
+ };
191
+
192
+ /**
193
+ * Attester configuration including public key and withdrawer
194
+ */
195
+ export type AttesterConfig = {
196
+ publicKey: {
197
+ x: bigint;
198
+ y: bigint;
199
+ };
200
+ withdrawer: EthAddress;
201
+ };
202
+
203
+ /**
204
+ * Complete view of an attester's state
205
+ */
206
+ export type AttesterView = {
207
+ status: AttesterStatus;
208
+ effectiveBalance: bigint;
209
+ exit: Exit;
210
+ config: AttesterConfig;
211
+ };
212
+
213
+ /**
214
+ * Return for a status call
215
+ */
216
+ export type RollupStatusResponse = {
217
+ provenCheckpointNumber: CheckpointNumber;
218
+ provenArchive: Fr;
219
+ pendingCheckpointNumber: CheckpointNumber;
220
+ pendingArchive: Fr;
221
+ archiveOfMyCheckpoint: Fr;
222
+ };
223
+
224
+ /** Arguments for the CheckpointProposed event. */
225
+ export type CheckpointProposedArgs = {
226
+ checkpointNumber: CheckpointNumber;
227
+ archive: Fr;
228
+ versionedBlobHashes: Buffer[];
229
+ /** Hash of attestations emitted in the CheckpointProposed event. */
230
+ attestationsHash: Buffer32;
231
+ /** Digest of the payload emitted in the CheckpointProposed event. */
232
+ payloadDigest: Buffer32;
233
+ };
234
+
235
+ /** Log type for CheckpointProposed events. */
236
+ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
237
+
238
+ const INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR = 'ValidatorSelection__InsufficientValidatorSetSize';
239
+
240
+ function isValidatorSelectionError(err: unknown, errorName: string): boolean {
241
+ return (
242
+ getErrorCause(err, ContractFunctionRevertedError)?.data?.errorName === errorName ||
243
+ decodeRpcRequestErrorName(err) === errorName
244
+ );
245
+ }
246
+
247
+ function decodeRpcRequestErrorName(err: unknown): string | undefined {
248
+ const data = getErrorCause(err, RpcRequestError)?.data;
249
+ if (!isHexString(data)) {
250
+ return undefined;
251
+ }
252
+
253
+ try {
254
+ return decodeErrorResult({ abi: RollupAbi, data }).errorName;
255
+ } catch {
256
+ return undefined;
257
+ }
258
+ }
259
+
260
+ function isHexString(value: unknown): value is Hex {
261
+ return typeof value === 'string' && value.startsWith('0x');
90
262
  }
91
263
 
92
264
  export class RollupContract {
93
265
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
266
+ private readonly logger = createLogger('ethereum:rollup');
94
267
 
95
268
  private static cachedStfStorageSlot: Hex | undefined;
269
+ private cachedEscapeHatch?: {
270
+ address: EthAddress;
271
+ contract: GetContractReturnType<typeof EscapeHatchAbi, ViemClient>;
272
+ };
96
273
 
97
274
  static get checkBlobStorageSlot(): bigint {
98
275
  const asString = RollupStorage.find(storage => storage.label === 'checkBlob')?.slot;
@@ -106,7 +283,7 @@ export class RollupContract {
106
283
  return (RollupContract.cachedStfStorageSlot ??= keccak256(Buffer.from('aztec.stf.storage', 'utf-8')));
107
284
  }
108
285
 
109
- static getFromL1ContractsValues(deployL1ContractsValues: DeployL1ContractsReturnType) {
286
+ static getFromL1ContractsValues(deployL1ContractsValues: DeployAztecL1ContractsReturnType) {
110
287
  const {
111
288
  l1Client,
112
289
  l1ContractAddresses: { rollupAddress },
@@ -116,7 +293,7 @@ export class RollupContract {
116
293
 
117
294
  static getFromConfig(config: L1ReaderConfig) {
118
295
  const client = getPublicClient(config);
119
- const address = config.l1Contracts.rollupAddress.toString();
296
+ const address = config.rollupAddress.toString();
120
297
  return new RollupContract(client, address);
121
298
  }
122
299
 
@@ -130,8 +307,8 @@ export class RollupContract {
130
307
  this.rollup = getContract({ address, abi: RollupAbi, client });
131
308
  }
132
309
 
133
- getGSE() {
134
- return this.rollup.read.getGSE();
310
+ async getGSE(): Promise<EthAddress> {
311
+ return EthAddress.fromString(await this.rollup.read.getGSE());
135
312
  }
136
313
 
137
314
  public get address() {
@@ -142,54 +319,38 @@ export class RollupContract {
142
319
  return this.rollup;
143
320
  }
144
321
 
145
- public async getSlashingProposer(): Promise<
146
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
147
- > {
322
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
148
323
  const slasher = await this.getSlasherContract();
149
324
  if (!slasher) {
150
325
  return undefined;
151
326
  }
152
327
 
153
328
  const proposerAddress = await slasher.getProposer();
154
- const proposerAbi = [
155
- {
156
- type: 'function',
157
- name: 'SLASHING_PROPOSER_TYPE',
158
- inputs: [],
159
- outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
160
- stateMutability: 'view',
161
- },
162
- ] as const;
163
-
164
- const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
165
- const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
166
- if (proposerType === SlashingProposerType.Tally.valueOf()) {
167
- return new TallySlashingProposerContract(this.client, proposerAddress);
168
- } else if (proposerType === SlashingProposerType.Empire.valueOf()) {
169
- return new EmpireSlashingProposerContract(this.client, proposerAddress);
170
- } else {
171
- throw new Error(`Unknown slashing proposer type: ${proposerType}`);
329
+ if (proposerAddress.isZero()) {
330
+ return undefined;
172
331
  }
332
+
333
+ return new SlashingProposerContract(this.client, proposerAddress);
173
334
  }
174
335
 
175
336
  @memoize
176
- getL1StartBlock() {
337
+ getL1StartBlock(): Promise<bigint> {
177
338
  return this.rollup.read.L1_BLOCK_AT_GENESIS();
178
339
  }
179
340
 
180
341
  @memoize
181
- getL1GenesisTime() {
342
+ getL1GenesisTime(): Promise<bigint> {
182
343
  return this.rollup.read.getGenesisTime();
183
344
  }
184
345
 
185
346
  @memoize
186
- getProofSubmissionEpochs() {
187
- return this.rollup.read.getProofSubmissionEpochs();
347
+ async getProofSubmissionEpochs(): Promise<number> {
348
+ return Number(await this.rollup.read.getProofSubmissionEpochs());
188
349
  }
189
350
 
190
351
  @memoize
191
- getEpochDuration() {
192
- return this.rollup.read.getEpochDuration();
352
+ async getEpochDuration(): Promise<number> {
353
+ return Number(await this.rollup.read.getEpochDuration());
193
354
  }
194
355
 
195
356
  @memoize
@@ -198,68 +359,82 @@ export class RollupContract {
198
359
  }
199
360
 
200
361
  @memoize
201
- getTargetCommitteeSize() {
202
- return this.rollup.read.getTargetCommitteeSize();
362
+ async getTargetCommitteeSize(): Promise<number> {
363
+ return Number(await this.rollup.read.getTargetCommitteeSize());
203
364
  }
204
365
 
205
366
  @memoize
206
- getEjectionThreshold() {
367
+ getEjectionThreshold(): Promise<bigint> {
207
368
  return this.rollup.read.getEjectionThreshold();
208
369
  }
209
370
 
210
371
  @memoize
211
- getLocalEjectionThreshold() {
372
+ getLocalEjectionThreshold(): Promise<bigint> {
212
373
  return this.rollup.read.getLocalEjectionThreshold();
213
374
  }
214
375
 
215
376
  @memoize
216
- getLagInEpochsForValidatorSet() {
217
- return this.rollup.read.getLagInEpochsForValidatorSet();
377
+ async getLagInEpochsForValidatorSet(): Promise<number> {
378
+ return Number(await this.rollup.read.getLagInEpochsForValidatorSet());
218
379
  }
219
380
 
220
381
  @memoize
221
- getLagInEpochsForRandao() {
222
- return this.rollup.read.getLagInEpochsForRandao();
382
+ async getLagInEpochsForRandao(): Promise<number> {
383
+ return Number(await this.rollup.read.getLagInEpochsForRandao());
223
384
  }
224
385
 
225
386
  @memoize
226
- getActivationThreshold() {
387
+ getActivationThreshold(): Promise<bigint> {
227
388
  return this.rollup.read.getActivationThreshold();
228
389
  }
229
390
 
230
391
  @memoize
231
- getExitDelay() {
232
- return this.rollup.read.getExitDelay();
392
+ async getExitDelay(): Promise<number> {
393
+ return Number(await this.rollup.read.getExitDelay());
233
394
  }
234
395
 
235
396
  @memoize
236
- getManaTarget() {
397
+ getManaTarget(): Promise<bigint> {
237
398
  return this.rollup.read.getManaTarget();
238
399
  }
239
400
 
240
401
  @memoize
241
- getProvingCostPerMana() {
402
+ getProvingCostPerMana(): Promise<bigint> {
242
403
  return this.rollup.read.getProvingCostPerManaInEth();
243
404
  }
244
405
 
245
406
  @memoize
246
- getProvingCostPerManaInFeeAsset() {
407
+ getProvingCostPerManaInFeeAsset(): Promise<bigint> {
247
408
  return this.rollup.read.getProvingCostPerManaInFeeAsset();
248
409
  }
249
410
 
250
411
  @memoize
251
- getManaLimit() {
412
+ getManaLimit(): Promise<bigint> {
252
413
  return this.rollup.read.getManaLimit();
253
414
  }
254
415
 
255
416
  @memoize
256
- getVersion() {
417
+ getVersion(): Promise<bigint> {
257
418
  return this.rollup.read.getVersion();
258
419
  }
259
420
 
260
421
  @memoize
261
- async getGenesisArchiveTreeRoot(): Promise<`0x${string}`> {
262
- return await this.rollup.read.archiveAt([0n]);
422
+ async getGenesisArchiveTreeRoot(): Promise<Fr> {
423
+ return Fr.fromString(await this.rollup.read.archiveAt([0n]));
424
+ }
425
+
426
+ @memoize
427
+ async getVkTreeRoot(): Promise<Fr> {
428
+ const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
429
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
430
+ return Fr.fromString(value ?? '0x0');
431
+ }
432
+
433
+ @memoize
434
+ async getProtocolContractsHash(): Promise<Fr> {
435
+ const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
436
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
437
+ return Fr.fromString(value ?? '0x0');
263
438
  }
264
439
 
265
440
  /**
@@ -274,13 +449,25 @@ export class RollupContract {
274
449
  slotDuration: number;
275
450
  epochDuration: number;
276
451
  proofSubmissionEpochs: number;
452
+ targetCommitteeSize: number;
453
+ rollupManaLimit: number;
277
454
  }> {
278
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
455
+ const [
456
+ l1StartBlock,
457
+ l1GenesisTime,
458
+ slotDuration,
459
+ epochDuration,
460
+ proofSubmissionEpochs,
461
+ targetCommitteeSize,
462
+ rollupManaLimit,
463
+ ] = await Promise.all([
279
464
  this.getL1StartBlock(),
280
465
  this.getL1GenesisTime(),
281
466
  this.getSlotDuration(),
282
467
  this.getEpochDuration(),
283
468
  this.getProofSubmissionEpochs(),
469
+ this.getTargetCommitteeSize(),
470
+ this.getManaLimit(),
284
471
  ]);
285
472
  return {
286
473
  l1StartBlock,
@@ -288,30 +475,89 @@ export class RollupContract {
288
475
  slotDuration,
289
476
  epochDuration: Number(epochDuration),
290
477
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
478
+ targetCommitteeSize,
479
+ rollupManaLimit: Number(rollupManaLimit),
291
480
  };
292
481
  }
293
482
 
294
- getSlasherAddress() {
295
- return this.rollup.read.getSlasher();
483
+ async getSlasherAddress(): Promise<EthAddress> {
484
+ return EthAddress.fromString(await this.rollup.read.getSlasher());
485
+ }
486
+
487
+ /**
488
+ * Returns the configured escape hatch contract address, or zero if disabled.
489
+ */
490
+ async getEscapeHatchAddress(): Promise<EthAddress> {
491
+ return EthAddress.fromString(await this.rollup.read.getEscapeHatch());
492
+ }
493
+
494
+ private async getEscapeHatchContract(): Promise<
495
+ GetContractReturnType<typeof EscapeHatchAbi, ViemClient> | undefined
496
+ > {
497
+ const escapeHatchAddress = await this.getEscapeHatchAddress();
498
+ if (escapeHatchAddress.isZero()) {
499
+ return undefined;
500
+ }
501
+
502
+ // Cache the viem contract wrapper since it will be used frequently.
503
+ if (!this.cachedEscapeHatch || !this.cachedEscapeHatch.address.equals(escapeHatchAddress)) {
504
+ this.cachedEscapeHatch = {
505
+ address: escapeHatchAddress,
506
+ contract: getContract({
507
+ address: escapeHatchAddress.toString(),
508
+ abi: EscapeHatchAbi,
509
+ client: this.client,
510
+ }),
511
+ };
512
+ }
513
+
514
+ return this.cachedEscapeHatch.contract;
515
+ }
516
+
517
+ /**
518
+ * Returns whether the escape hatch is open for the given epoch.
519
+ * If escape hatch is not configured, returns false.
520
+ *
521
+ * This function is intentionally defensive: any failure to query the escape hatch
522
+ * (RPC issues, transient errors, etc.) is treated as "closed" to avoid callers
523
+ * needing to sprinkle try/catch everywhere.
524
+ */
525
+ async isEscapeHatchOpen(epoch: EpochNumber): Promise<boolean> {
526
+ try {
527
+ const escapeHatch = await this.getEscapeHatchContract();
528
+ if (!escapeHatch) {
529
+ return false;
530
+ }
531
+
532
+ const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
533
+ return isOpen;
534
+ } catch (err) {
535
+ this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
536
+ epoch: Number(epoch),
537
+ error: err,
538
+ });
539
+ return false;
540
+ }
296
541
  }
297
542
 
298
543
  /**
299
544
  * Returns a SlasherContract instance for interacting with the slasher contract.
300
545
  */
301
546
  async getSlasherContract(): Promise<SlasherContract | undefined> {
302
- const slasherAddress = EthAddress.fromString(await this.getSlasherAddress());
547
+ const slasherAddress = await this.getSlasherAddress();
303
548
  if (slasherAddress.isZero()) {
304
549
  return undefined;
305
550
  }
306
551
  return new SlasherContract(this.client, slasherAddress);
307
552
  }
308
553
 
309
- getOwner() {
310
- return this.rollup.read.owner();
554
+ async getOwner(): Promise<EthAddress> {
555
+ return EthAddress.fromString(await this.rollup.read.owner());
311
556
  }
312
557
 
313
- getActiveAttesterCount() {
314
- return this.rollup.read.getActiveAttesterCount();
558
+ async getActiveAttesterCount(options?: { blockNumber?: bigint }): Promise<number> {
559
+ await checkBlockTag(options?.blockNumber, this.client);
560
+ return Number(await this.rollup.read.getActiveAttesterCount(options));
315
561
  }
316
562
 
317
563
  public async getSlashingProposerAddress() {
@@ -322,31 +568,50 @@ export class RollupContract {
322
568
  return await slasher.getProposer();
323
569
  }
324
570
 
325
- getCheckpointReward() {
571
+ getCheckpointReward(): Promise<bigint> {
326
572
  return this.rollup.read.getCheckpointReward();
327
573
  }
328
574
 
329
- async getCheckpointNumber(): Promise<CheckpointNumber> {
330
- return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
575
+ async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
576
+ await checkBlockTag(options?.blockNumber, this.client);
577
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
578
+ }
579
+
580
+ async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
581
+ await checkBlockTag(options?.blockNumber, this.client);
582
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
331
583
  }
332
584
 
333
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
334
- return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber());
585
+ async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
586
+ await checkBlockTag(options?.blockNumber, this.client);
587
+ return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
335
588
  }
336
589
 
337
- async getSlotNumber(): Promise<SlotNumber> {
338
- return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot());
590
+ async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
591
+ await checkBlockTag(options?.blockNumber, this.client);
592
+ const result = await this.rollup.read.getL1FeesAt([timestamp], options);
593
+ return {
594
+ baseFee: result.baseFee,
595
+ blobFee: result.blobFee,
596
+ };
339
597
  }
340
598
 
341
- getL1FeesAt(timestamp: bigint) {
342
- return this.rollup.read.getL1FeesAt([timestamp]);
599
+ async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
600
+ const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
601
+ return {
602
+ excessMana: result.excessMana,
603
+ manaUsed: result.manaUsed,
604
+ ethPerFeeAsset: result.ethPerFeeAsset,
605
+ congestionCost: result.congestionCost,
606
+ proverCost: result.proverCost,
607
+ };
343
608
  }
344
609
 
345
- getFeeAssetPerEth() {
346
- return this.rollup.read.getFeeAssetPerEth();
610
+ getEthPerFeeAsset(): Promise<bigint> {
611
+ return this.rollup.read.getEthPerFeeAsset();
347
612
  }
348
613
 
349
- async getCommitteeAt(timestamp: bigint): Promise<readonly `0x${string}`[] | undefined> {
614
+ async getCommitteeAt(timestamp: bigint): Promise<EthAddress[] | undefined> {
350
615
  const { result } = await this.client
351
616
  .simulateContract({
352
617
  address: this.address,
@@ -355,28 +620,28 @@ export class RollupContract {
355
620
  args: [timestamp],
356
621
  })
357
622
  .catch(e => {
358
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
623
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
359
624
  return { result: undefined };
360
625
  }
361
626
  throw e;
362
627
  });
363
628
 
364
- return result;
629
+ return result ? result.map(addr => EthAddress.fromString(addr)) : undefined;
365
630
  }
366
631
 
367
- getSampleSeedAt(timestamp: bigint) {
368
- return this.rollup.read.getSampleSeedAt([timestamp]);
632
+ async getSampleSeedAt(timestamp: bigint): Promise<Buffer32> {
633
+ return Buffer32.fromBigInt(await this.rollup.read.getSampleSeedAt([timestamp]));
369
634
  }
370
635
 
371
- getCurrentSampleSeed() {
372
- return this.rollup.read.getCurrentSampleSeed();
636
+ async getCurrentSampleSeed(): Promise<Buffer32> {
637
+ return Buffer32.fromBigInt(await this.rollup.read.getCurrentSampleSeed());
373
638
  }
374
639
 
375
640
  async getCurrentEpoch(): Promise<EpochNumber> {
376
641
  return EpochNumber.fromBigInt(await this.rollup.read.getCurrentEpoch());
377
642
  }
378
643
 
379
- async getCurrentEpochCommittee(): Promise<readonly `0x${string}`[] | undefined> {
644
+ async getCurrentEpochCommittee(): Promise<EthAddress[] | undefined> {
380
645
  const { result } = await this.client
381
646
  .simulateContract({
382
647
  address: this.address,
@@ -385,16 +650,16 @@ export class RollupContract {
385
650
  args: [],
386
651
  })
387
652
  .catch(e => {
388
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
653
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
389
654
  return { result: undefined };
390
655
  }
391
656
  throw e;
392
657
  });
393
658
 
394
- return result;
659
+ return result ? result.map(addr => EthAddress.fromString(addr)) : undefined;
395
660
  }
396
661
 
397
- async getCurrentProposer() {
662
+ async getCurrentProposer(): Promise<EthAddress> {
398
663
  const { result } = await this.client.simulateContract({
399
664
  address: this.address,
400
665
  abi: RollupAbi,
@@ -402,10 +667,10 @@ export class RollupContract {
402
667
  args: [],
403
668
  });
404
669
 
405
- return result;
670
+ return EthAddress.fromString(result);
406
671
  }
407
672
 
408
- async getProposerAt(timestamp: bigint) {
673
+ async getProposerAt(timestamp: bigint): Promise<EthAddress> {
409
674
  const { result } = await this.client.simulateContract({
410
675
  address: this.address,
411
676
  abi: RollupAbi,
@@ -413,11 +678,66 @@ export class RollupContract {
413
678
  args: [timestamp],
414
679
  });
415
680
 
416
- return result;
681
+ return EthAddress.fromString(result);
417
682
  }
418
683
 
419
- getCheckpoint(checkpointNumber: CheckpointNumber) {
420
- return this.rollup.read.getCheckpoint([BigInt(checkpointNumber)]);
684
+ async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
685
+ await checkBlockTag(options?.blockNumber, this.client);
686
+ const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
687
+ return {
688
+ archive: Fr.fromString(result.archive),
689
+ headerHash: Buffer32.fromString(result.headerHash),
690
+ blobCommitmentsHash: Buffer32.fromString(result.blobCommitmentsHash),
691
+ attestationsHash: Buffer32.fromString(result.attestationsHash),
692
+ payloadDigest: Buffer32.fromString(result.payloadDigest),
693
+ slotNumber: SlotNumber.fromBigInt(result.slotNumber),
694
+ feeHeader: {
695
+ excessMana: result.feeHeader.excessMana,
696
+ manaUsed: result.feeHeader.manaUsed,
697
+ ethPerFeeAsset: result.feeHeader.ethPerFeeAsset,
698
+ congestionCost: result.feeHeader.congestionCost,
699
+ proverCost: result.feeHeader.proverCost,
700
+ },
701
+ };
702
+ }
703
+
704
+ /** Returns the pending checkpoint from the rollup contract */
705
+ getPendingCheckpoint() {
706
+ // We retry because of race conditions during prunes: we may get a pending checkpoint number which is immediately
707
+ // reorged out due to a prune happening, causing the subsequent getCheckpoint call to fail. So we try again in that case.
708
+ return retry(
709
+ async () => {
710
+ const pendingCheckpointNumber = await this.getCheckpointNumber();
711
+ const pendingCheckpoint = await this.getCheckpoint(pendingCheckpointNumber);
712
+ return pendingCheckpoint;
713
+ },
714
+ 'getting pending checkpoint',
715
+ makeBackoff([0.5, 0.5, 0.5]),
716
+ );
717
+ }
718
+
719
+ /**
720
+ * Returns the effective pending checkpoint, accounting for potential prunes.
721
+ * When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
722
+ * This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
723
+ * @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
724
+ * @param options - Optional L1 block number to pin the queries to.
725
+ */
726
+ getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
727
+ return retry(
728
+ async () => {
729
+ const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
730
+ const canPrune = await this.canPruneAtTime(timestamp, options);
731
+ if (canPrune) {
732
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
733
+ return await this.getCheckpoint(provenCheckpointNumber, options);
734
+ }
735
+ const pendingCheckpointNumber = await this.getCheckpointNumber(options);
736
+ return await this.getCheckpoint(pendingCheckpointNumber, options);
737
+ },
738
+ 'getting effective pending checkpoint',
739
+ makeBackoff([0.5, 0.5, 0.5]),
740
+ );
421
741
  }
422
742
 
423
743
  async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
@@ -428,16 +748,16 @@ export class RollupContract {
428
748
  };
429
749
  }
430
750
 
431
- getTimestampForSlot(slot: SlotNumber) {
751
+ getTimestampForSlot(slot: SlotNumber): Promise<bigint> {
432
752
  return this.rollup.read.getTimestampForSlot([BigInt(slot)]);
433
753
  }
434
754
 
435
- getEntryQueueLength() {
436
- return this.rollup.read.getEntryQueueLength();
755
+ async getEntryQueueLength(): Promise<number> {
756
+ return Number(await this.rollup.read.getEntryQueueLength());
437
757
  }
438
758
 
439
- getAvailableValidatorFlushes() {
440
- return this.rollup.read.getAvailableValidatorFlushes();
759
+ async getAvailableValidatorFlushes(): Promise<number> {
760
+ return Number(await this.rollup.read.getAvailableValidatorFlushes());
441
761
  }
442
762
 
443
763
  async getNextFlushableEpoch(): Promise<EpochNumber> {
@@ -493,10 +813,11 @@ export class RollupContract {
493
813
  return EpochNumber.fromBigInt(await this.rollup.read.getEpochAtSlot([BigInt(slotNumber)]));
494
814
  }
495
815
 
496
- getEpochProofPublicInputs(
497
- args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly `0x${string}`[], `0x${string}`],
498
- ) {
499
- return this.rollup.read.getEpochProofPublicInputs(args);
816
+ async getEpochProofPublicInputs(
817
+ args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly ViemHeader[], `0x${string}`],
818
+ ): Promise<Fr[]> {
819
+ const result = await this.rollup.read.getEpochProofPublicInputs(args);
820
+ return result.map(Fr.fromString);
500
821
  }
501
822
 
502
823
  public async validateHeader(
@@ -537,14 +858,13 @@ export class RollupContract {
537
858
  * timestamp of the next L1 block
538
859
  * @throws otherwise
539
860
  */
540
- public async canProposeAtNextEthBlock(
861
+ public async canProposeAt(
541
862
  archive: Buffer,
542
863
  account: `0x${string}` | Account,
543
- slotDuration: number,
544
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
864
+ timestamp: bigint,
865
+ stateOverride: StateOverride = [],
545
866
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
546
- const latestBlock = await this.client.getBlock();
547
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
867
+ const timeOfNextL1Slot = timestamp;
548
868
  const who = typeof account === 'string' ? account : account.address;
549
869
 
550
870
  try {
@@ -556,7 +876,7 @@ export class RollupContract {
556
876
  functionName: 'canProposeAtTime',
557
877
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
558
878
  account,
559
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
879
+ stateOverride,
560
880
  });
561
881
 
562
882
  return {
@@ -570,20 +890,32 @@ export class RollupContract {
570
890
  }
571
891
 
572
892
  /**
573
- * Returns a state override that sets the pending checkpoint number to the specified value. Useful for simulations.
574
- * Requires querying the current state of the contract to get the current proven checkpoint number, as they are both
575
- * stored in the same slot. If the argument is undefined, it returns an empty override.
893
+ * Returns a state override that sets the pending and/or proven checkpoint numbers. Useful for simulations.
894
+ * Both values share a single storage slot (pending in the upper 128 bits, proven in the lower 128 bits), so
895
+ * a single combined override is emitted to avoid the second state-diff clobbering the first. The current live
896
+ * value is read once to preserve any half not being overridden. Returns an empty override if neither is set.
897
+ *
898
+ * Throws if the resulting `proven > pending`, which would crash the simulation: `STFLib.canPruneAtTime` calls
899
+ * `getEpochForCheckpoint(proven + 1)` whenever `pending != proven`, and that asserts `_n <= tips.pending`.
576
900
  */
577
- public async makePendingCheckpointNumberOverride(
578
- forcePendingCheckpointNumber: CheckpointNumber | undefined,
579
- ): Promise<StateOverride> {
580
- if (forcePendingCheckpointNumber === undefined) {
901
+ public async makeChainTipsOverride(override: {
902
+ pending?: CheckpointNumber;
903
+ proven?: CheckpointNumber;
904
+ }): Promise<StateOverride> {
905
+ if (override.pending === undefined && override.proven === undefined) {
581
906
  return [];
582
907
  }
583
908
  const slot = RollupContract.stfStorageSlot;
584
909
  const currentValue = await this.client.getStorageAt({ address: this.address, slot });
585
- const currentProvenCheckpointNumber = currentValue ? hexToBigInt(currentValue) & ((1n << 128n) - 1n) : 0n;
586
- const newValue = (BigInt(forcePendingCheckpointNumber) << 128n) | currentProvenCheckpointNumber;
910
+ const currentRaw = currentValue ? hexToBigInt(currentValue) : 0n;
911
+ const currentPending = currentRaw >> 128n;
912
+ const currentProven = currentRaw & ((1n << 128n) - 1n);
913
+ const newPending = override.pending !== undefined ? BigInt(override.pending) : currentPending;
914
+ const newProven = override.proven !== undefined ? BigInt(override.proven) : currentProven;
915
+ if (newProven > newPending) {
916
+ throw new Error(`Invalid chain tips override: proven (${newProven}) > pending (${newPending})`);
917
+ }
918
+ const newValue = (newPending << 128n) | newProven;
587
919
  return [
588
920
  {
589
921
  address: this.address,
@@ -592,6 +924,168 @@ export class RollupContract {
592
924
  ];
593
925
  }
594
926
 
927
+ /**
928
+ * Returns a state override that patches `tempCheckpointLogs[checkpointNumber]` with every field that
929
+ * the L1 contract reads during `propose()` for the checkpoint that builds on top of it (proposer
930
+ * pipelining simulation). Mirrors the writes done by `ProposeLib.addTempCheckpointLog`.
931
+ *
932
+ * `blobCommitmentsHash` and `attestationsHash` are intentionally not exposed here — the propose path
933
+ * never asserts against them, so leaving them at storage zero is harmless.
934
+ */
935
+ public async makeTempCheckpointLogOverride(
936
+ checkpointNumber: CheckpointNumber,
937
+ fields: TempCheckpointLogOverrideFields,
938
+ ): Promise<StateOverride> {
939
+ const constants = await this.getRollupConstants();
940
+ const slotAt = (field: TempCheckpointLogField) =>
941
+ `0x${this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants).toString(16).padStart(64, '0')}` as const;
942
+ const word = (v: bigint) => `0x${v.toString(16).padStart(64, '0')}` as const;
943
+
944
+ const stateDiff: { slot: `0x${string}`; value: `0x${string}` }[] = [];
945
+
946
+ if (fields.headerHash) {
947
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.HeaderHash), value: fields.headerHash.toString() });
948
+ }
949
+ if (fields.outHash) {
950
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.OutHash), value: fields.outHash.toString() });
951
+ }
952
+ if (fields.payloadDigest) {
953
+ stateDiff.push({
954
+ slot: slotAt(TempCheckpointLogField.PayloadDigest),
955
+ value: fields.payloadDigest.toString() as `0x${string}`,
956
+ });
957
+ }
958
+ if (fields.slotNumber !== undefined) {
959
+ // CompressedSlot is uint32 on L1 (SafeCast.toUint32 reverts on overflow). Match that behavior here
960
+ // so a malformed override surfaces immediately rather than silently truncating into a wrong slot.
961
+ const slotNumber = BigInt(fields.slotNumber);
962
+ if (slotNumber < 0n || slotNumber > 0xffffffffn) {
963
+ throw new Error(`slotNumber ${slotNumber} does not fit in uint32`);
964
+ }
965
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.SlotNumber), value: word(slotNumber) });
966
+ }
967
+ if (fields.feeHeader) {
968
+ stateDiff.push({
969
+ slot: slotAt(TempCheckpointLogField.FeeHeader),
970
+ value: word(RollupContract.compressFeeHeader(fields.feeHeader)),
971
+ });
972
+ }
973
+
974
+ if (stateDiff.length === 0) {
975
+ return [];
976
+ }
977
+ return [{ address: this.address, stateDiff }];
978
+ }
979
+
980
+ /**
981
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
982
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
983
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
984
+ */
985
+ public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
986
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
987
+ const archiveSlot = hexToBigInt(
988
+ keccak256(
989
+ encodeAbiParameters(
990
+ [{ type: 'uint256' }, { type: 'uint256' }],
991
+ [BigInt(checkpointNumber), archivesMappingBase],
992
+ ),
993
+ ),
994
+ );
995
+ return [
996
+ {
997
+ address: this.address,
998
+ stateDiff: [
999
+ {
1000
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
1001
+ value: archive.toString(),
1002
+ },
1003
+ ],
1004
+ },
1005
+ ];
1006
+ }
1007
+
1008
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
1009
+ public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
1010
+ type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
1011
+ const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
1012
+ for (const override of overrides) {
1013
+ for (const entry of override) {
1014
+ const key = entry.address.toLowerCase();
1015
+ const existing = byAddress.get(key);
1016
+ if (existing) {
1017
+ existing.stateDiff.push(...(entry.stateDiff ?? []));
1018
+ if (entry.balance !== undefined) {
1019
+ existing.balance = entry.balance;
1020
+ }
1021
+ } else {
1022
+ byAddress.set(key, {
1023
+ address: entry.address,
1024
+ balance: entry.balance,
1025
+ stateDiff: [...(entry.stateDiff ?? [])],
1026
+ });
1027
+ }
1028
+ }
1029
+ }
1030
+ return [...byAddress.values()];
1031
+ }
1032
+
1033
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
1034
+ public static compressFeeHeader(feeHeader: FeeHeader): bigint {
1035
+ const MASK_48_BITS = (1n << 48n) - 1n;
1036
+ const MASK_64_BITS = (1n << 64n) - 1n;
1037
+ const MASK_63_BITS = (1n << 63n) - 1n;
1038
+
1039
+ let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
1040
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
1041
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
1042
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
1043
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
1044
+ value |= 1n << 255n; // preheat flag
1045
+ return value;
1046
+ }
1047
+
1048
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
1049
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
1050
+ public static computeChildFeeHeader(
1051
+ parentFeeHeader: FeeHeader,
1052
+ childManaUsed: bigint,
1053
+ feeAssetPriceModifier: bigint,
1054
+ manaTarget: bigint,
1055
+ ): FeeHeader {
1056
+ const MIN_ETH_PER_FEE_ASSET = 100n;
1057
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
1058
+
1059
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
1060
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
1061
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
1062
+
1063
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
1064
+ const parentPrice =
1065
+ parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
1066
+ let newPrice: bigint;
1067
+ if (feeAssetPriceModifier >= 0n) {
1068
+ newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
1069
+ } else {
1070
+ const absMod = -feeAssetPriceModifier;
1071
+ newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
1072
+ }
1073
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
1074
+ newPrice = MIN_ETH_PER_FEE_ASSET;
1075
+ }
1076
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
1077
+ newPrice = MAX_ETH_PER_FEE_ASSET;
1078
+ }
1079
+
1080
+ return {
1081
+ excessMana,
1082
+ manaUsed: childManaUsed,
1083
+ ethPerFeeAsset: newPrice,
1084
+ congestionCost: 0n,
1085
+ proverCost: 0n,
1086
+ };
1087
+ }
1088
+
595
1089
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
596
1090
  public buildInvalidateBadAttestationRequest(
597
1091
  checkpointNumber: CheckpointNumber,
@@ -601,6 +1095,7 @@ export class RollupContract {
601
1095
  ): L1TxRequest {
602
1096
  return {
603
1097
  to: this.address,
1098
+ abi: RollupAbi,
604
1099
  data: encodeFunctionData({
605
1100
  abi: RollupAbi,
606
1101
  functionName: 'invalidateBadAttestation',
@@ -622,6 +1117,7 @@ export class RollupContract {
622
1117
  ): L1TxRequest {
623
1118
  return {
624
1119
  to: this.address,
1120
+ abi: RollupAbi,
625
1121
  data: encodeFunctionData({
626
1122
  abi: RollupAbi,
627
1123
  functionName: 'invalidateInsufficientAttestations',
@@ -638,90 +1134,140 @@ export class RollupContract {
638
1134
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
639
1135
  }
640
1136
 
641
- getManaBaseFeeAt(timestamp: bigint, inFeeAsset: boolean) {
642
- return this.rollup.read.getManaBaseFeeAt([timestamp, inFeeAsset]);
1137
+ getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
1138
+ return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
1139
+ }
1140
+
1141
+ async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
1142
+ const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
1143
+ return {
1144
+ sequencerCost: result.sequencerCost,
1145
+ proverCost: result.proverCost,
1146
+ congestionCost: result.congestionCost,
1147
+ congestionMultiplier: result.congestionMultiplier,
1148
+ };
643
1149
  }
644
1150
 
645
1151
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
646
1152
  return SlotNumber.fromBigInt(await this.rollup.read.getSlotAt([timestamp]));
647
1153
  }
648
1154
 
649
- async status(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }) {
1155
+ async status(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<RollupStatusResponse> {
650
1156
  await checkBlockTag(options?.blockNumber, this.client);
651
- return this.rollup.read.status([BigInt(checkpointNumber)], options);
1157
+ const result = await this.rollup.read.status([BigInt(checkpointNumber)], options);
1158
+ return {
1159
+ provenCheckpointNumber: CheckpointNumber.fromBigInt(result[0]),
1160
+ provenArchive: Fr.fromString(result[1]),
1161
+ pendingCheckpointNumber: CheckpointNumber.fromBigInt(result[2]),
1162
+ pendingArchive: Fr.fromString(result[3]),
1163
+ archiveOfMyCheckpoint: Fr.fromString(result[4]),
1164
+ };
652
1165
  }
653
1166
 
654
- async canPruneAtTime(timestamp: bigint, options?: { blockNumber?: bigint }) {
1167
+ async canPruneAtTime(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<boolean> {
655
1168
  await checkBlockTag(options?.blockNumber, this.client);
656
1169
  return this.rollup.read.canPruneAtTime([timestamp], options);
657
1170
  }
658
1171
 
659
- archive() {
660
- return this.rollup.read.archive();
1172
+ async archive(): Promise<Fr> {
1173
+ return Fr.fromString(await this.rollup.read.archive());
661
1174
  }
662
1175
 
663
- archiveAt(checkpointNumber: CheckpointNumber) {
664
- return this.rollup.read.archiveAt([BigInt(checkpointNumber)]);
1176
+ async archiveAt(checkpointNumber: CheckpointNumber): Promise<Fr> {
1177
+ return Fr.fromString(await this.rollup.read.archiveAt([BigInt(checkpointNumber)]));
665
1178
  }
666
1179
 
667
- getSequencerRewards(address: Hex | EthAddress) {
1180
+ getSequencerRewards(address: Hex | EthAddress): Promise<bigint> {
668
1181
  if (address instanceof EthAddress) {
669
1182
  address = address.toString();
670
1183
  }
671
1184
  return this.rollup.read.getSequencerRewards([address]);
672
1185
  }
673
1186
 
674
- getSpecificProverRewardsForEpoch(epoch: bigint, prover: Hex | EthAddress) {
1187
+ getSpecificProverRewardsForEpoch(epoch: bigint, prover: Hex | EthAddress): Promise<bigint> {
675
1188
  if (prover instanceof EthAddress) {
676
1189
  prover = prover.toString();
677
1190
  }
678
1191
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
679
1192
  }
680
1193
 
681
- async getAttesters() {
682
- const attesterSize = await this.getActiveAttesterCount();
1194
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
1195
+ // Pin every read to a single L1 block so the attester count and the chunked index reads
1196
+ // observe a consistent set. Without this, the count and each chunk default to `latest` and
1197
+ // can straddle a block boundary (or reorg), yielding an inconsistent or truncated set.
1198
+ const block = await this.client.getBlock();
1199
+ const blockNumber = block.number ?? undefined;
1200
+ const ts = timestamp ?? block.timestamp;
1201
+ const attesterSize = await this.getActiveAttesterCount({ blockNumber });
683
1202
  const gse = new GSEContract(this.client, await this.getGSE());
684
- const ts = (await this.client.getBlock()).timestamp;
685
-
686
- const indices = Array.from({ length: Number(attesterSize) }, (_, i) => BigInt(i));
1203
+ const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
687
1204
  const chunks = chunk(indices, 1000);
688
1205
 
689
- return (await Promise.all(chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk)))).flat();
1206
+ const results = await Promise.all(
1207
+ chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk, { blockNumber })),
1208
+ );
1209
+ return results.flat().map(addr => EthAddress.fromString(addr));
690
1210
  }
691
1211
 
692
- getAttesterView(address: Hex | EthAddress) {
1212
+ async getAttesterView(address: Hex | EthAddress): Promise<AttesterView> {
693
1213
  if (address instanceof EthAddress) {
694
1214
  address = address.toString();
695
1215
  }
696
- return this.rollup.read.getAttesterView([address]);
1216
+ const result = await this.rollup.read.getAttesterView([address]);
1217
+ return {
1218
+ status: result.status as AttesterStatus,
1219
+ effectiveBalance: result.effectiveBalance,
1220
+ exit: {
1221
+ withdrawalId: result.exit.withdrawalId,
1222
+ amount: result.exit.amount,
1223
+ exitableAt: result.exit.exitableAt,
1224
+ recipientOrWithdrawer: EthAddress.fromString(result.exit.recipientOrWithdrawer),
1225
+ isRecipient: result.exit.isRecipient,
1226
+ exists: result.exit.exists,
1227
+ },
1228
+ config: {
1229
+ publicKey: {
1230
+ x: result.config.publicKey.x,
1231
+ y: result.config.publicKey.y,
1232
+ },
1233
+ withdrawer: EthAddress.fromString(result.config.withdrawer),
1234
+ },
1235
+ };
697
1236
  }
698
1237
 
699
- getStatus(address: Hex | EthAddress) {
1238
+ async getStatus(address: Hex | EthAddress): Promise<AttesterStatus> {
700
1239
  if (address instanceof EthAddress) {
701
1240
  address = address.toString();
702
1241
  }
703
- return this.rollup.read.getStatus([address]);
1242
+ return (await this.rollup.read.getStatus([address])) as AttesterStatus;
704
1243
  }
705
1244
 
706
- getBlobCommitmentsHash(checkpointNumber: CheckpointNumber) {
707
- return this.rollup.read.getBlobCommitmentsHash([BigInt(checkpointNumber)]);
1245
+ async getBlobCommitmentsHash(checkpointNumber: CheckpointNumber): Promise<Buffer32> {
1246
+ return Buffer32.fromString(await this.rollup.read.getBlobCommitmentsHash([BigInt(checkpointNumber)]));
708
1247
  }
709
1248
 
710
- getCurrentBlobCommitmentsHash() {
711
- return this.rollup.read.getCurrentBlobCommitmentsHash();
1249
+ async getCurrentBlobCommitmentsHash(): Promise<Buffer32> {
1250
+ return Buffer32.fromString(await this.rollup.read.getCurrentBlobCommitmentsHash());
712
1251
  }
713
1252
 
714
- getStakingAsset() {
715
- return this.rollup.read.getStakingAsset();
1253
+ async getStakingAsset(): Promise<EthAddress> {
1254
+ return EthAddress.fromString(await this.rollup.read.getStakingAsset());
716
1255
  }
717
1256
 
718
- getRewardConfig() {
719
- return this.rollup.read.getRewardConfig();
1257
+ async getRewardConfig(): Promise<RewardConfig> {
1258
+ const result = await this.rollup.read.getRewardConfig();
1259
+ return {
1260
+ rewardDistributor: EthAddress.fromString(result.rewardDistributor),
1261
+ sequencerBps: BigInt(result.sequencerBps),
1262
+ booster: EthAddress.fromString(result.booster),
1263
+ checkpointReward: result.checkpointReward,
1264
+ };
720
1265
  }
721
1266
 
722
1267
  setupEpoch(l1TxUtils: L1TxUtils) {
723
1268
  return l1TxUtils.sendAndMonitorTransaction({
724
1269
  to: this.address,
1270
+ abi: RollupAbi,
725
1271
  data: encodeFunctionData({
726
1272
  abi: RollupAbi,
727
1273
  functionName: 'setupEpoch',
@@ -733,6 +1279,7 @@ export class RollupContract {
733
1279
  vote(l1TxUtils: L1TxUtils, proposalId: bigint) {
734
1280
  return l1TxUtils.sendAndMonitorTransaction({
735
1281
  to: this.address,
1282
+ abi: RollupAbi,
736
1283
  data: encodeFunctionData({
737
1284
  abi: RollupAbi,
738
1285
  functionName: 'vote',
@@ -760,7 +1307,7 @@ export class RollupContract {
760
1307
  }
761
1308
 
762
1309
  public listenToCheckpointInvalidated(
763
- callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
1310
+ callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
764
1311
  ): WatchContractEventReturnType {
765
1312
  return this.rollup.watchEvent.CheckpointInvalidated(
766
1313
  {},
@@ -769,7 +1316,7 @@ export class RollupContract {
769
1316
  for (const log of logs) {
770
1317
  const args = log.args;
771
1318
  if (args.checkpointNumber !== undefined) {
772
- callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
1319
+ callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
773
1320
  }
774
1321
  }
775
1322
  },
@@ -801,4 +1348,92 @@ export class RollupContract {
801
1348
  },
802
1349
  );
803
1350
  }
1351
+
1352
+ /**
1353
+ * Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
1354
+ * The Rollup inherits from Ownable and emits this event in its constructor, so the event
1355
+ * is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
1356
+ * probe to detect RPC nodes that prune historical logs.
1357
+ */
1358
+ async getOwnershipTransferredEventsAtDeploy() {
1359
+ const l1StartBlock = await this.getL1StartBlock();
1360
+ return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
1361
+ }
1362
+
1363
+ /** Fetches CheckpointProposed events within the given block range. */
1364
+ async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1365
+ const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
1366
+ return logs
1367
+ .filter(log => log.blockNumber! >= fromBlock && log.blockNumber! <= toBlock)
1368
+ .map(log => ({
1369
+ l1BlockNumber: log.blockNumber!,
1370
+ l1BlockHash: Buffer32.fromString(log.blockHash!),
1371
+ l1TransactionHash: log.transactionHash!,
1372
+ args: {
1373
+ checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
1374
+ archive: Fr.fromString(log.args.archive!),
1375
+ versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
1376
+ attestationsHash: (() => {
1377
+ if (!log.args.attestationsHash) {
1378
+ throw new Error(
1379
+ `CheckpointProposed event missing attestationsHash for checkpoint ${log.args.checkpointNumber}`,
1380
+ );
1381
+ }
1382
+ return Buffer32.fromString(log.args.attestationsHash);
1383
+ })(),
1384
+ payloadDigest: (() => {
1385
+ if (!log.args.payloadDigest) {
1386
+ throw new Error(
1387
+ `CheckpointProposed event missing payloadDigest for checkpoint ${log.args.checkpointNumber}`,
1388
+ );
1389
+ }
1390
+ return Buffer32.fromString(log.args.payloadDigest);
1391
+ })(),
1392
+ },
1393
+ }));
1394
+ }
1395
+
1396
+ /** Packs pending and proven checkpoint numbers into the chain tips storage format. */
1397
+ static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
1398
+ return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
1399
+ }
1400
+
1401
+ /** Storage slot for the chain tips (offset 0 within the STF storage struct). */
1402
+ static get chainTipsStorageSlot(): bigint {
1403
+ return BigInt(RollupContract.stfStorageSlot);
1404
+ }
1405
+
1406
+ /**
1407
+ * Computes the storage slot for a field within a tempCheckpointLog entry.
1408
+ * @param checkpointNumber - The checkpoint number
1409
+ * @param field - The field within the CompressedTempCheckpointLog struct
1410
+ */
1411
+ async getTempCheckpointLogStorageSlot(
1412
+ checkpointNumber: CheckpointNumber,
1413
+ field: TempCheckpointLogField,
1414
+ ): Promise<bigint> {
1415
+ const [epochDuration, proofSubmissionEpochs] = await Promise.all([
1416
+ this.getEpochDuration(),
1417
+ this.getProofSubmissionEpochs(),
1418
+ ]);
1419
+ return this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, { epochDuration, proofSubmissionEpochs });
1420
+ }
1421
+
1422
+ private computeTempCheckpointLogStorageSlot(
1423
+ checkpointNumber: CheckpointNumber,
1424
+ field: TempCheckpointLogField,
1425
+ constants: { epochDuration: number; proofSubmissionEpochs: number },
1426
+ ): bigint {
1427
+ const fieldOffset = BigInt(field);
1428
+ const { epochDuration, proofSubmissionEpochs } = constants;
1429
+ const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
1430
+ const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
1431
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
1432
+ const entryBase = BigInt(
1433
+ keccak256(
1434
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
1435
+ ),
1436
+ );
1437
+ return entryBase + fieldOffset;
1438
+ }
804
1439
  }