@aztec/ethereum 0.0.1-commit.42ee6df9b → 0.0.1-commit.431c48d
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.
- package/dest/client.d.ts +19 -3
- package/dest/client.d.ts.map +1 -1
- package/dest/client.js +52 -3
- package/dest/config.d.ts +15 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +29 -9
- package/dest/contracts/chain_state_override.d.ts +78 -0
- package/dest/contracts/chain_state_override.d.ts.map +1 -0
- package/dest/contracts/chain_state_override.js +137 -0
- package/dest/contracts/empire_base.d.ts +1 -3
- package/dest/contracts/empire_base.d.ts.map +1 -1
- package/dest/contracts/fee_asset_price_oracle.d.ts +6 -2
- package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -1
- package/dest/contracts/fee_asset_price_oracle.js +11 -6
- package/dest/contracts/governance.d.ts +135 -35
- package/dest/contracts/governance.d.ts.map +1 -1
- package/dest/contracts/governance.js +226 -15
- package/dest/contracts/governance_proposer.d.ts +33 -4
- package/dest/contracts/governance_proposer.d.ts.map +1 -1
- package/dest/contracts/governance_proposer.js +52 -13
- package/dest/contracts/gse.d.ts +4 -2
- package/dest/contracts/gse.d.ts.map +1 -1
- package/dest/contracts/gse.js +2 -2
- package/dest/contracts/inbox.d.ts +3 -3
- package/dest/contracts/inbox.d.ts.map +1 -1
- package/dest/contracts/inbox.js +9 -3
- package/dest/contracts/index.d.ts +3 -3
- package/dest/contracts/index.d.ts.map +1 -1
- package/dest/contracts/index.js +2 -2
- package/dest/contracts/multicall.d.ts +79 -13
- package/dest/contracts/multicall.d.ts.map +1 -1
- package/dest/contracts/multicall.js +117 -95
- package/dest/contracts/outbox.d.ts +27 -4
- package/dest/contracts/outbox.d.ts.map +1 -1
- package/dest/contracts/outbox.js +34 -7
- package/dest/contracts/rollup.d.ts +5401 -34
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +271 -119
- package/dest/contracts/slashing_proposer.d.ts +171 -0
- package/dest/contracts/slashing_proposer.d.ts.map +1 -0
- package/dest/contracts/slashing_proposer.js +753 -0
- package/dest/contracts/watch_event.d.ts +36 -0
- package/dest/contracts/watch_event.d.ts.map +1 -0
- package/dest/contracts/watch_event.js +66 -0
- package/dest/deploy_aztec_l1_contracts.d.ts +4 -7
- package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_aztec_l1_contracts.js +15 -8
- package/dest/foundry_binary.d.ts +13 -0
- package/dest/foundry_binary.d.ts.map +1 -0
- package/dest/foundry_binary.js +52 -0
- package/dest/generated/l1-contracts-defaults.d.ts +2 -2
- package/dest/generated/l1-contracts-defaults.js +2 -2
- package/dest/l1_artifacts.d.ts +14941 -16273
- package/dest/l1_artifacts.d.ts.map +1 -1
- package/dest/l1_artifacts.js +9 -24
- package/dest/l1_contract_addresses.d.ts +61 -65
- package/dest/l1_contract_addresses.d.ts.map +1 -1
- package/dest/l1_contract_addresses.js +101 -82
- package/dest/l1_reader.d.ts +3 -5
- package/dest/l1_reader.d.ts.map +1 -1
- package/dest/l1_reader.js +3 -6
- package/dest/l1_tx_utils/config.js +3 -3
- package/dest/l1_tx_utils/l1_tx_utils.d.ts +7 -1
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils.js +82 -29
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/readonly_l1_tx_utils.js +49 -19
- package/dest/l1_tx_utils/tx_delayer.d.ts +5 -3
- package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -1
- package/dest/l1_tx_utils/tx_delayer.js +7 -5
- package/dest/publisher_manager.d.ts +18 -6
- package/dest/publisher_manager.d.ts.map +1 -1
- package/dest/publisher_manager.js +44 -7
- package/dest/queries.d.ts +13 -2
- package/dest/queries.d.ts.map +1 -1
- package/dest/queries.js +62 -3
- package/dest/test/blob_kzg_warmup.d.ts +19 -0
- package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
- package/dest/test/blob_kzg_warmup.js +64 -0
- package/dest/test/chain_monitor.d.ts +34 -2
- package/dest/test/chain_monitor.d.ts.map +1 -1
- package/dest/test/chain_monitor.js +76 -6
- package/dest/test/eth_cheat_codes.d.ts +26 -3
- package/dest/test/eth_cheat_codes.d.ts.map +1 -1
- package/dest/test/eth_cheat_codes.js +61 -40
- package/dest/test/index.d.ts +2 -1
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +1 -0
- package/dest/test/recording_rpc_server.d.ts +18 -0
- package/dest/test/recording_rpc_server.d.ts.map +1 -0
- package/dest/test/recording_rpc_server.js +68 -0
- package/dest/test/rollup_cheat_codes.d.ts +60 -3
- package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
- package/dest/test/rollup_cheat_codes.js +86 -7
- package/dest/test/start_anvil.d.ts +1 -1
- package/dest/test/start_anvil.d.ts.map +1 -1
- package/dest/test/start_anvil.js +41 -5
- package/dest/utils.d.ts +9 -2
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +45 -14
- package/package.json +6 -6
- package/src/client.ts +63 -1
- package/src/config.ts +44 -13
- package/src/contracts/chain_state_override.ts +200 -0
- package/src/contracts/empire_base.ts +0 -2
- package/src/contracts/fee_asset_price_oracle.ts +10 -5
- package/src/contracts/governance.ts +310 -10
- package/src/contracts/governance_proposer.ts +56 -13
- package/src/contracts/gse.ts +7 -2
- package/src/contracts/inbox.ts +12 -4
- package/src/contracts/index.ts +2 -2
- package/src/contracts/multicall.ts +179 -105
- package/src/contracts/outbox.ts +42 -8
- package/src/contracts/rollup.ts +302 -119
- package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +119 -45
- package/src/contracts/watch_event.ts +110 -0
- package/src/deploy_aztec_l1_contracts.ts +19 -10
- package/src/foundry_binary.ts +57 -0
- package/src/generated/l1-contracts-defaults.ts +2 -2
- package/src/l1_artifacts.ts +12 -35
- package/src/l1_contract_addresses.ts +120 -94
- package/src/l1_reader.ts +4 -9
- package/src/l1_tx_utils/config.ts +3 -3
- package/src/l1_tx_utils/l1_tx_utils.ts +83 -36
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +49 -13
- package/src/l1_tx_utils/tx_delayer.ts +16 -7
- package/src/publisher_manager.ts +54 -18
- package/src/queries.ts +70 -4
- package/src/test/blob_kzg_warmup.ts +65 -0
- package/src/test/chain_monitor.ts +93 -5
- package/src/test/eth_cheat_codes.ts +57 -43
- package/src/test/index.ts +1 -0
- package/src/test/recording_rpc_server.ts +69 -0
- package/src/test/rollup_cheat_codes.ts +115 -6
- package/src/test/start_anvil.ts +38 -6
- package/src/utils.ts +41 -17
- package/dest/contracts/empire_slashing_proposer.d.ts +0 -69
- package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
- package/dest/contracts/empire_slashing_proposer.js +0 -216
- package/dest/contracts/tally_slashing_proposer.d.ts +0 -140
- package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
- package/dest/contracts/tally_slashing_proposer.js +0 -320
- package/src/contracts/empire_slashing_proposer.ts +0 -265
package/src/contracts/rollup.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
6
6
|
import type { ViemSignature } from '@aztec/foundation/eth-signature';
|
|
7
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
8
8
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
9
|
+
import { getErrorCause } from '@aztec/foundation/types';
|
|
9
10
|
import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
|
|
10
11
|
import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
|
|
11
12
|
import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
|
|
@@ -13,10 +14,14 @@ import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
|
|
|
13
14
|
import chunk from 'lodash.chunk';
|
|
14
15
|
import {
|
|
15
16
|
type Account,
|
|
17
|
+
ContractFunctionRevertedError,
|
|
16
18
|
type GetContractReturnType,
|
|
17
19
|
type Hex,
|
|
20
|
+
type Log,
|
|
21
|
+
RpcRequestError,
|
|
18
22
|
type StateOverride,
|
|
19
23
|
type WatchContractEventReturnType,
|
|
24
|
+
decodeErrorResult,
|
|
20
25
|
encodeAbiParameters,
|
|
21
26
|
encodeFunctionData,
|
|
22
27
|
getContract,
|
|
@@ -31,12 +36,12 @@ import type { L1ReaderConfig } from '../l1_reader.js';
|
|
|
31
36
|
import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
|
|
32
37
|
import type { ViemClient } from '../types.js';
|
|
33
38
|
import { formatViemError } from '../utils.js';
|
|
34
|
-
import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
|
|
35
39
|
import { GSEContract } from './gse.js';
|
|
36
40
|
import type { L1EventLog } from './log.js';
|
|
37
41
|
import { SlasherContract } from './slasher_contract.js';
|
|
38
|
-
import {
|
|
42
|
+
import { SlashingProposerContract } from './slashing_proposer.js';
|
|
39
43
|
import { checkBlockTag } from './utils.js';
|
|
44
|
+
import { type WatchContractEventOptions, watchContractEvent } from './watch_event.js';
|
|
40
45
|
|
|
41
46
|
export type ViemCommitteeAttestation = {
|
|
42
47
|
addr: `0x${string}`;
|
|
@@ -57,7 +62,6 @@ export type L1RollupContractAddresses = Pick<
|
|
|
57
62
|
| 'feeJuiceAddress'
|
|
58
63
|
| 'stakingAssetAddress'
|
|
59
64
|
| 'rewardDistributorAddress'
|
|
60
|
-
| 'slashFactoryAddress'
|
|
61
65
|
| 'gseAddress'
|
|
62
66
|
>;
|
|
63
67
|
|
|
@@ -80,6 +84,7 @@ export type ViemHeader = {
|
|
|
80
84
|
feeRecipient: `0x${string}`;
|
|
81
85
|
gasFees: ViemGasFees;
|
|
82
86
|
totalManaUsed: bigint;
|
|
87
|
+
accumulatedFees: bigint;
|
|
83
88
|
};
|
|
84
89
|
|
|
85
90
|
export type ViemGasFees = {
|
|
@@ -87,12 +92,6 @@ export type ViemGasFees = {
|
|
|
87
92
|
feePerL2Gas: bigint;
|
|
88
93
|
};
|
|
89
94
|
|
|
90
|
-
export enum SlashingProposerType {
|
|
91
|
-
None = 0,
|
|
92
|
-
Tally = 1,
|
|
93
|
-
Empire = 2,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
95
|
/**
|
|
97
96
|
* Status of a validator/attester in the staking system.
|
|
98
97
|
* Matches the Status enum in StakingLib.sol
|
|
@@ -136,6 +135,31 @@ export type L1FeeData = {
|
|
|
136
135
|
blobFee: bigint;
|
|
137
136
|
};
|
|
138
137
|
|
|
138
|
+
/** Field offsets within the CompressedTempCheckpointLog struct in Solidity storage. */
|
|
139
|
+
export enum TempCheckpointLogField {
|
|
140
|
+
HeaderHash = 0,
|
|
141
|
+
BlobCommitmentsHash = 1,
|
|
142
|
+
OutHash = 2,
|
|
143
|
+
AttestationsHash = 3,
|
|
144
|
+
PayloadDigest = 4,
|
|
145
|
+
SlotNumber = 5,
|
|
146
|
+
FeeHeader = 6,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Field-level override input for `tempCheckpointLogs[checkpointNumber]`. Covers the fields the
|
|
151
|
+
* `propose()` path actually reads back. `payloadDigest` is `Buffer32` because it carries an
|
|
152
|
+
* arbitrary `bytes32` value rather than a BN254 scalar. `slotNumber` carries the uint32 portion
|
|
153
|
+
* of the on-chain `CompressedSlot`.
|
|
154
|
+
*/
|
|
155
|
+
export type TempCheckpointLogOverrideFields = {
|
|
156
|
+
headerHash?: Fr;
|
|
157
|
+
outHash?: Fr;
|
|
158
|
+
payloadDigest?: Buffer32;
|
|
159
|
+
slotNumber?: SlotNumber;
|
|
160
|
+
feeHeader?: FeeHeader;
|
|
161
|
+
};
|
|
162
|
+
|
|
139
163
|
/** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
|
|
140
164
|
export type ManaMinFeeComponents = {
|
|
141
165
|
sequencerCost: bigint;
|
|
@@ -212,6 +236,35 @@ export type CheckpointProposedArgs = {
|
|
|
212
236
|
/** Log type for CheckpointProposed events. */
|
|
213
237
|
export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
|
|
214
238
|
|
|
239
|
+
const INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR = 'ValidatorSelection__InsufficientValidatorSetSize';
|
|
240
|
+
|
|
241
|
+
/** SlasherUpdated events are rare governance operations, so their watcher polls well below the client's interval. */
|
|
242
|
+
const SLASHER_UPDATED_POLLING_INTERVAL_MS = 60_000;
|
|
243
|
+
|
|
244
|
+
function isValidatorSelectionError(err: unknown, errorName: string): boolean {
|
|
245
|
+
return (
|
|
246
|
+
getErrorCause(err, ContractFunctionRevertedError)?.data?.errorName === errorName ||
|
|
247
|
+
decodeRpcRequestErrorName(err) === errorName
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function decodeRpcRequestErrorName(err: unknown): string | undefined {
|
|
252
|
+
const data = getErrorCause(err, RpcRequestError)?.data;
|
|
253
|
+
if (!isHexString(data)) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
return decodeErrorResult({ abi: RollupAbi, data }).errorName;
|
|
259
|
+
} catch {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function isHexString(value: unknown): value is Hex {
|
|
265
|
+
return typeof value === 'string' && value.startsWith('0x');
|
|
266
|
+
}
|
|
267
|
+
|
|
215
268
|
export class RollupContract {
|
|
216
269
|
private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
|
|
217
270
|
private readonly logger = createLogger('ethereum:rollup');
|
|
@@ -244,7 +297,7 @@ export class RollupContract {
|
|
|
244
297
|
|
|
245
298
|
static getFromConfig(config: L1ReaderConfig) {
|
|
246
299
|
const client = getPublicClient(config);
|
|
247
|
-
const address = config.
|
|
300
|
+
const address = config.rollupAddress.toString();
|
|
248
301
|
return new RollupContract(client, address);
|
|
249
302
|
}
|
|
250
303
|
|
|
@@ -270,34 +323,18 @@ export class RollupContract {
|
|
|
270
323
|
return this.rollup;
|
|
271
324
|
}
|
|
272
325
|
|
|
273
|
-
public async getSlashingProposer(): Promise<
|
|
274
|
-
EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
|
|
275
|
-
> {
|
|
326
|
+
public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
|
|
276
327
|
const slasher = await this.getSlasherContract();
|
|
277
328
|
if (!slasher) {
|
|
278
329
|
return undefined;
|
|
279
330
|
}
|
|
280
331
|
|
|
281
332
|
const proposerAddress = await slasher.getProposer();
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
type: 'function',
|
|
285
|
-
name: 'SLASHING_PROPOSER_TYPE',
|
|
286
|
-
inputs: [],
|
|
287
|
-
outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
|
|
288
|
-
stateMutability: 'view',
|
|
289
|
-
},
|
|
290
|
-
] as const;
|
|
291
|
-
|
|
292
|
-
const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
|
|
293
|
-
const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
|
|
294
|
-
if (proposerType === SlashingProposerType.Tally.valueOf()) {
|
|
295
|
-
return new TallySlashingProposerContract(this.client, proposerAddress);
|
|
296
|
-
} else if (proposerType === SlashingProposerType.Empire.valueOf()) {
|
|
297
|
-
return new EmpireSlashingProposerContract(this.client, proposerAddress);
|
|
298
|
-
} else {
|
|
299
|
-
throw new Error(`Unknown slashing proposer type: ${proposerType}`);
|
|
333
|
+
if (proposerAddress.isZero()) {
|
|
334
|
+
return undefined;
|
|
300
335
|
}
|
|
336
|
+
|
|
337
|
+
return new SlashingProposerContract(this.client, proposerAddress);
|
|
301
338
|
}
|
|
302
339
|
|
|
303
340
|
@memoize
|
|
@@ -522,8 +559,9 @@ export class RollupContract {
|
|
|
522
559
|
return EthAddress.fromString(await this.rollup.read.owner());
|
|
523
560
|
}
|
|
524
561
|
|
|
525
|
-
async getActiveAttesterCount(): Promise<number> {
|
|
526
|
-
|
|
562
|
+
async getActiveAttesterCount(options?: { blockNumber?: bigint }): Promise<number> {
|
|
563
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
564
|
+
return Number(await this.rollup.read.getActiveAttesterCount(options));
|
|
527
565
|
}
|
|
528
566
|
|
|
529
567
|
public async getSlashingProposerAddress() {
|
|
@@ -538,8 +576,9 @@ export class RollupContract {
|
|
|
538
576
|
return this.rollup.read.getCheckpointReward();
|
|
539
577
|
}
|
|
540
578
|
|
|
541
|
-
async getCheckpointNumber(): Promise<CheckpointNumber> {
|
|
542
|
-
|
|
579
|
+
async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
580
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
581
|
+
return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
|
|
543
582
|
}
|
|
544
583
|
|
|
545
584
|
async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
@@ -547,18 +586,31 @@ export class RollupContract {
|
|
|
547
586
|
return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
|
|
548
587
|
}
|
|
549
588
|
|
|
550
|
-
async getSlotNumber(): Promise<SlotNumber> {
|
|
551
|
-
|
|
589
|
+
async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
|
|
590
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
591
|
+
return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
|
|
552
592
|
}
|
|
553
593
|
|
|
554
|
-
async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
|
|
555
|
-
|
|
594
|
+
async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
|
|
595
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
596
|
+
const result = await this.rollup.read.getL1FeesAt([timestamp], options);
|
|
556
597
|
return {
|
|
557
598
|
baseFee: result.baseFee,
|
|
558
599
|
blobFee: result.blobFee,
|
|
559
600
|
};
|
|
560
601
|
}
|
|
561
602
|
|
|
603
|
+
async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
|
|
604
|
+
const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
|
|
605
|
+
return {
|
|
606
|
+
excessMana: result.excessMana,
|
|
607
|
+
manaUsed: result.manaUsed,
|
|
608
|
+
ethPerFeeAsset: result.ethPerFeeAsset,
|
|
609
|
+
congestionCost: result.congestionCost,
|
|
610
|
+
proverCost: result.proverCost,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
562
614
|
getEthPerFeeAsset(): Promise<bigint> {
|
|
563
615
|
return this.rollup.read.getEthPerFeeAsset();
|
|
564
616
|
}
|
|
@@ -572,7 +624,7 @@ export class RollupContract {
|
|
|
572
624
|
args: [timestamp],
|
|
573
625
|
})
|
|
574
626
|
.catch(e => {
|
|
575
|
-
if (e
|
|
627
|
+
if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
|
|
576
628
|
return { result: undefined };
|
|
577
629
|
}
|
|
578
630
|
throw e;
|
|
@@ -602,7 +654,7 @@ export class RollupContract {
|
|
|
602
654
|
args: [],
|
|
603
655
|
})
|
|
604
656
|
.catch(e => {
|
|
605
|
-
if (e
|
|
657
|
+
if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
|
|
606
658
|
return { result: undefined };
|
|
607
659
|
}
|
|
608
660
|
throw e;
|
|
@@ -633,8 +685,9 @@ export class RollupContract {
|
|
|
633
685
|
return EthAddress.fromString(result);
|
|
634
686
|
}
|
|
635
687
|
|
|
636
|
-
async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
|
|
637
|
-
|
|
688
|
+
async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
|
|
689
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
690
|
+
const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
|
|
638
691
|
return {
|
|
639
692
|
archive: Fr.fromString(result.archive),
|
|
640
693
|
headerHash: Buffer32.fromString(result.headerHash),
|
|
@@ -667,6 +720,30 @@ export class RollupContract {
|
|
|
667
720
|
);
|
|
668
721
|
}
|
|
669
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Returns the effective pending checkpoint, accounting for potential prunes.
|
|
725
|
+
* When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
|
|
726
|
+
* This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
|
|
727
|
+
* @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
|
|
728
|
+
* @param options - Optional L1 block number to pin the queries to.
|
|
729
|
+
*/
|
|
730
|
+
getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
|
|
731
|
+
return retry(
|
|
732
|
+
async () => {
|
|
733
|
+
const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
|
|
734
|
+
const canPrune = await this.canPruneAtTime(timestamp, options);
|
|
735
|
+
if (canPrune) {
|
|
736
|
+
const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
|
|
737
|
+
return await this.getCheckpoint(provenCheckpointNumber, options);
|
|
738
|
+
}
|
|
739
|
+
const pendingCheckpointNumber = await this.getCheckpointNumber(options);
|
|
740
|
+
return await this.getCheckpoint(pendingCheckpointNumber, options);
|
|
741
|
+
},
|
|
742
|
+
'getting effective pending checkpoint',
|
|
743
|
+
makeBackoff([0.5, 0.5, 0.5]),
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
670
747
|
async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
|
|
671
748
|
const { pending, proven } = await this.rollup.read.getTips();
|
|
672
749
|
return {
|
|
@@ -741,7 +818,7 @@ export class RollupContract {
|
|
|
741
818
|
}
|
|
742
819
|
|
|
743
820
|
async getEpochProofPublicInputs(
|
|
744
|
-
args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly
|
|
821
|
+
args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly ViemHeader[], `0x${string}`],
|
|
745
822
|
): Promise<Fr[]> {
|
|
746
823
|
const result = await this.rollup.read.getEpochProofPublicInputs(args);
|
|
747
824
|
return result.map(Fr.fromString);
|
|
@@ -789,19 +866,11 @@ export class RollupContract {
|
|
|
789
866
|
archive: Buffer,
|
|
790
867
|
account: `0x${string}` | Account,
|
|
791
868
|
timestamp: bigint,
|
|
792
|
-
|
|
793
|
-
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
794
|
-
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
795
|
-
} = {},
|
|
869
|
+
stateOverride: StateOverride = [],
|
|
796
870
|
): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
|
|
797
871
|
const timeOfNextL1Slot = timestamp;
|
|
798
872
|
const who = typeof account === 'string' ? account : account.address;
|
|
799
873
|
|
|
800
|
-
const stateOverride = RollupContract.mergeStateOverrides(
|
|
801
|
-
await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
|
|
802
|
-
opts.forceArchive ? this.makeArchiveOverride(opts.forceArchive.checkpointNumber, opts.forceArchive.archive) : [],
|
|
803
|
-
);
|
|
804
|
-
|
|
805
874
|
try {
|
|
806
875
|
const {
|
|
807
876
|
result: [slot, checkpointNumber],
|
|
@@ -825,20 +894,32 @@ export class RollupContract {
|
|
|
825
894
|
}
|
|
826
895
|
|
|
827
896
|
/**
|
|
828
|
-
* Returns a state override that sets the pending
|
|
829
|
-
*
|
|
830
|
-
*
|
|
897
|
+
* Returns a state override that sets the pending and/or proven checkpoint numbers. Useful for simulations.
|
|
898
|
+
* Both values share a single storage slot (pending in the upper 128 bits, proven in the lower 128 bits), so
|
|
899
|
+
* a single combined override is emitted to avoid the second state-diff clobbering the first. The current live
|
|
900
|
+
* value is read once to preserve any half not being overridden. Returns an empty override if neither is set.
|
|
901
|
+
*
|
|
902
|
+
* Throws if the resulting `proven > pending`, which would crash the simulation: `STFLib.canPruneAtTime` calls
|
|
903
|
+
* `getEpochForCheckpoint(proven + 1)` whenever `pending != proven`, and that asserts `_n <= tips.pending`.
|
|
831
904
|
*/
|
|
832
|
-
public async
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
905
|
+
public async makeChainTipsOverride(override: {
|
|
906
|
+
pending?: CheckpointNumber;
|
|
907
|
+
proven?: CheckpointNumber;
|
|
908
|
+
}): Promise<StateOverride> {
|
|
909
|
+
if (override.pending === undefined && override.proven === undefined) {
|
|
836
910
|
return [];
|
|
837
911
|
}
|
|
838
912
|
const slot = RollupContract.stfStorageSlot;
|
|
839
913
|
const currentValue = await this.client.getStorageAt({ address: this.address, slot });
|
|
840
|
-
const
|
|
841
|
-
const
|
|
914
|
+
const currentRaw = currentValue ? hexToBigInt(currentValue) : 0n;
|
|
915
|
+
const currentPending = currentRaw >> 128n;
|
|
916
|
+
const currentProven = currentRaw & ((1n << 128n) - 1n);
|
|
917
|
+
const newPending = override.pending !== undefined ? BigInt(override.pending) : currentPending;
|
|
918
|
+
const newProven = override.proven !== undefined ? BigInt(override.proven) : currentProven;
|
|
919
|
+
if (newProven > newPending) {
|
|
920
|
+
throw new Error(`Invalid chain tips override: proven (${newProven}) > pending (${newPending})`);
|
|
921
|
+
}
|
|
922
|
+
const newValue = (newPending << 128n) | newProven;
|
|
842
923
|
return [
|
|
843
924
|
{
|
|
844
925
|
address: this.address,
|
|
@@ -848,39 +929,56 @@ export class RollupContract {
|
|
|
848
929
|
}
|
|
849
930
|
|
|
850
931
|
/**
|
|
851
|
-
* Returns a state override that
|
|
852
|
-
*
|
|
932
|
+
* Returns a state override that patches `tempCheckpointLogs[checkpointNumber]` with every field that
|
|
933
|
+
* the L1 contract reads during `propose()` for the checkpoint that builds on top of it (proposer
|
|
934
|
+
* pipelining simulation). Mirrors the writes done by `ProposeLib.addTempCheckpointLog`.
|
|
935
|
+
*
|
|
936
|
+
* `blobCommitmentsHash` and `attestationsHash` are intentionally not exposed here — the propose path
|
|
937
|
+
* never asserts against them, so leaving them at storage zero is harmless.
|
|
853
938
|
*/
|
|
854
|
-
public async
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
939
|
+
public async makeTempCheckpointLogOverride(
|
|
940
|
+
checkpointNumber: CheckpointNumber,
|
|
941
|
+
fields: TempCheckpointLogOverrideFields,
|
|
942
|
+
): Promise<StateOverride> {
|
|
943
|
+
const constants = await this.getRollupConstants();
|
|
944
|
+
const slotAt = (field: TempCheckpointLogField) =>
|
|
945
|
+
`0x${this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants).toString(16).padStart(64, '0')}` as const;
|
|
946
|
+
const word = (v: bigint) => `0x${v.toString(16).padStart(64, '0')}` as const;
|
|
861
947
|
|
|
862
|
-
|
|
863
|
-
const structBaseSlot = hexToBigInt(
|
|
864
|
-
keccak256(
|
|
865
|
-
encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsMappingBase]),
|
|
866
|
-
),
|
|
867
|
-
);
|
|
948
|
+
const stateDiff: { slot: `0x${string}`; value: `0x${string}` }[] = [];
|
|
868
949
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
950
|
+
if (fields.headerHash) {
|
|
951
|
+
stateDiff.push({ slot: slotAt(TempCheckpointLogField.HeaderHash), value: fields.headerHash.toString() });
|
|
952
|
+
}
|
|
953
|
+
if (fields.outHash) {
|
|
954
|
+
stateDiff.push({ slot: slotAt(TempCheckpointLogField.OutHash), value: fields.outHash.toString() });
|
|
955
|
+
}
|
|
956
|
+
if (fields.payloadDigest) {
|
|
957
|
+
stateDiff.push({
|
|
958
|
+
slot: slotAt(TempCheckpointLogField.PayloadDigest),
|
|
959
|
+
value: fields.payloadDigest.toString() as `0x${string}`,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
if (fields.slotNumber !== undefined) {
|
|
963
|
+
// CompressedSlot is uint32 on L1 (SafeCast.toUint32 reverts on overflow). Match that behavior here
|
|
964
|
+
// so a malformed override surfaces immediately rather than silently truncating into a wrong slot.
|
|
965
|
+
const slotNumber = BigInt(fields.slotNumber);
|
|
966
|
+
if (slotNumber < 0n || slotNumber > 0xffffffffn) {
|
|
967
|
+
throw new Error(`slotNumber ${slotNumber} does not fit in uint32`);
|
|
968
|
+
}
|
|
969
|
+
stateDiff.push({ slot: slotAt(TempCheckpointLogField.SlotNumber), value: word(slotNumber) });
|
|
970
|
+
}
|
|
971
|
+
if (fields.feeHeader) {
|
|
972
|
+
stateDiff.push({
|
|
973
|
+
slot: slotAt(TempCheckpointLogField.FeeHeader),
|
|
974
|
+
value: word(RollupContract.compressFeeHeader(fields.feeHeader)),
|
|
975
|
+
});
|
|
976
|
+
}
|
|
872
977
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
{
|
|
878
|
-
slot: `0x${feeHeaderSlot.toString(16).padStart(64, '0')}`,
|
|
879
|
-
value: `0x${compressed.toString(16).padStart(64, '0')}`,
|
|
880
|
-
},
|
|
881
|
-
],
|
|
882
|
-
},
|
|
883
|
-
];
|
|
978
|
+
if (stateDiff.length === 0) {
|
|
979
|
+
return [];
|
|
980
|
+
}
|
|
981
|
+
return [{ address: this.address, stateDiff }];
|
|
884
982
|
}
|
|
885
983
|
|
|
886
984
|
/**
|
|
@@ -1098,13 +1196,20 @@ export class RollupContract {
|
|
|
1098
1196
|
}
|
|
1099
1197
|
|
|
1100
1198
|
async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
|
|
1101
|
-
|
|
1199
|
+
// Pin every read to a single L1 block so the attester count and the chunked index reads
|
|
1200
|
+
// observe a consistent set. Without this, the count and each chunk default to `latest` and
|
|
1201
|
+
// can straddle a block boundary (or reorg), yielding an inconsistent or truncated set.
|
|
1202
|
+
const block = await this.client.getBlock();
|
|
1203
|
+
const blockNumber = block.number ?? undefined;
|
|
1204
|
+
const ts = timestamp ?? block.timestamp;
|
|
1205
|
+
const attesterSize = await this.getActiveAttesterCount({ blockNumber });
|
|
1102
1206
|
const gse = new GSEContract(this.client, await this.getGSE());
|
|
1103
|
-
const ts = timestamp ?? (await this.client.getBlock()).timestamp;
|
|
1104
1207
|
const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
|
|
1105
1208
|
const chunks = chunk(indices, 1000);
|
|
1106
1209
|
|
|
1107
|
-
const results = await Promise.all(
|
|
1210
|
+
const results = await Promise.all(
|
|
1211
|
+
chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk, { blockNumber })),
|
|
1212
|
+
);
|
|
1108
1213
|
return results.flat().map(addr => EthAddress.fromString(addr));
|
|
1109
1214
|
}
|
|
1110
1215
|
|
|
@@ -1187,39 +1292,57 @@ export class RollupContract {
|
|
|
1187
1292
|
});
|
|
1188
1293
|
}
|
|
1189
1294
|
|
|
1295
|
+
/**
|
|
1296
|
+
* Watches for SlasherUpdated events. Events are delivered by polling `eth_getLogs`: a reorg may re-emit them and
|
|
1297
|
+
* removals are never reported, and events mined within roughly one polling interval of subscribing may be missed.
|
|
1298
|
+
* Slasher rotations are rare governance operations, so by default this polls much slower than the other watchers.
|
|
1299
|
+
*/
|
|
1190
1300
|
public listenToSlasherChanged(
|
|
1191
1301
|
callback: (args: { oldSlasher: `0x${string}`; newSlasher: `0x${string}` }) => unknown,
|
|
1302
|
+
options?: WatchContractEventOptions,
|
|
1192
1303
|
): WatchContractEventReturnType {
|
|
1193
|
-
return
|
|
1194
|
-
|
|
1304
|
+
return watchContractEvent(
|
|
1305
|
+
this.client,
|
|
1306
|
+
this.logger,
|
|
1195
1307
|
{
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1308
|
+
address: this.address,
|
|
1309
|
+
abi: RollupAbi,
|
|
1310
|
+
eventName: 'SlasherUpdated',
|
|
1311
|
+
onLog: log => {
|
|
1312
|
+
const { oldSlasher, newSlasher } = log.args;
|
|
1313
|
+
if (oldSlasher && newSlasher) {
|
|
1314
|
+
return callback({ oldSlasher, newSlasher });
|
|
1202
1315
|
}
|
|
1203
1316
|
},
|
|
1204
1317
|
},
|
|
1318
|
+
{ pollingIntervalMs: SLASHER_UPDATED_POLLING_INTERVAL_MS, ...options },
|
|
1205
1319
|
);
|
|
1206
1320
|
}
|
|
1207
1321
|
|
|
1322
|
+
/**
|
|
1323
|
+
* Watches for CheckpointInvalidated events. Events are delivered by polling `eth_getLogs`: a reorg may re-emit
|
|
1324
|
+
* them and removals are never reported, and events mined within roughly one polling interval of subscribing may
|
|
1325
|
+
* be missed.
|
|
1326
|
+
*/
|
|
1208
1327
|
public listenToCheckpointInvalidated(
|
|
1209
|
-
callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
|
|
1328
|
+
callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
|
|
1329
|
+
options?: WatchContractEventOptions,
|
|
1210
1330
|
): WatchContractEventReturnType {
|
|
1211
|
-
return
|
|
1212
|
-
|
|
1331
|
+
return watchContractEvent(
|
|
1332
|
+
this.client,
|
|
1333
|
+
this.logger,
|
|
1213
1334
|
{
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1335
|
+
address: this.address,
|
|
1336
|
+
abi: RollupAbi,
|
|
1337
|
+
eventName: 'CheckpointInvalidated',
|
|
1338
|
+
onLog: log => {
|
|
1339
|
+
const { checkpointNumber } = log.args;
|
|
1340
|
+
if (checkpointNumber !== undefined) {
|
|
1341
|
+
return callback({ checkpointNumber: CheckpointNumber.fromBigInt(checkpointNumber), event: log });
|
|
1220
1342
|
}
|
|
1221
1343
|
},
|
|
1222
1344
|
},
|
|
1345
|
+
options,
|
|
1223
1346
|
);
|
|
1224
1347
|
}
|
|
1225
1348
|
|
|
@@ -1231,23 +1354,39 @@ export class RollupContract {
|
|
|
1231
1354
|
}));
|
|
1232
1355
|
}
|
|
1233
1356
|
|
|
1357
|
+
/**
|
|
1358
|
+
* Watches for Slashed events. Events are delivered by polling `eth_getLogs`: a reorg may re-emit them and
|
|
1359
|
+
* removals are never reported, and events mined within roughly one polling interval of subscribing may be missed.
|
|
1360
|
+
*/
|
|
1234
1361
|
public listenToSlash(
|
|
1235
1362
|
callback: (args: { amount: bigint; attester: EthAddress }) => unknown,
|
|
1363
|
+
options?: WatchContractEventOptions,
|
|
1236
1364
|
): WatchContractEventReturnType {
|
|
1237
|
-
return
|
|
1238
|
-
|
|
1365
|
+
return watchContractEvent(
|
|
1366
|
+
this.client,
|
|
1367
|
+
this.logger,
|
|
1239
1368
|
{
|
|
1369
|
+
address: this.address,
|
|
1370
|
+
abi: RollupAbi,
|
|
1371
|
+
eventName: 'Slashed',
|
|
1240
1372
|
strict: true,
|
|
1241
|
-
|
|
1242
|
-
for (const log of logs) {
|
|
1243
|
-
const args = log.args;
|
|
1244
|
-
callback({ amount: args.amount!, attester: EthAddress.fromString(args.attester!) });
|
|
1245
|
-
}
|
|
1246
|
-
},
|
|
1373
|
+
onLog: log => callback({ amount: log.args.amount, attester: EthAddress.fromString(log.args.attester) }),
|
|
1247
1374
|
},
|
|
1375
|
+
options,
|
|
1248
1376
|
);
|
|
1249
1377
|
}
|
|
1250
1378
|
|
|
1379
|
+
/**
|
|
1380
|
+
* Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
|
|
1381
|
+
* The Rollup inherits from Ownable and emits this event in its constructor, so the event
|
|
1382
|
+
* is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
|
|
1383
|
+
* probe to detect RPC nodes that prune historical logs.
|
|
1384
|
+
*/
|
|
1385
|
+
async getOwnershipTransferredEventsAtDeploy() {
|
|
1386
|
+
const l1StartBlock = await this.getL1StartBlock();
|
|
1387
|
+
return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1251
1390
|
/** Fetches CheckpointProposed events within the given block range. */
|
|
1252
1391
|
async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
|
|
1253
1392
|
const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
|
|
@@ -1280,4 +1419,48 @@ export class RollupContract {
|
|
|
1280
1419
|
},
|
|
1281
1420
|
}));
|
|
1282
1421
|
}
|
|
1422
|
+
|
|
1423
|
+
/** Packs pending and proven checkpoint numbers into the chain tips storage format. */
|
|
1424
|
+
static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
|
|
1425
|
+
return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/** Storage slot for the chain tips (offset 0 within the STF storage struct). */
|
|
1429
|
+
static get chainTipsStorageSlot(): bigint {
|
|
1430
|
+
return BigInt(RollupContract.stfStorageSlot);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* Computes the storage slot for a field within a tempCheckpointLog entry.
|
|
1435
|
+
* @param checkpointNumber - The checkpoint number
|
|
1436
|
+
* @param field - The field within the CompressedTempCheckpointLog struct
|
|
1437
|
+
*/
|
|
1438
|
+
async getTempCheckpointLogStorageSlot(
|
|
1439
|
+
checkpointNumber: CheckpointNumber,
|
|
1440
|
+
field: TempCheckpointLogField,
|
|
1441
|
+
): Promise<bigint> {
|
|
1442
|
+
const [epochDuration, proofSubmissionEpochs] = await Promise.all([
|
|
1443
|
+
this.getEpochDuration(),
|
|
1444
|
+
this.getProofSubmissionEpochs(),
|
|
1445
|
+
]);
|
|
1446
|
+
return this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, { epochDuration, proofSubmissionEpochs });
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
private computeTempCheckpointLogStorageSlot(
|
|
1450
|
+
checkpointNumber: CheckpointNumber,
|
|
1451
|
+
field: TempCheckpointLogField,
|
|
1452
|
+
constants: { epochDuration: number; proofSubmissionEpochs: number },
|
|
1453
|
+
): bigint {
|
|
1454
|
+
const fieldOffset = BigInt(field);
|
|
1455
|
+
const { epochDuration, proofSubmissionEpochs } = constants;
|
|
1456
|
+
const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
|
|
1457
|
+
const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
|
|
1458
|
+
const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
|
|
1459
|
+
const entryBase = BigInt(
|
|
1460
|
+
keccak256(
|
|
1461
|
+
encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
|
|
1462
|
+
),
|
|
1463
|
+
);
|
|
1464
|
+
return entryBase + fieldOffset;
|
|
1465
|
+
}
|
|
1283
1466
|
}
|