@aztec/ethereum 0.0.1-commit.3fd054f6 → 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 +12 -7
- 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 +5418 -28
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +389 -95
- 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 +9 -1
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils.js +115 -54
- 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 +13 -5
- 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 +429 -86
- 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 +107 -50
- 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
|
@@ -4,7 +4,9 @@ import { Fr } from '@aztec/foundation/curves/bn254';
|
|
|
4
4
|
import { memoize } from '@aztec/foundation/decorators';
|
|
5
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
6
|
import type { ViemSignature } from '@aztec/foundation/eth-signature';
|
|
7
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
7
8
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
9
|
+
import { getErrorCause } from '@aztec/foundation/types';
|
|
8
10
|
import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
|
|
9
11
|
import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
|
|
10
12
|
import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
|
|
@@ -12,10 +14,15 @@ import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
|
|
|
12
14
|
import chunk from 'lodash.chunk';
|
|
13
15
|
import {
|
|
14
16
|
type Account,
|
|
17
|
+
ContractFunctionRevertedError,
|
|
15
18
|
type GetContractReturnType,
|
|
16
19
|
type Hex,
|
|
20
|
+
type Log,
|
|
21
|
+
RpcRequestError,
|
|
17
22
|
type StateOverride,
|
|
18
23
|
type WatchContractEventReturnType,
|
|
24
|
+
decodeErrorResult,
|
|
25
|
+
encodeAbiParameters,
|
|
19
26
|
encodeFunctionData,
|
|
20
27
|
getContract,
|
|
21
28
|
hexToBigInt,
|
|
@@ -29,12 +36,12 @@ import type { L1ReaderConfig } from '../l1_reader.js';
|
|
|
29
36
|
import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
|
|
30
37
|
import type { ViemClient } from '../types.js';
|
|
31
38
|
import { formatViemError } from '../utils.js';
|
|
32
|
-
import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
|
|
33
39
|
import { GSEContract } from './gse.js';
|
|
34
40
|
import type { L1EventLog } from './log.js';
|
|
35
41
|
import { SlasherContract } from './slasher_contract.js';
|
|
36
|
-
import {
|
|
42
|
+
import { SlashingProposerContract } from './slashing_proposer.js';
|
|
37
43
|
import { checkBlockTag } from './utils.js';
|
|
44
|
+
import { type WatchContractEventOptions, watchContractEvent } from './watch_event.js';
|
|
38
45
|
|
|
39
46
|
export type ViemCommitteeAttestation = {
|
|
40
47
|
addr: `0x${string}`;
|
|
@@ -55,7 +62,6 @@ export type L1RollupContractAddresses = Pick<
|
|
|
55
62
|
| 'feeJuiceAddress'
|
|
56
63
|
| 'stakingAssetAddress'
|
|
57
64
|
| 'rewardDistributorAddress'
|
|
58
|
-
| 'slashFactoryAddress'
|
|
59
65
|
| 'gseAddress'
|
|
60
66
|
>;
|
|
61
67
|
|
|
@@ -78,6 +84,7 @@ export type ViemHeader = {
|
|
|
78
84
|
feeRecipient: `0x${string}`;
|
|
79
85
|
gasFees: ViemGasFees;
|
|
80
86
|
totalManaUsed: bigint;
|
|
87
|
+
accumulatedFees: bigint;
|
|
81
88
|
};
|
|
82
89
|
|
|
83
90
|
export type ViemGasFees = {
|
|
@@ -85,12 +92,6 @@ export type ViemGasFees = {
|
|
|
85
92
|
feePerL2Gas: bigint;
|
|
86
93
|
};
|
|
87
94
|
|
|
88
|
-
export enum SlashingProposerType {
|
|
89
|
-
None = 0,
|
|
90
|
-
Tally = 1,
|
|
91
|
-
Empire = 2,
|
|
92
|
-
}
|
|
93
|
-
|
|
94
95
|
/**
|
|
95
96
|
* Status of a validator/attester in the staking system.
|
|
96
97
|
* Matches the Status enum in StakingLib.sol
|
|
@@ -134,6 +135,31 @@ export type L1FeeData = {
|
|
|
134
135
|
blobFee: bigint;
|
|
135
136
|
};
|
|
136
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
|
+
|
|
137
163
|
/** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
|
|
138
164
|
export type ManaMinFeeComponents = {
|
|
139
165
|
sequencerCost: bigint;
|
|
@@ -210,8 +236,38 @@ export type CheckpointProposedArgs = {
|
|
|
210
236
|
/** Log type for CheckpointProposed events. */
|
|
211
237
|
export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
|
|
212
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
|
+
|
|
213
268
|
export class RollupContract {
|
|
214
269
|
private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
|
|
270
|
+
private readonly logger = createLogger('ethereum:rollup');
|
|
215
271
|
|
|
216
272
|
private static cachedStfStorageSlot: Hex | undefined;
|
|
217
273
|
private cachedEscapeHatch?: {
|
|
@@ -241,7 +297,7 @@ export class RollupContract {
|
|
|
241
297
|
|
|
242
298
|
static getFromConfig(config: L1ReaderConfig) {
|
|
243
299
|
const client = getPublicClient(config);
|
|
244
|
-
const address = config.
|
|
300
|
+
const address = config.rollupAddress.toString();
|
|
245
301
|
return new RollupContract(client, address);
|
|
246
302
|
}
|
|
247
303
|
|
|
@@ -267,34 +323,18 @@ export class RollupContract {
|
|
|
267
323
|
return this.rollup;
|
|
268
324
|
}
|
|
269
325
|
|
|
270
|
-
public async getSlashingProposer(): Promise<
|
|
271
|
-
EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
|
|
272
|
-
> {
|
|
326
|
+
public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
|
|
273
327
|
const slasher = await this.getSlasherContract();
|
|
274
328
|
if (!slasher) {
|
|
275
329
|
return undefined;
|
|
276
330
|
}
|
|
277
331
|
|
|
278
332
|
const proposerAddress = await slasher.getProposer();
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
type: 'function',
|
|
282
|
-
name: 'SLASHING_PROPOSER_TYPE',
|
|
283
|
-
inputs: [],
|
|
284
|
-
outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
|
|
285
|
-
stateMutability: 'view',
|
|
286
|
-
},
|
|
287
|
-
] as const;
|
|
288
|
-
|
|
289
|
-
const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
|
|
290
|
-
const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
|
|
291
|
-
if (proposerType === SlashingProposerType.Tally.valueOf()) {
|
|
292
|
-
return new TallySlashingProposerContract(this.client, proposerAddress);
|
|
293
|
-
} else if (proposerType === SlashingProposerType.Empire.valueOf()) {
|
|
294
|
-
return new EmpireSlashingProposerContract(this.client, proposerAddress);
|
|
295
|
-
} else {
|
|
296
|
-
throw new Error(`Unknown slashing proposer type: ${proposerType}`);
|
|
333
|
+
if (proposerAddress.isZero()) {
|
|
334
|
+
return undefined;
|
|
297
335
|
}
|
|
336
|
+
|
|
337
|
+
return new SlashingProposerContract(this.client, proposerAddress);
|
|
298
338
|
}
|
|
299
339
|
|
|
300
340
|
@memoize
|
|
@@ -495,7 +535,11 @@ export class RollupContract {
|
|
|
495
535
|
|
|
496
536
|
const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
|
|
497
537
|
return isOpen;
|
|
498
|
-
} catch {
|
|
538
|
+
} catch (err) {
|
|
539
|
+
this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
|
|
540
|
+
epoch: Number(epoch),
|
|
541
|
+
error: err,
|
|
542
|
+
});
|
|
499
543
|
return false;
|
|
500
544
|
}
|
|
501
545
|
}
|
|
@@ -515,8 +559,9 @@ export class RollupContract {
|
|
|
515
559
|
return EthAddress.fromString(await this.rollup.read.owner());
|
|
516
560
|
}
|
|
517
561
|
|
|
518
|
-
async getActiveAttesterCount(): Promise<number> {
|
|
519
|
-
|
|
562
|
+
async getActiveAttesterCount(options?: { blockNumber?: bigint }): Promise<number> {
|
|
563
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
564
|
+
return Number(await this.rollup.read.getActiveAttesterCount(options));
|
|
520
565
|
}
|
|
521
566
|
|
|
522
567
|
public async getSlashingProposerAddress() {
|
|
@@ -531,8 +576,9 @@ export class RollupContract {
|
|
|
531
576
|
return this.rollup.read.getCheckpointReward();
|
|
532
577
|
}
|
|
533
578
|
|
|
534
|
-
async getCheckpointNumber(): Promise<CheckpointNumber> {
|
|
535
|
-
|
|
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));
|
|
536
582
|
}
|
|
537
583
|
|
|
538
584
|
async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
@@ -540,18 +586,31 @@ export class RollupContract {
|
|
|
540
586
|
return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
|
|
541
587
|
}
|
|
542
588
|
|
|
543
|
-
async getSlotNumber(): Promise<SlotNumber> {
|
|
544
|
-
|
|
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));
|
|
545
592
|
}
|
|
546
593
|
|
|
547
|
-
async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
|
|
548
|
-
|
|
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);
|
|
549
597
|
return {
|
|
550
598
|
baseFee: result.baseFee,
|
|
551
599
|
blobFee: result.blobFee,
|
|
552
600
|
};
|
|
553
601
|
}
|
|
554
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
|
+
|
|
555
614
|
getEthPerFeeAsset(): Promise<bigint> {
|
|
556
615
|
return this.rollup.read.getEthPerFeeAsset();
|
|
557
616
|
}
|
|
@@ -565,7 +624,7 @@ export class RollupContract {
|
|
|
565
624
|
args: [timestamp],
|
|
566
625
|
})
|
|
567
626
|
.catch(e => {
|
|
568
|
-
if (e
|
|
627
|
+
if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
|
|
569
628
|
return { result: undefined };
|
|
570
629
|
}
|
|
571
630
|
throw e;
|
|
@@ -595,7 +654,7 @@ export class RollupContract {
|
|
|
595
654
|
args: [],
|
|
596
655
|
})
|
|
597
656
|
.catch(e => {
|
|
598
|
-
if (e
|
|
657
|
+
if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
|
|
599
658
|
return { result: undefined };
|
|
600
659
|
}
|
|
601
660
|
throw e;
|
|
@@ -626,8 +685,9 @@ export class RollupContract {
|
|
|
626
685
|
return EthAddress.fromString(result);
|
|
627
686
|
}
|
|
628
687
|
|
|
629
|
-
async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
|
|
630
|
-
|
|
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);
|
|
631
691
|
return {
|
|
632
692
|
archive: Fr.fromString(result.archive),
|
|
633
693
|
headerHash: Buffer32.fromString(result.headerHash),
|
|
@@ -660,6 +720,30 @@ export class RollupContract {
|
|
|
660
720
|
);
|
|
661
721
|
}
|
|
662
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
|
+
|
|
663
747
|
async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
|
|
664
748
|
const { pending, proven } = await this.rollup.read.getTips();
|
|
665
749
|
return {
|
|
@@ -734,7 +818,7 @@ export class RollupContract {
|
|
|
734
818
|
}
|
|
735
819
|
|
|
736
820
|
async getEpochProofPublicInputs(
|
|
737
|
-
args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly
|
|
821
|
+
args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly ViemHeader[], `0x${string}`],
|
|
738
822
|
): Promise<Fr[]> {
|
|
739
823
|
const result = await this.rollup.read.getEpochProofPublicInputs(args);
|
|
740
824
|
return result.map(Fr.fromString);
|
|
@@ -782,7 +866,7 @@ export class RollupContract {
|
|
|
782
866
|
archive: Buffer,
|
|
783
867
|
account: `0x${string}` | Account,
|
|
784
868
|
timestamp: bigint,
|
|
785
|
-
|
|
869
|
+
stateOverride: StateOverride = [],
|
|
786
870
|
): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
|
|
787
871
|
const timeOfNextL1Slot = timestamp;
|
|
788
872
|
const who = typeof account === 'string' ? account : account.address;
|
|
@@ -796,7 +880,7 @@ export class RollupContract {
|
|
|
796
880
|
functionName: 'canProposeAtTime',
|
|
797
881
|
args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
|
|
798
882
|
account,
|
|
799
|
-
stateOverride
|
|
883
|
+
stateOverride,
|
|
800
884
|
});
|
|
801
885
|
|
|
802
886
|
return {
|
|
@@ -810,20 +894,32 @@ export class RollupContract {
|
|
|
810
894
|
}
|
|
811
895
|
|
|
812
896
|
/**
|
|
813
|
-
* Returns a state override that sets the pending
|
|
814
|
-
*
|
|
815
|
-
*
|
|
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`.
|
|
816
904
|
*/
|
|
817
|
-
public async
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
905
|
+
public async makeChainTipsOverride(override: {
|
|
906
|
+
pending?: CheckpointNumber;
|
|
907
|
+
proven?: CheckpointNumber;
|
|
908
|
+
}): Promise<StateOverride> {
|
|
909
|
+
if (override.pending === undefined && override.proven === undefined) {
|
|
821
910
|
return [];
|
|
822
911
|
}
|
|
823
912
|
const slot = RollupContract.stfStorageSlot;
|
|
824
913
|
const currentValue = await this.client.getStorageAt({ address: this.address, slot });
|
|
825
|
-
const
|
|
826
|
-
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;
|
|
827
923
|
return [
|
|
828
924
|
{
|
|
829
925
|
address: this.address,
|
|
@@ -832,6 +928,168 @@ export class RollupContract {
|
|
|
832
928
|
];
|
|
833
929
|
}
|
|
834
930
|
|
|
931
|
+
/**
|
|
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.
|
|
938
|
+
*/
|
|
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;
|
|
947
|
+
|
|
948
|
+
const stateDiff: { slot: `0x${string}`; value: `0x${string}` }[] = [];
|
|
949
|
+
|
|
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
|
+
}
|
|
977
|
+
|
|
978
|
+
if (stateDiff.length === 0) {
|
|
979
|
+
return [];
|
|
980
|
+
}
|
|
981
|
+
return [{ address: this.address, stateDiff }];
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Returns a state override that sets archives[checkpointNumber] to the given archive value.
|
|
986
|
+
* Used when simulating a canProposeAtTime call where the local archive differs from L1
|
|
987
|
+
* (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
|
|
988
|
+
*/
|
|
989
|
+
public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
|
|
990
|
+
const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
|
|
991
|
+
const archiveSlot = hexToBigInt(
|
|
992
|
+
keccak256(
|
|
993
|
+
encodeAbiParameters(
|
|
994
|
+
[{ type: 'uint256' }, { type: 'uint256' }],
|
|
995
|
+
[BigInt(checkpointNumber), archivesMappingBase],
|
|
996
|
+
),
|
|
997
|
+
),
|
|
998
|
+
);
|
|
999
|
+
return [
|
|
1000
|
+
{
|
|
1001
|
+
address: this.address,
|
|
1002
|
+
stateDiff: [
|
|
1003
|
+
{
|
|
1004
|
+
slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
|
|
1005
|
+
value: archive.toString(),
|
|
1006
|
+
},
|
|
1007
|
+
],
|
|
1008
|
+
},
|
|
1009
|
+
];
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
|
|
1013
|
+
public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
|
|
1014
|
+
type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
|
|
1015
|
+
const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
|
|
1016
|
+
for (const override of overrides) {
|
|
1017
|
+
for (const entry of override) {
|
|
1018
|
+
const key = entry.address.toLowerCase();
|
|
1019
|
+
const existing = byAddress.get(key);
|
|
1020
|
+
if (existing) {
|
|
1021
|
+
existing.stateDiff.push(...(entry.stateDiff ?? []));
|
|
1022
|
+
if (entry.balance !== undefined) {
|
|
1023
|
+
existing.balance = entry.balance;
|
|
1024
|
+
}
|
|
1025
|
+
} else {
|
|
1026
|
+
byAddress.set(key, {
|
|
1027
|
+
address: entry.address,
|
|
1028
|
+
balance: entry.balance,
|
|
1029
|
+
stateDiff: [...(entry.stateDiff ?? [])],
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return [...byAddress.values()];
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
|
|
1038
|
+
public static compressFeeHeader(feeHeader: FeeHeader): bigint {
|
|
1039
|
+
const MASK_48_BITS = (1n << 48n) - 1n;
|
|
1040
|
+
const MASK_64_BITS = (1n << 64n) - 1n;
|
|
1041
|
+
const MASK_63_BITS = (1n << 63n) - 1n;
|
|
1042
|
+
|
|
1043
|
+
let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
|
|
1044
|
+
value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
|
|
1045
|
+
value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
|
|
1046
|
+
value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
|
|
1047
|
+
value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
|
|
1048
|
+
value |= 1n << 255n; // preheat flag
|
|
1049
|
+
return value;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/** Computes the fee header for a child checkpoint given parent fee header and child data.
|
|
1053
|
+
* Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
|
|
1054
|
+
public static computeChildFeeHeader(
|
|
1055
|
+
parentFeeHeader: FeeHeader,
|
|
1056
|
+
childManaUsed: bigint,
|
|
1057
|
+
feeAssetPriceModifier: bigint,
|
|
1058
|
+
manaTarget: bigint,
|
|
1059
|
+
): FeeHeader {
|
|
1060
|
+
const MIN_ETH_PER_FEE_ASSET = 100n;
|
|
1061
|
+
const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
|
|
1062
|
+
|
|
1063
|
+
// excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
|
|
1064
|
+
const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
|
|
1065
|
+
const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
|
|
1066
|
+
|
|
1067
|
+
// ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
|
|
1068
|
+
const parentPrice =
|
|
1069
|
+
parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
|
|
1070
|
+
let newPrice: bigint;
|
|
1071
|
+
if (feeAssetPriceModifier >= 0n) {
|
|
1072
|
+
newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
|
|
1073
|
+
} else {
|
|
1074
|
+
const absMod = -feeAssetPriceModifier;
|
|
1075
|
+
newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
|
|
1076
|
+
}
|
|
1077
|
+
if (newPrice < MIN_ETH_PER_FEE_ASSET) {
|
|
1078
|
+
newPrice = MIN_ETH_PER_FEE_ASSET;
|
|
1079
|
+
}
|
|
1080
|
+
if (newPrice > MAX_ETH_PER_FEE_ASSET) {
|
|
1081
|
+
newPrice = MAX_ETH_PER_FEE_ASSET;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
return {
|
|
1085
|
+
excessMana,
|
|
1086
|
+
manaUsed: childManaUsed,
|
|
1087
|
+
ethPerFeeAsset: newPrice,
|
|
1088
|
+
congestionCost: 0n,
|
|
1089
|
+
proverCost: 0n,
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
835
1093
|
/** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
|
|
836
1094
|
public buildInvalidateBadAttestationRequest(
|
|
837
1095
|
checkpointNumber: CheckpointNumber,
|
|
@@ -880,8 +1138,8 @@ export class RollupContract {
|
|
|
880
1138
|
return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
|
|
881
1139
|
}
|
|
882
1140
|
|
|
883
|
-
getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
|
|
884
|
-
return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
|
|
1141
|
+
getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
|
|
1142
|
+
return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
|
|
885
1143
|
}
|
|
886
1144
|
|
|
887
1145
|
async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
|
|
@@ -938,13 +1196,20 @@ export class RollupContract {
|
|
|
938
1196
|
}
|
|
939
1197
|
|
|
940
1198
|
async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
|
|
941
|
-
|
|
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 });
|
|
942
1206
|
const gse = new GSEContract(this.client, await this.getGSE());
|
|
943
|
-
const ts = timestamp ?? (await this.client.getBlock()).timestamp;
|
|
944
1207
|
const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
|
|
945
1208
|
const chunks = chunk(indices, 1000);
|
|
946
1209
|
|
|
947
|
-
const results = await Promise.all(
|
|
1210
|
+
const results = await Promise.all(
|
|
1211
|
+
chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk, { blockNumber })),
|
|
1212
|
+
);
|
|
948
1213
|
return results.flat().map(addr => EthAddress.fromString(addr));
|
|
949
1214
|
}
|
|
950
1215
|
|
|
@@ -1027,39 +1292,57 @@ export class RollupContract {
|
|
|
1027
1292
|
});
|
|
1028
1293
|
}
|
|
1029
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
|
+
*/
|
|
1030
1300
|
public listenToSlasherChanged(
|
|
1031
1301
|
callback: (args: { oldSlasher: `0x${string}`; newSlasher: `0x${string}` }) => unknown,
|
|
1302
|
+
options?: WatchContractEventOptions,
|
|
1032
1303
|
): WatchContractEventReturnType {
|
|
1033
|
-
return
|
|
1034
|
-
|
|
1304
|
+
return watchContractEvent(
|
|
1305
|
+
this.client,
|
|
1306
|
+
this.logger,
|
|
1035
1307
|
{
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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 });
|
|
1042
1315
|
}
|
|
1043
1316
|
},
|
|
1044
1317
|
},
|
|
1318
|
+
{ pollingIntervalMs: SLASHER_UPDATED_POLLING_INTERVAL_MS, ...options },
|
|
1045
1319
|
);
|
|
1046
1320
|
}
|
|
1047
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
|
+
*/
|
|
1048
1327
|
public listenToCheckpointInvalidated(
|
|
1049
|
-
callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
|
|
1328
|
+
callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
|
|
1329
|
+
options?: WatchContractEventOptions,
|
|
1050
1330
|
): WatchContractEventReturnType {
|
|
1051
|
-
return
|
|
1052
|
-
|
|
1331
|
+
return watchContractEvent(
|
|
1332
|
+
this.client,
|
|
1333
|
+
this.logger,
|
|
1053
1334
|
{
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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 });
|
|
1060
1342
|
}
|
|
1061
1343
|
},
|
|
1062
1344
|
},
|
|
1345
|
+
options,
|
|
1063
1346
|
);
|
|
1064
1347
|
}
|
|
1065
1348
|
|
|
@@ -1071,23 +1354,39 @@ export class RollupContract {
|
|
|
1071
1354
|
}));
|
|
1072
1355
|
}
|
|
1073
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
|
+
*/
|
|
1074
1361
|
public listenToSlash(
|
|
1075
1362
|
callback: (args: { amount: bigint; attester: EthAddress }) => unknown,
|
|
1363
|
+
options?: WatchContractEventOptions,
|
|
1076
1364
|
): WatchContractEventReturnType {
|
|
1077
|
-
return
|
|
1078
|
-
|
|
1365
|
+
return watchContractEvent(
|
|
1366
|
+
this.client,
|
|
1367
|
+
this.logger,
|
|
1079
1368
|
{
|
|
1369
|
+
address: this.address,
|
|
1370
|
+
abi: RollupAbi,
|
|
1371
|
+
eventName: 'Slashed',
|
|
1080
1372
|
strict: true,
|
|
1081
|
-
|
|
1082
|
-
for (const log of logs) {
|
|
1083
|
-
const args = log.args;
|
|
1084
|
-
callback({ amount: args.amount!, attester: EthAddress.fromString(args.attester!) });
|
|
1085
|
-
}
|
|
1086
|
-
},
|
|
1373
|
+
onLog: log => callback({ amount: log.args.amount, attester: EthAddress.fromString(log.args.attester) }),
|
|
1087
1374
|
},
|
|
1375
|
+
options,
|
|
1088
1376
|
);
|
|
1089
1377
|
}
|
|
1090
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
|
+
|
|
1091
1390
|
/** Fetches CheckpointProposed events within the given block range. */
|
|
1092
1391
|
async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
|
|
1093
1392
|
const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
|
|
@@ -1120,4 +1419,48 @@ export class RollupContract {
|
|
|
1120
1419
|
},
|
|
1121
1420
|
}));
|
|
1122
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
|
+
}
|
|
1123
1466
|
}
|