@aztec/ethereum 0.0.1-commit.f2ce05ee → 0.0.1-commit.f5a9928
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 +27 -3
- package/dest/client.d.ts.map +1 -1
- package/dest/client.js +63 -8
- package/dest/config.d.ts +17 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +38 -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/fee_asset_price_oracle.d.ts +105 -0
- package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
- package/dest/contracts/fee_asset_price_oracle.js +656 -0
- package/dest/contracts/governance.d.ts +135 -35
- package/dest/contracts/governance.d.ts.map +1 -1
- package/dest/contracts/governance.js +229 -18
- package/dest/contracts/governance_proposer.d.ts +33 -2
- package/dest/contracts/governance_proposer.d.ts.map +1 -1
- package/dest/contracts/governance_proposer.js +54 -6
- 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 +4 -3
- package/dest/contracts/index.d.ts.map +1 -1
- package/dest/contracts/index.js +3 -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/registry.d.ts +3 -1
- package/dest/contracts/registry.d.ts.map +1 -1
- package/dest/contracts/registry.js +30 -1
- package/dest/contracts/rollup.d.ts +5437 -34
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +448 -104
- 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 +5 -9
- package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_aztec_l1_contracts.js +48 -27
- package/dest/deploy_l1_contract.js +3 -3
- 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 +3 -3
- package/dest/generated/l1-contracts-defaults.js +3 -3
- package/dest/l1_artifacts.d.ts +15524 -15847
- 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 +5 -5
- package/dest/l1_reader.d.ts.map +1 -1
- package/dest/l1_reader.js +9 -7
- package/dest/l1_tx_utils/config.d.ts +7 -1
- package/dest/l1_tx_utils/config.d.ts.map +1 -1
- package/dest/l1_tx_utils/config.js +17 -4
- package/dest/l1_tx_utils/factory.d.ts +18 -10
- package/dest/l1_tx_utils/factory.d.ts.map +1 -1
- package/dest/l1_tx_utils/factory.js +17 -7
- package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
- package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
- package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
- package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
- package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
- package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
- package/dest/l1_tx_utils/index-blobs.js +2 -2
- package/dest/l1_tx_utils/index.d.ts +2 -1
- package/dest/l1_tx_utils/index.d.ts.map +1 -1
- package/dest/l1_tx_utils/index.js +1 -0
- package/dest/l1_tx_utils/l1_tx_utils.d.ts +26 -7
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils.js +156 -79
- 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 +57 -23
- package/dest/l1_tx_utils/tx_delayer.d.ts +58 -0
- package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
- package/dest/{test → l1_tx_utils}/tx_delayer.js +67 -37
- package/dest/publisher_manager.d.ts +34 -8
- package/dest/publisher_manager.d.ts.map +1 -1
- package/dest/publisher_manager.js +119 -8
- 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 +55 -4
- package/dest/test/chain_monitor.d.ts.map +1 -1
- package/dest/test/chain_monitor.js +104 -3
- package/dest/test/eth_cheat_codes.d.ts +31 -6
- package/dest/test/eth_cheat_codes.d.ts.map +1 -1
- package/dest/test/eth_cheat_codes.js +67 -44
- package/dest/test/index.d.ts +2 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +1 -2
- 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 +23 -3
- package/dest/test/start_anvil.d.ts.map +1 -1
- package/dest/test/start_anvil.js +182 -32
- package/dest/test/upgrade_utils.js +2 -2
- package/dest/utils.d.ts +9 -2
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +59 -24
- package/package.json +6 -8
- package/src/client.ts +72 -2
- package/src/config.ts +58 -14
- package/src/contracts/chain_state_override.ts +200 -0
- package/src/contracts/fee_asset_price_oracle.ts +285 -0
- package/src/contracts/governance.ts +313 -13
- package/src/contracts/governance_proposer.ts +58 -9
- package/src/contracts/gse.ts +7 -2
- package/src/contracts/inbox.ts +13 -5
- package/src/contracts/index.ts +3 -2
- package/src/contracts/multicall.ts +243 -105
- package/src/contracts/outbox.ts +42 -8
- package/src/contracts/registry.ts +31 -1
- package/src/contracts/rollup.ts +507 -109
- 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 +75 -39
- package/src/deploy_l1_contract.ts +3 -3
- package/src/foundry_binary.ts +57 -0
- package/src/generated/l1-contracts-defaults.ts +3 -3
- package/src/l1_artifacts.ts +12 -35
- package/src/l1_contract_addresses.ts +120 -94
- package/src/l1_reader.ts +17 -10
- package/src/l1_tx_utils/config.ts +23 -3
- package/src/l1_tx_utils/factory.ts +31 -31
- package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
- package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
- package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
- package/src/l1_tx_utils/index-blobs.ts +2 -2
- package/src/l1_tx_utils/index.ts +1 -0
- package/src/l1_tx_utils/l1_tx_utils.ts +149 -64
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +57 -17
- package/src/{test → l1_tx_utils}/tx_delayer.ts +93 -56
- package/src/publisher_manager.ts +143 -12
- package/src/queries.ts +70 -4
- package/src/test/blob_kzg_warmup.ts +65 -0
- package/src/test/chain_monitor.ts +148 -3
- package/src/test/eth_cheat_codes.ts +63 -47
- package/src/test/index.ts +1 -2
- package/src/test/recording_rpc_server.ts +69 -0
- package/src/test/rollup_cheat_codes.ts +115 -6
- package/src/test/start_anvil.ts +212 -32
- package/src/test/upgrade_utils.ts +2 -2
- package/src/utils.ts +56 -29
- package/dest/contracts/empire_slashing_proposer.d.ts +0 -67
- package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
- package/dest/contracts/empire_slashing_proposer.js +0 -207
- 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/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
- package/dest/test/delayed_tx_utils.d.ts +0 -13
- package/dest/test/delayed_tx_utils.d.ts.map +0 -1
- package/dest/test/delayed_tx_utils.js +0 -28
- package/dest/test/tx_delayer.d.ts +0 -36
- package/dest/test/tx_delayer.d.ts.map +0 -1
- package/src/contracts/empire_slashing_proposer.ts +0 -259
- package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
- package/src/test/delayed_tx_utils.ts +0 -52
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,39 @@ 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
|
+
|
|
163
|
+
/** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
|
|
164
|
+
export type ManaMinFeeComponents = {
|
|
165
|
+
sequencerCost: bigint;
|
|
166
|
+
proverCost: bigint;
|
|
167
|
+
congestionCost: bigint;
|
|
168
|
+
congestionMultiplier: bigint;
|
|
169
|
+
};
|
|
170
|
+
|
|
137
171
|
/**
|
|
138
172
|
* Reward configuration for the rollup
|
|
139
173
|
*/
|
|
@@ -193,17 +227,47 @@ export type CheckpointProposedArgs = {
|
|
|
193
227
|
checkpointNumber: CheckpointNumber;
|
|
194
228
|
archive: Fr;
|
|
195
229
|
versionedBlobHashes: Buffer[];
|
|
196
|
-
/** Hash of attestations
|
|
197
|
-
attestationsHash
|
|
198
|
-
/** Digest of the payload
|
|
199
|
-
payloadDigest
|
|
230
|
+
/** Hash of attestations emitted in the CheckpointProposed event. */
|
|
231
|
+
attestationsHash: Buffer32;
|
|
232
|
+
/** Digest of the payload emitted in the CheckpointProposed event. */
|
|
233
|
+
payloadDigest: Buffer32;
|
|
200
234
|
};
|
|
201
235
|
|
|
202
236
|
/** Log type for CheckpointProposed events. */
|
|
203
237
|
export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
|
|
204
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
|
+
|
|
205
268
|
export class RollupContract {
|
|
206
269
|
private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
|
|
270
|
+
private readonly logger = createLogger('ethereum:rollup');
|
|
207
271
|
|
|
208
272
|
private static cachedStfStorageSlot: Hex | undefined;
|
|
209
273
|
private cachedEscapeHatch?: {
|
|
@@ -233,7 +297,7 @@ export class RollupContract {
|
|
|
233
297
|
|
|
234
298
|
static getFromConfig(config: L1ReaderConfig) {
|
|
235
299
|
const client = getPublicClient(config);
|
|
236
|
-
const address = config.
|
|
300
|
+
const address = config.rollupAddress.toString();
|
|
237
301
|
return new RollupContract(client, address);
|
|
238
302
|
}
|
|
239
303
|
|
|
@@ -259,34 +323,18 @@ export class RollupContract {
|
|
|
259
323
|
return this.rollup;
|
|
260
324
|
}
|
|
261
325
|
|
|
262
|
-
public async getSlashingProposer(): Promise<
|
|
263
|
-
EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
|
|
264
|
-
> {
|
|
326
|
+
public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
|
|
265
327
|
const slasher = await this.getSlasherContract();
|
|
266
328
|
if (!slasher) {
|
|
267
329
|
return undefined;
|
|
268
330
|
}
|
|
269
331
|
|
|
270
332
|
const proposerAddress = await slasher.getProposer();
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
type: 'function',
|
|
274
|
-
name: 'SLASHING_PROPOSER_TYPE',
|
|
275
|
-
inputs: [],
|
|
276
|
-
outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
|
|
277
|
-
stateMutability: 'view',
|
|
278
|
-
},
|
|
279
|
-
] as const;
|
|
280
|
-
|
|
281
|
-
const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
|
|
282
|
-
const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
|
|
283
|
-
if (proposerType === SlashingProposerType.Tally.valueOf()) {
|
|
284
|
-
return new TallySlashingProposerContract(this.client, proposerAddress);
|
|
285
|
-
} else if (proposerType === SlashingProposerType.Empire.valueOf()) {
|
|
286
|
-
return new EmpireSlashingProposerContract(this.client, proposerAddress);
|
|
287
|
-
} else {
|
|
288
|
-
throw new Error(`Unknown slashing proposer type: ${proposerType}`);
|
|
333
|
+
if (proposerAddress.isZero()) {
|
|
334
|
+
return undefined;
|
|
289
335
|
}
|
|
336
|
+
|
|
337
|
+
return new SlashingProposerContract(this.client, proposerAddress);
|
|
290
338
|
}
|
|
291
339
|
|
|
292
340
|
@memoize
|
|
@@ -379,6 +427,20 @@ export class RollupContract {
|
|
|
379
427
|
return Fr.fromString(await this.rollup.read.archiveAt([0n]));
|
|
380
428
|
}
|
|
381
429
|
|
|
430
|
+
@memoize
|
|
431
|
+
async getVkTreeRoot(): Promise<Fr> {
|
|
432
|
+
const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
|
|
433
|
+
const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
|
|
434
|
+
return Fr.fromString(value ?? '0x0');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
@memoize
|
|
438
|
+
async getProtocolContractsHash(): Promise<Fr> {
|
|
439
|
+
const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
|
|
440
|
+
const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
|
|
441
|
+
return Fr.fromString(value ?? '0x0');
|
|
442
|
+
}
|
|
443
|
+
|
|
382
444
|
/**
|
|
383
445
|
* Returns rollup constants used for epoch queries.
|
|
384
446
|
* Return type is `L1RollupConstants` which is defined in stdlib,
|
|
@@ -392,16 +454,25 @@ export class RollupContract {
|
|
|
392
454
|
epochDuration: number;
|
|
393
455
|
proofSubmissionEpochs: number;
|
|
394
456
|
targetCommitteeSize: number;
|
|
457
|
+
rollupManaLimit: number;
|
|
395
458
|
}> {
|
|
396
|
-
const [
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
459
|
+
const [
|
|
460
|
+
l1StartBlock,
|
|
461
|
+
l1GenesisTime,
|
|
462
|
+
slotDuration,
|
|
463
|
+
epochDuration,
|
|
464
|
+
proofSubmissionEpochs,
|
|
465
|
+
targetCommitteeSize,
|
|
466
|
+
rollupManaLimit,
|
|
467
|
+
] = await Promise.all([
|
|
468
|
+
this.getL1StartBlock(),
|
|
469
|
+
this.getL1GenesisTime(),
|
|
470
|
+
this.getSlotDuration(),
|
|
471
|
+
this.getEpochDuration(),
|
|
472
|
+
this.getProofSubmissionEpochs(),
|
|
473
|
+
this.getTargetCommitteeSize(),
|
|
474
|
+
this.getManaLimit(),
|
|
475
|
+
]);
|
|
405
476
|
return {
|
|
406
477
|
l1StartBlock,
|
|
407
478
|
l1GenesisTime,
|
|
@@ -409,6 +480,7 @@ export class RollupContract {
|
|
|
409
480
|
epochDuration: Number(epochDuration),
|
|
410
481
|
proofSubmissionEpochs: Number(proofSubmissionEpochs),
|
|
411
482
|
targetCommitteeSize,
|
|
483
|
+
rollupManaLimit: Number(rollupManaLimit),
|
|
412
484
|
};
|
|
413
485
|
}
|
|
414
486
|
|
|
@@ -463,7 +535,11 @@ export class RollupContract {
|
|
|
463
535
|
|
|
464
536
|
const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
|
|
465
537
|
return isOpen;
|
|
466
|
-
} 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
|
+
});
|
|
467
543
|
return false;
|
|
468
544
|
}
|
|
469
545
|
}
|
|
@@ -483,8 +559,9 @@ export class RollupContract {
|
|
|
483
559
|
return EthAddress.fromString(await this.rollup.read.owner());
|
|
484
560
|
}
|
|
485
561
|
|
|
486
|
-
async getActiveAttesterCount(): Promise<number> {
|
|
487
|
-
|
|
562
|
+
async getActiveAttesterCount(options?: { blockNumber?: bigint }): Promise<number> {
|
|
563
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
564
|
+
return Number(await this.rollup.read.getActiveAttesterCount(options));
|
|
488
565
|
}
|
|
489
566
|
|
|
490
567
|
public async getSlashingProposerAddress() {
|
|
@@ -499,26 +576,41 @@ export class RollupContract {
|
|
|
499
576
|
return this.rollup.read.getCheckpointReward();
|
|
500
577
|
}
|
|
501
578
|
|
|
502
|
-
async getCheckpointNumber(): Promise<CheckpointNumber> {
|
|
503
|
-
|
|
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));
|
|
504
582
|
}
|
|
505
583
|
|
|
506
|
-
async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
|
|
507
|
-
|
|
584
|
+
async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
585
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
586
|
+
return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
|
|
508
587
|
}
|
|
509
588
|
|
|
510
|
-
async getSlotNumber(): Promise<SlotNumber> {
|
|
511
|
-
|
|
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));
|
|
512
592
|
}
|
|
513
593
|
|
|
514
|
-
async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
|
|
515
|
-
|
|
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);
|
|
516
597
|
return {
|
|
517
598
|
baseFee: result.baseFee,
|
|
518
599
|
blobFee: result.blobFee,
|
|
519
600
|
};
|
|
520
601
|
}
|
|
521
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
|
+
|
|
522
614
|
getEthPerFeeAsset(): Promise<bigint> {
|
|
523
615
|
return this.rollup.read.getEthPerFeeAsset();
|
|
524
616
|
}
|
|
@@ -532,7 +624,7 @@ export class RollupContract {
|
|
|
532
624
|
args: [timestamp],
|
|
533
625
|
})
|
|
534
626
|
.catch(e => {
|
|
535
|
-
if (e
|
|
627
|
+
if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
|
|
536
628
|
return { result: undefined };
|
|
537
629
|
}
|
|
538
630
|
throw e;
|
|
@@ -562,7 +654,7 @@ export class RollupContract {
|
|
|
562
654
|
args: [],
|
|
563
655
|
})
|
|
564
656
|
.catch(e => {
|
|
565
|
-
if (e
|
|
657
|
+
if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
|
|
566
658
|
return { result: undefined };
|
|
567
659
|
}
|
|
568
660
|
throw e;
|
|
@@ -593,8 +685,9 @@ export class RollupContract {
|
|
|
593
685
|
return EthAddress.fromString(result);
|
|
594
686
|
}
|
|
595
687
|
|
|
596
|
-
async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
|
|
597
|
-
|
|
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);
|
|
598
691
|
return {
|
|
599
692
|
archive: Fr.fromString(result.archive),
|
|
600
693
|
headerHash: Buffer32.fromString(result.headerHash),
|
|
@@ -627,6 +720,30 @@ export class RollupContract {
|
|
|
627
720
|
);
|
|
628
721
|
}
|
|
629
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
|
+
|
|
630
747
|
async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
|
|
631
748
|
const { pending, proven } = await this.rollup.read.getTips();
|
|
632
749
|
return {
|
|
@@ -701,7 +818,7 @@ export class RollupContract {
|
|
|
701
818
|
}
|
|
702
819
|
|
|
703
820
|
async getEpochProofPublicInputs(
|
|
704
|
-
args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly
|
|
821
|
+
args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly ViemHeader[], `0x${string}`],
|
|
705
822
|
): Promise<Fr[]> {
|
|
706
823
|
const result = await this.rollup.read.getEpochProofPublicInputs(args);
|
|
707
824
|
return result.map(Fr.fromString);
|
|
@@ -745,14 +862,13 @@ export class RollupContract {
|
|
|
745
862
|
* timestamp of the next L1 block
|
|
746
863
|
* @throws otherwise
|
|
747
864
|
*/
|
|
748
|
-
public async
|
|
865
|
+
public async canProposeAt(
|
|
749
866
|
archive: Buffer,
|
|
750
867
|
account: `0x${string}` | Account,
|
|
751
|
-
|
|
752
|
-
|
|
868
|
+
timestamp: bigint,
|
|
869
|
+
stateOverride: StateOverride = [],
|
|
753
870
|
): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
|
|
754
|
-
const
|
|
755
|
-
const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
|
|
871
|
+
const timeOfNextL1Slot = timestamp;
|
|
756
872
|
const who = typeof account === 'string' ? account : account.address;
|
|
757
873
|
|
|
758
874
|
try {
|
|
@@ -764,7 +880,7 @@ export class RollupContract {
|
|
|
764
880
|
functionName: 'canProposeAtTime',
|
|
765
881
|
args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
|
|
766
882
|
account,
|
|
767
|
-
stateOverride
|
|
883
|
+
stateOverride,
|
|
768
884
|
});
|
|
769
885
|
|
|
770
886
|
return {
|
|
@@ -778,20 +894,32 @@ export class RollupContract {
|
|
|
778
894
|
}
|
|
779
895
|
|
|
780
896
|
/**
|
|
781
|
-
* Returns a state override that sets the pending
|
|
782
|
-
*
|
|
783
|
-
*
|
|
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`.
|
|
784
904
|
*/
|
|
785
|
-
public async
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
905
|
+
public async makeChainTipsOverride(override: {
|
|
906
|
+
pending?: CheckpointNumber;
|
|
907
|
+
proven?: CheckpointNumber;
|
|
908
|
+
}): Promise<StateOverride> {
|
|
909
|
+
if (override.pending === undefined && override.proven === undefined) {
|
|
789
910
|
return [];
|
|
790
911
|
}
|
|
791
912
|
const slot = RollupContract.stfStorageSlot;
|
|
792
913
|
const currentValue = await this.client.getStorageAt({ address: this.address, slot });
|
|
793
|
-
const
|
|
794
|
-
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;
|
|
795
923
|
return [
|
|
796
924
|
{
|
|
797
925
|
address: this.address,
|
|
@@ -800,6 +928,168 @@ export class RollupContract {
|
|
|
800
928
|
];
|
|
801
929
|
}
|
|
802
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
|
+
|
|
803
1093
|
/** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
|
|
804
1094
|
public buildInvalidateBadAttestationRequest(
|
|
805
1095
|
checkpointNumber: CheckpointNumber,
|
|
@@ -848,8 +1138,18 @@ export class RollupContract {
|
|
|
848
1138
|
return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
|
|
849
1139
|
}
|
|
850
1140
|
|
|
851
|
-
getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
|
|
852
|
-
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 });
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
|
|
1146
|
+
const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
|
|
1147
|
+
return {
|
|
1148
|
+
sequencerCost: result.sequencerCost,
|
|
1149
|
+
proverCost: result.proverCost,
|
|
1150
|
+
congestionCost: result.congestionCost,
|
|
1151
|
+
congestionMultiplier: result.congestionMultiplier,
|
|
1152
|
+
};
|
|
853
1153
|
}
|
|
854
1154
|
|
|
855
1155
|
async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
|
|
@@ -895,15 +1195,21 @@ export class RollupContract {
|
|
|
895
1195
|
return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
|
|
896
1196
|
}
|
|
897
1197
|
|
|
898
|
-
async getAttesters(): Promise<EthAddress[]> {
|
|
899
|
-
|
|
1198
|
+
async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
|
|
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 });
|
|
900
1206
|
const gse = new GSEContract(this.client, await this.getGSE());
|
|
901
|
-
const ts = (await this.client.getBlock()).timestamp;
|
|
902
|
-
|
|
903
1207
|
const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
|
|
904
1208
|
const chunks = chunk(indices, 1000);
|
|
905
1209
|
|
|
906
|
-
const results = await Promise.all(
|
|
1210
|
+
const results = await Promise.all(
|
|
1211
|
+
chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk, { blockNumber })),
|
|
1212
|
+
);
|
|
907
1213
|
return results.flat().map(addr => EthAddress.fromString(addr));
|
|
908
1214
|
}
|
|
909
1215
|
|
|
@@ -986,39 +1292,57 @@ export class RollupContract {
|
|
|
986
1292
|
});
|
|
987
1293
|
}
|
|
988
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
|
+
*/
|
|
989
1300
|
public listenToSlasherChanged(
|
|
990
1301
|
callback: (args: { oldSlasher: `0x${string}`; newSlasher: `0x${string}` }) => unknown,
|
|
1302
|
+
options?: WatchContractEventOptions,
|
|
991
1303
|
): WatchContractEventReturnType {
|
|
992
|
-
return
|
|
993
|
-
|
|
1304
|
+
return watchContractEvent(
|
|
1305
|
+
this.client,
|
|
1306
|
+
this.logger,
|
|
994
1307
|
{
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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 });
|
|
1001
1315
|
}
|
|
1002
1316
|
},
|
|
1003
1317
|
},
|
|
1318
|
+
{ pollingIntervalMs: SLASHER_UPDATED_POLLING_INTERVAL_MS, ...options },
|
|
1004
1319
|
);
|
|
1005
1320
|
}
|
|
1006
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
|
+
*/
|
|
1007
1327
|
public listenToCheckpointInvalidated(
|
|
1008
|
-
callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
|
|
1328
|
+
callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
|
|
1329
|
+
options?: WatchContractEventOptions,
|
|
1009
1330
|
): WatchContractEventReturnType {
|
|
1010
|
-
return
|
|
1011
|
-
|
|
1331
|
+
return watchContractEvent(
|
|
1332
|
+
this.client,
|
|
1333
|
+
this.logger,
|
|
1012
1334
|
{
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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 });
|
|
1019
1342
|
}
|
|
1020
1343
|
},
|
|
1021
1344
|
},
|
|
1345
|
+
options,
|
|
1022
1346
|
);
|
|
1023
1347
|
}
|
|
1024
1348
|
|
|
@@ -1030,23 +1354,39 @@ export class RollupContract {
|
|
|
1030
1354
|
}));
|
|
1031
1355
|
}
|
|
1032
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
|
+
*/
|
|
1033
1361
|
public listenToSlash(
|
|
1034
1362
|
callback: (args: { amount: bigint; attester: EthAddress }) => unknown,
|
|
1363
|
+
options?: WatchContractEventOptions,
|
|
1035
1364
|
): WatchContractEventReturnType {
|
|
1036
|
-
return
|
|
1037
|
-
|
|
1365
|
+
return watchContractEvent(
|
|
1366
|
+
this.client,
|
|
1367
|
+
this.logger,
|
|
1038
1368
|
{
|
|
1369
|
+
address: this.address,
|
|
1370
|
+
abi: RollupAbi,
|
|
1371
|
+
eventName: 'Slashed',
|
|
1039
1372
|
strict: true,
|
|
1040
|
-
|
|
1041
|
-
for (const log of logs) {
|
|
1042
|
-
const args = log.args;
|
|
1043
|
-
callback({ amount: args.amount!, attester: EthAddress.fromString(args.attester!) });
|
|
1044
|
-
}
|
|
1045
|
-
},
|
|
1373
|
+
onLog: log => callback({ amount: log.args.amount, attester: EthAddress.fromString(log.args.attester) }),
|
|
1046
1374
|
},
|
|
1375
|
+
options,
|
|
1047
1376
|
);
|
|
1048
1377
|
}
|
|
1049
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
|
+
|
|
1050
1390
|
/** Fetches CheckpointProposed events within the given block range. */
|
|
1051
1391
|
async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
|
|
1052
1392
|
const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
|
|
@@ -1060,9 +1400,67 @@ export class RollupContract {
|
|
|
1060
1400
|
checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
|
|
1061
1401
|
archive: Fr.fromString(log.args.archive!),
|
|
1062
1402
|
versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
|
|
1063
|
-
attestationsHash:
|
|
1064
|
-
|
|
1403
|
+
attestationsHash: (() => {
|
|
1404
|
+
if (!log.args.attestationsHash) {
|
|
1405
|
+
throw new Error(
|
|
1406
|
+
`CheckpointProposed event missing attestationsHash for checkpoint ${log.args.checkpointNumber}`,
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
return Buffer32.fromString(log.args.attestationsHash);
|
|
1410
|
+
})(),
|
|
1411
|
+
payloadDigest: (() => {
|
|
1412
|
+
if (!log.args.payloadDigest) {
|
|
1413
|
+
throw new Error(
|
|
1414
|
+
`CheckpointProposed event missing payloadDigest for checkpoint ${log.args.checkpointNumber}`,
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
return Buffer32.fromString(log.args.payloadDigest);
|
|
1418
|
+
})(),
|
|
1065
1419
|
},
|
|
1066
1420
|
}));
|
|
1067
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
|
+
}
|
|
1068
1466
|
}
|