@aztec/ethereum 3.0.0-nightly.20250904 → 3.0.0-nightly.20250906

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/src/config.ts CHANGED
@@ -118,10 +118,15 @@ const TestnetGovernanceConfiguration = {
118
118
  };
119
119
 
120
120
  export const getGovernanceConfiguration = (networkName: NetworkNames) => {
121
- if (networkName === 'alpha-testnet' || networkName === 'testnet') {
122
- return TestnetGovernanceConfiguration;
121
+ switch (networkName) {
122
+ case 'alpha-testnet':
123
+ case 'testnet':
124
+ return TestnetGovernanceConfiguration;
125
+ case 'local':
126
+ return LocalGovernanceConfiguration;
127
+ default:
128
+ throw new Error('Unrecognized network name: ' + networkName);
123
129
  }
124
- return LocalGovernanceConfiguration;
125
130
  };
126
131
 
127
132
  const TestnetGSEConfiguration = {
@@ -135,10 +140,15 @@ const LocalGSEConfiguration = {
135
140
  };
136
141
 
137
142
  export const getGSEConfiguration = (networkName: NetworkNames) => {
138
- if (networkName === 'alpha-testnet' || networkName === 'testnet') {
139
- return TestnetGSEConfiguration;
143
+ switch (networkName) {
144
+ case 'alpha-testnet':
145
+ case 'testnet':
146
+ return TestnetGSEConfiguration;
147
+ case 'local':
148
+ return LocalGSEConfiguration;
149
+ default:
150
+ throw new Error('Unrecognized network name: ' + networkName);
140
151
  }
141
- return LocalGSEConfiguration;
142
152
  };
143
153
 
144
154
  // Making a default config here as we are only using it thought the deployment
@@ -159,10 +169,15 @@ const TestnetRewardConfig = {
159
169
  };
160
170
 
161
171
  export const getRewardConfig = (networkName: NetworkNames) => {
162
- if (networkName === 'alpha-testnet' || networkName === 'testnet') {
163
- return TestnetRewardConfig;
172
+ switch (networkName) {
173
+ case 'alpha-testnet':
174
+ case 'testnet':
175
+ return TestnetRewardConfig;
176
+ case 'local':
177
+ return LocalRewardConfig;
178
+ default:
179
+ throw new Error('Unrecognized network name: ' + networkName);
164
180
  }
165
- return LocalRewardConfig;
166
181
  };
167
182
 
168
183
  const LocalRewardBoostConfig = {
@@ -182,10 +197,15 @@ const TestnetRewardBoostConfig = {
182
197
  };
183
198
 
184
199
  export const getRewardBoostConfig = (networkName: NetworkNames) => {
185
- if (networkName === 'alpha-testnet' || networkName === 'testnet') {
186
- return TestnetRewardBoostConfig;
200
+ switch (networkName) {
201
+ case 'alpha-testnet':
202
+ case 'testnet':
203
+ return TestnetRewardBoostConfig;
204
+ case 'local':
205
+ return LocalRewardBoostConfig;
206
+ default:
207
+ throw new Error('Unrecognized network name: ' + networkName);
187
208
  }
188
- return LocalRewardBoostConfig;
189
209
  };
190
210
 
191
211
  // Similar to the above, no need for environment variables for this.
@@ -206,10 +226,15 @@ const TestnetEntryQueueConfig = {
206
226
  };
207
227
 
208
228
  export const getEntryQueueConfig = (networkName: NetworkNames) => {
209
- if (networkName === 'alpha-testnet' || networkName === 'testnet') {
210
- return TestnetEntryQueueConfig;
229
+ switch (networkName) {
230
+ case 'alpha-testnet':
231
+ case 'testnet':
232
+ return TestnetEntryQueueConfig;
233
+ case 'local':
234
+ return LocalEntryQueueConfig;
235
+ default:
236
+ throw new Error('Unrecognized network name: ' + networkName);
211
237
  }
212
- return LocalEntryQueueConfig;
213
238
  };
214
239
 
215
240
  export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> = {
@@ -264,6 +264,35 @@ export class RollupContract {
264
264
  return block.archive;
265
265
  }
266
266
 
267
+ /**
268
+ * Returns rollup constants used for epoch queries.
269
+ * Return type is `L1RollupConstants` which is defined in stdlib,
270
+ * so we cant reference it until we move this contract to that package.
271
+ */
272
+ @memoize
273
+ public async getRollupConstants(): Promise<{
274
+ l1StartBlock: bigint;
275
+ l1GenesisTime: bigint;
276
+ slotDuration: number;
277
+ epochDuration: number;
278
+ proofSubmissionEpochs: number;
279
+ }> {
280
+ const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
281
+ this.getL1StartBlock(),
282
+ this.getL1GenesisTime(),
283
+ this.getSlotDuration(),
284
+ this.getEpochDuration(),
285
+ this.getProofSubmissionEpochs(),
286
+ ]);
287
+ return {
288
+ l1StartBlock,
289
+ l1GenesisTime,
290
+ slotDuration: Number(slotDuration),
291
+ epochDuration: Number(epochDuration),
292
+ proofSubmissionEpochs: Number(proofSubmissionEpochs),
293
+ };
294
+ }
295
+
267
296
  getSlasher() {
268
297
  return this.rollup.read.getSlasher();
269
298
  }
@@ -2,9 +2,9 @@ import { type L1TxRequest, type ViemClient, tryExtractEvent } from '@aztec/ether
2
2
  import { Buffer32 } from '@aztec/foundation/buffer';
3
3
  import { EthAddress } from '@aztec/foundation/eth-address';
4
4
  import { Signature } from '@aztec/foundation/eth-signature';
5
+ import { hexToBuffer } from '@aztec/foundation/string';
5
6
  import { TallySlashingProposerAbi } from '@aztec/l1-artifacts/TallySlashingProposerAbi';
6
7
 
7
- import EventEmitter from 'events';
8
8
  import {
9
9
  type GetContractReturnType,
10
10
  type Hex,
@@ -18,7 +18,7 @@ import {
18
18
  * Wrapper around the TallySlashingProposer contract that provides
19
19
  * a TypeScript interface for interacting with the consensus-based slashing system.
20
20
  */
21
- export class TallySlashingProposerContract extends EventEmitter {
21
+ export class TallySlashingProposerContract {
22
22
  private readonly contract: GetContractReturnType<typeof TallySlashingProposerAbi, ViemClient>;
23
23
 
24
24
  public readonly type = 'tally' as const;
@@ -27,7 +27,6 @@ export class TallySlashingProposerContract extends EventEmitter {
27
27
  public readonly client: ViemClient,
28
28
  address: Hex | EthAddress,
29
29
  ) {
30
- super();
31
30
  this.contract = getContract({
32
31
  address: typeof address === 'string' ? address : address.toString(),
33
32
  abi: TallySlashingProposerAbi,
@@ -221,6 +220,21 @@ export class TallySlashingProposerContract extends EventEmitter {
221
220
  };
222
221
  }
223
222
 
223
+ /** Returns the last vote emitted for a given round */
224
+ public async getLastVote(round: bigint) {
225
+ const { voteCount } = await this.getRound(round);
226
+ const validators = (await this.contract.simulate.getSlashTargetCommittees([round])).result.flat();
227
+ const vote = await this.contract.read.getVotes([round, voteCount - 1n]);
228
+ const decoded = decodeSlashConsensusVotes(hexToBuffer(vote));
229
+ const slashAmounts = await this.getSlashingAmounts();
230
+ return decoded
231
+ .map((units, i) => ({
232
+ validator: EthAddress.fromString(validators[i]),
233
+ slashAmount: slashAmounts[units - 1] ?? 0n,
234
+ }))
235
+ .filter(v => v.slashAmount > 0n);
236
+ }
237
+
224
238
  /**
225
239
  * Listen for VoteCast events
226
240
  * @param callback - Callback function to handle vote cast events
@@ -265,3 +279,28 @@ export class TallySlashingProposerContract extends EventEmitter {
265
279
  );
266
280
  }
267
281
  }
282
+
283
+ /**
284
+ * Decodes a Buffer containing slash votes back into an array of numbers.
285
+ * Each vote is represented as a 2-bit value (0, 1, 2, or 3) representing slashing units.
286
+ * @dev This should live in stdlib next to encodeSlashConsensusVotes but is here since we
287
+ * do not have a dependency to stdlib from the ethereum package. We need a larger refactor to fix this.
288
+ * @param buffer - The Buffer containing encoded slash votes
289
+ * @returns An array of numbers representing the slash votes
290
+ */
291
+ export function decodeSlashConsensusVotes(buffer: Buffer): number[] {
292
+ const votes: number[] = [];
293
+ for (let i = 0; i < buffer.length; i++) {
294
+ const voteByte = buffer.readUInt8(i);
295
+ // Decode votes from Solidity's bit order (LSB to MSB)
296
+ // Bits 0-1: validator at index i*4
297
+ // Bits 2-3: validator at index i*4+1
298
+ // Bits 4-5: validator at index i*4+2
299
+ // Bits 6-7: validator at index i*4+3
300
+ votes.push((voteByte >> 0) & 0x03);
301
+ votes.push((voteByte >> 2) & 0x03);
302
+ votes.push((voteByte >> 4) & 0x03);
303
+ votes.push((voteByte >> 6) & 0x03);
304
+ }
305
+ return votes;
306
+ }
@@ -58,73 +58,62 @@ const parseEnv = (val: string) => EthAddress.fromString(val);
58
58
  export const l1ContractAddressesMapping: ConfigMappingsType<
59
59
  Omit<L1ContractAddresses, 'gseAddress' | 'zkPassportVerifierAddress'>
60
60
  > = {
61
- rollupAddress: {
62
- env: 'ROLLUP_CONTRACT_ADDRESS',
63
- description: 'The deployed L1 rollup contract address.',
64
- parseEnv,
65
- },
66
61
  registryAddress: {
67
62
  env: 'REGISTRY_CONTRACT_ADDRESS',
68
63
  description: 'The deployed L1 registry contract address.',
69
64
  parseEnv,
70
65
  },
66
+ slashFactoryAddress: {
67
+ env: 'SLASH_FACTORY_CONTRACT_ADDRESS',
68
+ description: 'The deployed L1 slashFactory contract address',
69
+ parseEnv,
70
+ },
71
+ feeAssetHandlerAddress: {
72
+ env: 'FEE_ASSET_HANDLER_CONTRACT_ADDRESS',
73
+ description: 'The deployed L1 feeAssetHandler contract address',
74
+ parseEnv,
75
+ },
76
+ rollupAddress: {
77
+ description: 'The deployed L1 rollup contract address.',
78
+ parseEnv,
79
+ },
71
80
  inboxAddress: {
72
- env: 'INBOX_CONTRACT_ADDRESS',
73
81
  description: 'The deployed L1 inbox contract address.',
74
82
  parseEnv,
75
83
  },
76
84
  outboxAddress: {
77
- env: 'OUTBOX_CONTRACT_ADDRESS',
78
85
  description: 'The deployed L1 outbox contract address.',
79
86
  parseEnv,
80
87
  },
81
88
  feeJuiceAddress: {
82
- env: 'FEE_JUICE_CONTRACT_ADDRESS',
83
89
  description: 'The deployed L1 Fee Juice contract address.',
84
90
  parseEnv,
85
91
  },
86
92
  stakingAssetAddress: {
87
- env: 'STAKING_ASSET_CONTRACT_ADDRESS',
88
93
  description: 'The deployed L1 staking asset contract address.',
89
94
  parseEnv,
90
95
  },
91
96
  feeJuicePortalAddress: {
92
- env: 'FEE_JUICE_PORTAL_CONTRACT_ADDRESS',
93
97
  description: 'The deployed L1 Fee Juice portal contract address.',
94
98
  parseEnv,
95
99
  },
96
100
  coinIssuerAddress: {
97
- env: 'COIN_ISSUER_CONTRACT_ADDRESS',
98
101
  description: 'The deployed L1 coinIssuer contract address',
99
102
  parseEnv,
100
103
  },
101
104
  rewardDistributorAddress: {
102
- env: 'REWARD_DISTRIBUTOR_CONTRACT_ADDRESS',
103
105
  description: 'The deployed L1 rewardDistributor contract address',
104
106
  parseEnv,
105
107
  },
106
108
  governanceProposerAddress: {
107
- env: 'GOVERNANCE_PROPOSER_CONTRACT_ADDRESS',
108
109
  description: 'The deployed L1 governanceProposer contract address',
109
110
  parseEnv,
110
111
  },
111
112
  governanceAddress: {
112
- env: 'GOVERNANCE_CONTRACT_ADDRESS',
113
113
  description: 'The deployed L1 governance contract address',
114
114
  parseEnv,
115
115
  },
116
- slashFactoryAddress: {
117
- env: 'SLASH_FACTORY_CONTRACT_ADDRESS',
118
- description: 'The deployed L1 slashFactory contract address',
119
- parseEnv,
120
- },
121
- feeAssetHandlerAddress: {
122
- env: 'FEE_ASSET_HANDLER_CONTRACT_ADDRESS',
123
- description: 'The deployed L1 feeAssetHandler contract address',
124
- parseEnv,
125
- },
126
116
  stakingAssetHandlerAddress: {
127
- env: 'STAKING_ASSET_HANDLER_CONTRACT_ADDRESS',
128
117
  description: 'The deployed L1 stakingAssetHandler contract address',
129
118
  parseEnv,
130
119
  },
package/src/l1_reader.ts CHANGED
@@ -15,20 +15,20 @@ export interface L1ReaderConfig {
15
15
  }
16
16
 
17
17
  export const l1ReaderConfigMappings: ConfigMappingsType<L1ReaderConfig> = {
18
- l1RpcUrls: {
19
- env: 'ETHEREUM_HOSTS',
20
- description: 'The RPC Url of the ethereum host.',
21
- parseEnv: (val: string) => val.split(',').map(url => url.trim()),
18
+ l1Contracts: {
19
+ description: 'The deployed L1 contract addresses',
20
+ nested: l1ContractAddressesMapping,
22
21
  },
23
22
  l1ChainId: {
24
23
  env: 'L1_CHAIN_ID',
25
24
  parseEnv: (val: string) => +val,
26
- defaultValue: 31337,
27
25
  description: 'The chain ID of the ethereum host.',
28
26
  },
29
- l1Contracts: {
30
- description: 'The deployed L1 contract addresses',
31
- nested: l1ContractAddressesMapping,
27
+ l1RpcUrls: {
28
+ env: 'ETHEREUM_HOSTS',
29
+ description: 'The RPC Url of the ethereum host.',
30
+ parseEnv: (val: string) => val.split(',').map(url => url.trim()),
31
+ defaultValue: [],
32
32
  },
33
33
  viemPollingIntervalMS: {
34
34
  env: 'L1_READER_VIEM_POLLING_INTERVAL_MS',
@@ -14,6 +14,7 @@ export type ChainMonitorEventMap = {
14
14
  'l2-block-proven': [{ l2ProvenBlockNumber: number; l1BlockNumber: number; timestamp: bigint }];
15
15
  'l2-messages': [{ totalL2Messages: number; l1BlockNumber: number }];
16
16
  'l2-epoch': [{ l2EpochNumber: number; timestamp: bigint; committee: EthAddress[] | undefined }];
17
+ 'l2-slot': [{ l2SlotNumber: number; timestamp: bigint }];
17
18
  };
18
19
 
19
20
  /** Utility class that polls the chain on quick intervals and logs new L1 blocks, L2 blocks, and L2 proofs. */
@@ -38,6 +39,8 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
38
39
  public totalL2Messages: number = 0;
39
40
  /** Current L2 epoch number */
40
41
  public l2EpochNumber!: bigint;
42
+ /** Current L2 slot number */
43
+ public l2SlotNumber!: bigint;
41
44
 
42
45
  constructor(
43
46
  private readonly rollup: RollupContract,
@@ -143,7 +146,12 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
143
146
  this.l2EpochNumber = l2Epoch;
144
147
  committee = (await this.rollup.getCurrentEpochCommittee())?.map(addr => EthAddress.fromString(addr));
145
148
  this.emit('l2-epoch', { l2EpochNumber: Number(l2Epoch), timestamp, committee });
146
- msg += ` starting new epoch ${this.l2EpochNumber} with committee ${committee?.join(', ') ?? 'undefined'}`;
149
+ msg += ` starting new epoch ${this.l2EpochNumber} `;
150
+ }
151
+
152
+ if (l2SlotNumber !== this.l2SlotNumber) {
153
+ this.l2SlotNumber = l2SlotNumber;
154
+ this.emit('l2-slot', { l2SlotNumber: Number(l2SlotNumber), timestamp });
147
155
  }
148
156
 
149
157
  this.logger.info(msg, {
@@ -160,4 +168,20 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
160
168
 
161
169
  return this;
162
170
  }
171
+
172
+ public waitUntilL2Slot(slot: number | bigint): Promise<void> {
173
+ const targetSlot = typeof slot === 'bigint' ? slot.valueOf() : slot;
174
+ if (this.l2SlotNumber >= targetSlot) {
175
+ return Promise.resolve();
176
+ }
177
+ return new Promise(resolve => {
178
+ const listener = (data: { l2SlotNumber: number; timestamp: bigint }) => {
179
+ if (data.l2SlotNumber >= targetSlot) {
180
+ this.off('l2-slot', listener);
181
+ resolve();
182
+ }
183
+ };
184
+ this.on('l2-slot', listener);
185
+ });
186
+ }
163
187
  }