@aztec/ethereum 0.0.1-commit.2448fdb → 0.0.1-commit.2606882
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 +17 -3
- package/dest/client.d.ts.map +1 -1
- package/dest/client.js +49 -3
- package/dest/config.d.ts +6 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +6 -10
- 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 +108 -33
- package/dest/contracts/governance.d.ts.map +1 -1
- package/dest/contracts/governance.js +193 -12
- package/dest/contracts/governance_proposer.d.ts +33 -3
- package/dest/contracts/governance_proposer.d.ts.map +1 -1
- package/dest/contracts/governance_proposer.js +51 -9
- 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 +127 -12
- package/dest/contracts/multicall.d.ts.map +1 -1
- package/dest/contracts/multicall.js +201 -94
- 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 +5239 -23
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +326 -61
- package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +3 -4
- package/dest/contracts/slashing_proposer.d.ts.map +1 -0
- package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +13 -15
- package/dest/deploy_aztec_l1_contracts.d.ts +3 -6
- package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_aztec_l1_contracts.js +2 -4
- 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 +13409 -15883
- 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/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 +54 -35
- 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 +2 -1
- 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 +21 -7
- package/dest/publisher_manager.d.ts.map +1 -1
- package/dest/publisher_manager.js +81 -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/chain_monitor.d.ts +2 -1
- package/dest/test/chain_monitor.d.ts.map +1 -1
- package/dest/test/chain_monitor.js +5 -0
- package/dest/test/eth_cheat_codes.d.ts +7 -1
- package/dest/test/eth_cheat_codes.d.ts.map +1 -1
- package/dest/test/eth_cheat_codes.js +11 -0
- package/dest/test/rollup_cheat_codes.d.ts +27 -3
- package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
- package/dest/test/rollup_cheat_codes.js +50 -6
- package/dest/utils.d.ts +9 -2
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +48 -16
- package/package.json +6 -8
- package/src/client.ts +57 -1
- package/src/config.ts +10 -14
- 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 +266 -9
- package/src/contracts/governance_proposer.ts +52 -5
- package/src/contracts/inbox.ts +13 -5
- package/src/contracts/index.ts +2 -2
- package/src/contracts/multicall.ts +243 -105
- package/src/contracts/outbox.ts +42 -8
- package/src/contracts/rollup.ts +364 -57
- package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +14 -16
- package/src/deploy_aztec_l1_contracts.ts +1 -5
- 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/l1_tx_utils.ts +51 -28
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +2 -1
- package/src/l1_tx_utils/tx_delayer.ts +16 -7
- package/src/publisher_manager.ts +105 -10
- package/src/queries.ts +70 -4
- package/src/test/chain_monitor.ts +5 -0
- package/src/test/eth_cheat_codes.ts +10 -0
- package/src/test/rollup_cheat_codes.ts +53 -5
- package/src/utils.ts +45 -18
- 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.map +0 -1
- 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,11 +36,10 @@ 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';
|
|
38
44
|
|
|
39
45
|
export type ViemCommitteeAttestation = {
|
|
@@ -55,7 +61,6 @@ export type L1RollupContractAddresses = Pick<
|
|
|
55
61
|
| 'feeJuiceAddress'
|
|
56
62
|
| 'stakingAssetAddress'
|
|
57
63
|
| 'rewardDistributorAddress'
|
|
58
|
-
| 'slashFactoryAddress'
|
|
59
64
|
| 'gseAddress'
|
|
60
65
|
>;
|
|
61
66
|
|
|
@@ -85,12 +90,6 @@ export type ViemGasFees = {
|
|
|
85
90
|
feePerL2Gas: bigint;
|
|
86
91
|
};
|
|
87
92
|
|
|
88
|
-
export enum SlashingProposerType {
|
|
89
|
-
None = 0,
|
|
90
|
-
Tally = 1,
|
|
91
|
-
Empire = 2,
|
|
92
|
-
}
|
|
93
|
-
|
|
94
93
|
/**
|
|
95
94
|
* Status of a validator/attester in the staking system.
|
|
96
95
|
* Matches the Status enum in StakingLib.sol
|
|
@@ -134,6 +133,31 @@ export type L1FeeData = {
|
|
|
134
133
|
blobFee: bigint;
|
|
135
134
|
};
|
|
136
135
|
|
|
136
|
+
/** Field offsets within the CompressedTempCheckpointLog struct in Solidity storage. */
|
|
137
|
+
export enum TempCheckpointLogField {
|
|
138
|
+
HeaderHash = 0,
|
|
139
|
+
BlobCommitmentsHash = 1,
|
|
140
|
+
OutHash = 2,
|
|
141
|
+
AttestationsHash = 3,
|
|
142
|
+
PayloadDigest = 4,
|
|
143
|
+
SlotNumber = 5,
|
|
144
|
+
FeeHeader = 6,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Field-level override input for `tempCheckpointLogs[checkpointNumber]`. Covers the fields the
|
|
149
|
+
* `propose()` path actually reads back. `payloadDigest` is `Buffer32` because it carries an
|
|
150
|
+
* arbitrary `bytes32` value rather than a BN254 scalar. `slotNumber` carries the uint32 portion
|
|
151
|
+
* of the on-chain `CompressedSlot`.
|
|
152
|
+
*/
|
|
153
|
+
export type TempCheckpointLogOverrideFields = {
|
|
154
|
+
headerHash?: Fr;
|
|
155
|
+
outHash?: Fr;
|
|
156
|
+
payloadDigest?: Buffer32;
|
|
157
|
+
slotNumber?: SlotNumber;
|
|
158
|
+
feeHeader?: FeeHeader;
|
|
159
|
+
};
|
|
160
|
+
|
|
137
161
|
/** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
|
|
138
162
|
export type ManaMinFeeComponents = {
|
|
139
163
|
sequencerCost: bigint;
|
|
@@ -210,8 +234,35 @@ export type CheckpointProposedArgs = {
|
|
|
210
234
|
/** Log type for CheckpointProposed events. */
|
|
211
235
|
export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
|
|
212
236
|
|
|
237
|
+
const INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR = 'ValidatorSelection__InsufficientValidatorSetSize';
|
|
238
|
+
|
|
239
|
+
function isValidatorSelectionError(err: unknown, errorName: string): boolean {
|
|
240
|
+
return (
|
|
241
|
+
getErrorCause(err, ContractFunctionRevertedError)?.data?.errorName === errorName ||
|
|
242
|
+
decodeRpcRequestErrorName(err) === errorName
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function decodeRpcRequestErrorName(err: unknown): string | undefined {
|
|
247
|
+
const data = getErrorCause(err, RpcRequestError)?.data;
|
|
248
|
+
if (!isHexString(data)) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
return decodeErrorResult({ abi: RollupAbi, data }).errorName;
|
|
254
|
+
} catch {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function isHexString(value: unknown): value is Hex {
|
|
260
|
+
return typeof value === 'string' && value.startsWith('0x');
|
|
261
|
+
}
|
|
262
|
+
|
|
213
263
|
export class RollupContract {
|
|
214
264
|
private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
|
|
265
|
+
private readonly logger = createLogger('ethereum:rollup');
|
|
215
266
|
|
|
216
267
|
private static cachedStfStorageSlot: Hex | undefined;
|
|
217
268
|
private cachedEscapeHatch?: {
|
|
@@ -241,7 +292,7 @@ export class RollupContract {
|
|
|
241
292
|
|
|
242
293
|
static getFromConfig(config: L1ReaderConfig) {
|
|
243
294
|
const client = getPublicClient(config);
|
|
244
|
-
const address = config.
|
|
295
|
+
const address = config.rollupAddress.toString();
|
|
245
296
|
return new RollupContract(client, address);
|
|
246
297
|
}
|
|
247
298
|
|
|
@@ -267,34 +318,18 @@ export class RollupContract {
|
|
|
267
318
|
return this.rollup;
|
|
268
319
|
}
|
|
269
320
|
|
|
270
|
-
public async getSlashingProposer(): Promise<
|
|
271
|
-
EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
|
|
272
|
-
> {
|
|
321
|
+
public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
|
|
273
322
|
const slasher = await this.getSlasherContract();
|
|
274
323
|
if (!slasher) {
|
|
275
324
|
return undefined;
|
|
276
325
|
}
|
|
277
326
|
|
|
278
327
|
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}`);
|
|
328
|
+
if (proposerAddress.isZero()) {
|
|
329
|
+
return undefined;
|
|
297
330
|
}
|
|
331
|
+
|
|
332
|
+
return new SlashingProposerContract(this.client, proposerAddress);
|
|
298
333
|
}
|
|
299
334
|
|
|
300
335
|
@memoize
|
|
@@ -495,7 +530,11 @@ export class RollupContract {
|
|
|
495
530
|
|
|
496
531
|
const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
|
|
497
532
|
return isOpen;
|
|
498
|
-
} catch {
|
|
533
|
+
} catch (err) {
|
|
534
|
+
this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
|
|
535
|
+
epoch: Number(epoch),
|
|
536
|
+
error: err,
|
|
537
|
+
});
|
|
499
538
|
return false;
|
|
500
539
|
}
|
|
501
540
|
}
|
|
@@ -531,8 +570,9 @@ export class RollupContract {
|
|
|
531
570
|
return this.rollup.read.getCheckpointReward();
|
|
532
571
|
}
|
|
533
572
|
|
|
534
|
-
async getCheckpointNumber(): Promise<CheckpointNumber> {
|
|
535
|
-
|
|
573
|
+
async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
574
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
575
|
+
return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
|
|
536
576
|
}
|
|
537
577
|
|
|
538
578
|
async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
@@ -540,18 +580,31 @@ export class RollupContract {
|
|
|
540
580
|
return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
|
|
541
581
|
}
|
|
542
582
|
|
|
543
|
-
async getSlotNumber(): Promise<SlotNumber> {
|
|
544
|
-
|
|
583
|
+
async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
|
|
584
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
585
|
+
return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
|
|
545
586
|
}
|
|
546
587
|
|
|
547
|
-
async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
|
|
548
|
-
|
|
588
|
+
async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
|
|
589
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
590
|
+
const result = await this.rollup.read.getL1FeesAt([timestamp], options);
|
|
549
591
|
return {
|
|
550
592
|
baseFee: result.baseFee,
|
|
551
593
|
blobFee: result.blobFee,
|
|
552
594
|
};
|
|
553
595
|
}
|
|
554
596
|
|
|
597
|
+
async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
|
|
598
|
+
const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
|
|
599
|
+
return {
|
|
600
|
+
excessMana: result.excessMana,
|
|
601
|
+
manaUsed: result.manaUsed,
|
|
602
|
+
ethPerFeeAsset: result.ethPerFeeAsset,
|
|
603
|
+
congestionCost: result.congestionCost,
|
|
604
|
+
proverCost: result.proverCost,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
555
608
|
getEthPerFeeAsset(): Promise<bigint> {
|
|
556
609
|
return this.rollup.read.getEthPerFeeAsset();
|
|
557
610
|
}
|
|
@@ -565,7 +618,7 @@ export class RollupContract {
|
|
|
565
618
|
args: [timestamp],
|
|
566
619
|
})
|
|
567
620
|
.catch(e => {
|
|
568
|
-
if (e
|
|
621
|
+
if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
|
|
569
622
|
return { result: undefined };
|
|
570
623
|
}
|
|
571
624
|
throw e;
|
|
@@ -595,7 +648,7 @@ export class RollupContract {
|
|
|
595
648
|
args: [],
|
|
596
649
|
})
|
|
597
650
|
.catch(e => {
|
|
598
|
-
if (e
|
|
651
|
+
if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
|
|
599
652
|
return { result: undefined };
|
|
600
653
|
}
|
|
601
654
|
throw e;
|
|
@@ -626,8 +679,9 @@ export class RollupContract {
|
|
|
626
679
|
return EthAddress.fromString(result);
|
|
627
680
|
}
|
|
628
681
|
|
|
629
|
-
async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
|
|
630
|
-
|
|
682
|
+
async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
|
|
683
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
684
|
+
const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
|
|
631
685
|
return {
|
|
632
686
|
archive: Fr.fromString(result.archive),
|
|
633
687
|
headerHash: Buffer32.fromString(result.headerHash),
|
|
@@ -660,6 +714,30 @@ export class RollupContract {
|
|
|
660
714
|
);
|
|
661
715
|
}
|
|
662
716
|
|
|
717
|
+
/**
|
|
718
|
+
* Returns the effective pending checkpoint, accounting for potential prunes.
|
|
719
|
+
* When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
|
|
720
|
+
* This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
|
|
721
|
+
* @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
|
|
722
|
+
* @param options - Optional L1 block number to pin the queries to.
|
|
723
|
+
*/
|
|
724
|
+
getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
|
|
725
|
+
return retry(
|
|
726
|
+
async () => {
|
|
727
|
+
const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
|
|
728
|
+
const canPrune = await this.canPruneAtTime(timestamp, options);
|
|
729
|
+
if (canPrune) {
|
|
730
|
+
const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
|
|
731
|
+
return await this.getCheckpoint(provenCheckpointNumber, options);
|
|
732
|
+
}
|
|
733
|
+
const pendingCheckpointNumber = await this.getCheckpointNumber(options);
|
|
734
|
+
return await this.getCheckpoint(pendingCheckpointNumber, options);
|
|
735
|
+
},
|
|
736
|
+
'getting effective pending checkpoint',
|
|
737
|
+
makeBackoff([0.5, 0.5, 0.5]),
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
663
741
|
async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
|
|
664
742
|
const { pending, proven } = await this.rollup.read.getTips();
|
|
665
743
|
return {
|
|
@@ -782,7 +860,7 @@ export class RollupContract {
|
|
|
782
860
|
archive: Buffer,
|
|
783
861
|
account: `0x${string}` | Account,
|
|
784
862
|
timestamp: bigint,
|
|
785
|
-
|
|
863
|
+
stateOverride: StateOverride = [],
|
|
786
864
|
): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
|
|
787
865
|
const timeOfNextL1Slot = timestamp;
|
|
788
866
|
const who = typeof account === 'string' ? account : account.address;
|
|
@@ -796,7 +874,7 @@ export class RollupContract {
|
|
|
796
874
|
functionName: 'canProposeAtTime',
|
|
797
875
|
args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
|
|
798
876
|
account,
|
|
799
|
-
stateOverride
|
|
877
|
+
stateOverride,
|
|
800
878
|
});
|
|
801
879
|
|
|
802
880
|
return {
|
|
@@ -810,20 +888,32 @@ export class RollupContract {
|
|
|
810
888
|
}
|
|
811
889
|
|
|
812
890
|
/**
|
|
813
|
-
* Returns a state override that sets the pending
|
|
814
|
-
*
|
|
815
|
-
*
|
|
891
|
+
* Returns a state override that sets the pending and/or proven checkpoint numbers. Useful for simulations.
|
|
892
|
+
* Both values share a single storage slot (pending in the upper 128 bits, proven in the lower 128 bits), so
|
|
893
|
+
* a single combined override is emitted to avoid the second state-diff clobbering the first. The current live
|
|
894
|
+
* value is read once to preserve any half not being overridden. Returns an empty override if neither is set.
|
|
895
|
+
*
|
|
896
|
+
* Throws if the resulting `proven > pending`, which would crash the simulation: `STFLib.canPruneAtTime` calls
|
|
897
|
+
* `getEpochForCheckpoint(proven + 1)` whenever `pending != proven`, and that asserts `_n <= tips.pending`.
|
|
816
898
|
*/
|
|
817
|
-
public async
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
899
|
+
public async makeChainTipsOverride(override: {
|
|
900
|
+
pending?: CheckpointNumber;
|
|
901
|
+
proven?: CheckpointNumber;
|
|
902
|
+
}): Promise<StateOverride> {
|
|
903
|
+
if (override.pending === undefined && override.proven === undefined) {
|
|
821
904
|
return [];
|
|
822
905
|
}
|
|
823
906
|
const slot = RollupContract.stfStorageSlot;
|
|
824
907
|
const currentValue = await this.client.getStorageAt({ address: this.address, slot });
|
|
825
|
-
const
|
|
826
|
-
const
|
|
908
|
+
const currentRaw = currentValue ? hexToBigInt(currentValue) : 0n;
|
|
909
|
+
const currentPending = currentRaw >> 128n;
|
|
910
|
+
const currentProven = currentRaw & ((1n << 128n) - 1n);
|
|
911
|
+
const newPending = override.pending !== undefined ? BigInt(override.pending) : currentPending;
|
|
912
|
+
const newProven = override.proven !== undefined ? BigInt(override.proven) : currentProven;
|
|
913
|
+
if (newProven > newPending) {
|
|
914
|
+
throw new Error(`Invalid chain tips override: proven (${newProven}) > pending (${newPending})`);
|
|
915
|
+
}
|
|
916
|
+
const newValue = (newPending << 128n) | newProven;
|
|
827
917
|
return [
|
|
828
918
|
{
|
|
829
919
|
address: this.address,
|
|
@@ -832,6 +922,168 @@ export class RollupContract {
|
|
|
832
922
|
];
|
|
833
923
|
}
|
|
834
924
|
|
|
925
|
+
/**
|
|
926
|
+
* Returns a state override that patches `tempCheckpointLogs[checkpointNumber]` with every field that
|
|
927
|
+
* the L1 contract reads during `propose()` for the checkpoint that builds on top of it (proposer
|
|
928
|
+
* pipelining simulation). Mirrors the writes done by `ProposeLib.addTempCheckpointLog`.
|
|
929
|
+
*
|
|
930
|
+
* `blobCommitmentsHash` and `attestationsHash` are intentionally not exposed here — the propose path
|
|
931
|
+
* never asserts against them, so leaving them at storage zero is harmless.
|
|
932
|
+
*/
|
|
933
|
+
public async makeTempCheckpointLogOverride(
|
|
934
|
+
checkpointNumber: CheckpointNumber,
|
|
935
|
+
fields: TempCheckpointLogOverrideFields,
|
|
936
|
+
): Promise<StateOverride> {
|
|
937
|
+
const constants = await this.getRollupConstants();
|
|
938
|
+
const slotAt = (field: TempCheckpointLogField) =>
|
|
939
|
+
`0x${this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants).toString(16).padStart(64, '0')}` as const;
|
|
940
|
+
const word = (v: bigint) => `0x${v.toString(16).padStart(64, '0')}` as const;
|
|
941
|
+
|
|
942
|
+
const stateDiff: { slot: `0x${string}`; value: `0x${string}` }[] = [];
|
|
943
|
+
|
|
944
|
+
if (fields.headerHash) {
|
|
945
|
+
stateDiff.push({ slot: slotAt(TempCheckpointLogField.HeaderHash), value: fields.headerHash.toString() });
|
|
946
|
+
}
|
|
947
|
+
if (fields.outHash) {
|
|
948
|
+
stateDiff.push({ slot: slotAt(TempCheckpointLogField.OutHash), value: fields.outHash.toString() });
|
|
949
|
+
}
|
|
950
|
+
if (fields.payloadDigest) {
|
|
951
|
+
stateDiff.push({
|
|
952
|
+
slot: slotAt(TempCheckpointLogField.PayloadDigest),
|
|
953
|
+
value: fields.payloadDigest.toString() as `0x${string}`,
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
if (fields.slotNumber !== undefined) {
|
|
957
|
+
// CompressedSlot is uint32 on L1 (SafeCast.toUint32 reverts on overflow). Match that behavior here
|
|
958
|
+
// so a malformed override surfaces immediately rather than silently truncating into a wrong slot.
|
|
959
|
+
const slotNumber = BigInt(fields.slotNumber);
|
|
960
|
+
if (slotNumber < 0n || slotNumber > 0xffffffffn) {
|
|
961
|
+
throw new Error(`slotNumber ${slotNumber} does not fit in uint32`);
|
|
962
|
+
}
|
|
963
|
+
stateDiff.push({ slot: slotAt(TempCheckpointLogField.SlotNumber), value: word(slotNumber) });
|
|
964
|
+
}
|
|
965
|
+
if (fields.feeHeader) {
|
|
966
|
+
stateDiff.push({
|
|
967
|
+
slot: slotAt(TempCheckpointLogField.FeeHeader),
|
|
968
|
+
value: word(RollupContract.compressFeeHeader(fields.feeHeader)),
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (stateDiff.length === 0) {
|
|
973
|
+
return [];
|
|
974
|
+
}
|
|
975
|
+
return [{ address: this.address, stateDiff }];
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Returns a state override that sets archives[checkpointNumber] to the given archive value.
|
|
980
|
+
* Used when simulating a canProposeAtTime call where the local archive differs from L1
|
|
981
|
+
* (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
|
|
982
|
+
*/
|
|
983
|
+
public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
|
|
984
|
+
const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
|
|
985
|
+
const archiveSlot = hexToBigInt(
|
|
986
|
+
keccak256(
|
|
987
|
+
encodeAbiParameters(
|
|
988
|
+
[{ type: 'uint256' }, { type: 'uint256' }],
|
|
989
|
+
[BigInt(checkpointNumber), archivesMappingBase],
|
|
990
|
+
),
|
|
991
|
+
),
|
|
992
|
+
);
|
|
993
|
+
return [
|
|
994
|
+
{
|
|
995
|
+
address: this.address,
|
|
996
|
+
stateDiff: [
|
|
997
|
+
{
|
|
998
|
+
slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
|
|
999
|
+
value: archive.toString(),
|
|
1000
|
+
},
|
|
1001
|
+
],
|
|
1002
|
+
},
|
|
1003
|
+
];
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
|
|
1007
|
+
public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
|
|
1008
|
+
type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
|
|
1009
|
+
const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
|
|
1010
|
+
for (const override of overrides) {
|
|
1011
|
+
for (const entry of override) {
|
|
1012
|
+
const key = entry.address.toLowerCase();
|
|
1013
|
+
const existing = byAddress.get(key);
|
|
1014
|
+
if (existing) {
|
|
1015
|
+
existing.stateDiff.push(...(entry.stateDiff ?? []));
|
|
1016
|
+
if (entry.balance !== undefined) {
|
|
1017
|
+
existing.balance = entry.balance;
|
|
1018
|
+
}
|
|
1019
|
+
} else {
|
|
1020
|
+
byAddress.set(key, {
|
|
1021
|
+
address: entry.address,
|
|
1022
|
+
balance: entry.balance,
|
|
1023
|
+
stateDiff: [...(entry.stateDiff ?? [])],
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return [...byAddress.values()];
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
|
|
1032
|
+
public static compressFeeHeader(feeHeader: FeeHeader): bigint {
|
|
1033
|
+
const MASK_48_BITS = (1n << 48n) - 1n;
|
|
1034
|
+
const MASK_64_BITS = (1n << 64n) - 1n;
|
|
1035
|
+
const MASK_63_BITS = (1n << 63n) - 1n;
|
|
1036
|
+
|
|
1037
|
+
let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
|
|
1038
|
+
value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
|
|
1039
|
+
value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
|
|
1040
|
+
value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
|
|
1041
|
+
value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
|
|
1042
|
+
value |= 1n << 255n; // preheat flag
|
|
1043
|
+
return value;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/** Computes the fee header for a child checkpoint given parent fee header and child data.
|
|
1047
|
+
* Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
|
|
1048
|
+
public static computeChildFeeHeader(
|
|
1049
|
+
parentFeeHeader: FeeHeader,
|
|
1050
|
+
childManaUsed: bigint,
|
|
1051
|
+
feeAssetPriceModifier: bigint,
|
|
1052
|
+
manaTarget: bigint,
|
|
1053
|
+
): FeeHeader {
|
|
1054
|
+
const MIN_ETH_PER_FEE_ASSET = 100n;
|
|
1055
|
+
const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
|
|
1056
|
+
|
|
1057
|
+
// excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
|
|
1058
|
+
const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
|
|
1059
|
+
const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
|
|
1060
|
+
|
|
1061
|
+
// ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
|
|
1062
|
+
const parentPrice =
|
|
1063
|
+
parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
|
|
1064
|
+
let newPrice: bigint;
|
|
1065
|
+
if (feeAssetPriceModifier >= 0n) {
|
|
1066
|
+
newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
|
|
1067
|
+
} else {
|
|
1068
|
+
const absMod = -feeAssetPriceModifier;
|
|
1069
|
+
newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
|
|
1070
|
+
}
|
|
1071
|
+
if (newPrice < MIN_ETH_PER_FEE_ASSET) {
|
|
1072
|
+
newPrice = MIN_ETH_PER_FEE_ASSET;
|
|
1073
|
+
}
|
|
1074
|
+
if (newPrice > MAX_ETH_PER_FEE_ASSET) {
|
|
1075
|
+
newPrice = MAX_ETH_PER_FEE_ASSET;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
return {
|
|
1079
|
+
excessMana,
|
|
1080
|
+
manaUsed: childManaUsed,
|
|
1081
|
+
ethPerFeeAsset: newPrice,
|
|
1082
|
+
congestionCost: 0n,
|
|
1083
|
+
proverCost: 0n,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
|
|
835
1087
|
/** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
|
|
836
1088
|
public buildInvalidateBadAttestationRequest(
|
|
837
1089
|
checkpointNumber: CheckpointNumber,
|
|
@@ -880,8 +1132,8 @@ export class RollupContract {
|
|
|
880
1132
|
return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
|
|
881
1133
|
}
|
|
882
1134
|
|
|
883
|
-
getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
|
|
884
|
-
return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
|
|
1135
|
+
getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
|
|
1136
|
+
return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
|
|
885
1137
|
}
|
|
886
1138
|
|
|
887
1139
|
async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
|
|
@@ -1046,7 +1298,7 @@ export class RollupContract {
|
|
|
1046
1298
|
}
|
|
1047
1299
|
|
|
1048
1300
|
public listenToCheckpointInvalidated(
|
|
1049
|
-
callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
|
|
1301
|
+
callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
|
|
1050
1302
|
): WatchContractEventReturnType {
|
|
1051
1303
|
return this.rollup.watchEvent.CheckpointInvalidated(
|
|
1052
1304
|
{},
|
|
@@ -1055,7 +1307,7 @@ export class RollupContract {
|
|
|
1055
1307
|
for (const log of logs) {
|
|
1056
1308
|
const args = log.args;
|
|
1057
1309
|
if (args.checkpointNumber !== undefined) {
|
|
1058
|
-
callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
|
|
1310
|
+
callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
|
|
1059
1311
|
}
|
|
1060
1312
|
}
|
|
1061
1313
|
},
|
|
@@ -1088,6 +1340,17 @@ export class RollupContract {
|
|
|
1088
1340
|
);
|
|
1089
1341
|
}
|
|
1090
1342
|
|
|
1343
|
+
/**
|
|
1344
|
+
* Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
|
|
1345
|
+
* The Rollup inherits from Ownable and emits this event in its constructor, so the event
|
|
1346
|
+
* is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
|
|
1347
|
+
* probe to detect RPC nodes that prune historical logs.
|
|
1348
|
+
*/
|
|
1349
|
+
async getOwnershipTransferredEventsAtDeploy() {
|
|
1350
|
+
const l1StartBlock = await this.getL1StartBlock();
|
|
1351
|
+
return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1091
1354
|
/** Fetches CheckpointProposed events within the given block range. */
|
|
1092
1355
|
async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
|
|
1093
1356
|
const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
|
|
@@ -1120,4 +1383,48 @@ export class RollupContract {
|
|
|
1120
1383
|
},
|
|
1121
1384
|
}));
|
|
1122
1385
|
}
|
|
1386
|
+
|
|
1387
|
+
/** Packs pending and proven checkpoint numbers into the chain tips storage format. */
|
|
1388
|
+
static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
|
|
1389
|
+
return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/** Storage slot for the chain tips (offset 0 within the STF storage struct). */
|
|
1393
|
+
static get chainTipsStorageSlot(): bigint {
|
|
1394
|
+
return BigInt(RollupContract.stfStorageSlot);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Computes the storage slot for a field within a tempCheckpointLog entry.
|
|
1399
|
+
* @param checkpointNumber - The checkpoint number
|
|
1400
|
+
* @param field - The field within the CompressedTempCheckpointLog struct
|
|
1401
|
+
*/
|
|
1402
|
+
async getTempCheckpointLogStorageSlot(
|
|
1403
|
+
checkpointNumber: CheckpointNumber,
|
|
1404
|
+
field: TempCheckpointLogField,
|
|
1405
|
+
): Promise<bigint> {
|
|
1406
|
+
const [epochDuration, proofSubmissionEpochs] = await Promise.all([
|
|
1407
|
+
this.getEpochDuration(),
|
|
1408
|
+
this.getProofSubmissionEpochs(),
|
|
1409
|
+
]);
|
|
1410
|
+
return this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, { epochDuration, proofSubmissionEpochs });
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
private computeTempCheckpointLogStorageSlot(
|
|
1414
|
+
checkpointNumber: CheckpointNumber,
|
|
1415
|
+
field: TempCheckpointLogField,
|
|
1416
|
+
constants: { epochDuration: number; proofSubmissionEpochs: number },
|
|
1417
|
+
): bigint {
|
|
1418
|
+
const fieldOffset = BigInt(field);
|
|
1419
|
+
const { epochDuration, proofSubmissionEpochs } = constants;
|
|
1420
|
+
const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
|
|
1421
|
+
const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
|
|
1422
|
+
const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
|
|
1423
|
+
const entryBase = BigInt(
|
|
1424
|
+
keccak256(
|
|
1425
|
+
encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
|
|
1426
|
+
),
|
|
1427
|
+
);
|
|
1428
|
+
return entryBase + fieldOffset;
|
|
1429
|
+
}
|
|
1123
1430
|
}
|