@aztec/ethereum 0.0.1-commit.e0f15ab9b → 0.0.1-commit.e304674f1

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.
Files changed (47) hide show
  1. package/dest/config.d.ts +6 -6
  2. package/dest/config.d.ts.map +1 -1
  3. package/dest/config.js +5 -9
  4. package/dest/contracts/inbox.d.ts +3 -3
  5. package/dest/contracts/inbox.d.ts.map +1 -1
  6. package/dest/contracts/inbox.js +5 -6
  7. package/dest/contracts/index.d.ts +2 -3
  8. package/dest/contracts/index.d.ts.map +1 -1
  9. package/dest/contracts/index.js +1 -2
  10. package/dest/contracts/rollup.d.ts +28 -11
  11. package/dest/contracts/rollup.d.ts.map +1 -1
  12. package/dest/contracts/rollup.js +152 -41
  13. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +3 -4
  14. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  15. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +13 -15
  16. package/dest/deploy_aztec_l1_contracts.d.ts +3 -6
  17. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  18. package/dest/deploy_aztec_l1_contracts.js +2 -4
  19. package/dest/generated/l1-contracts-defaults.d.ts +1 -1
  20. package/dest/generated/l1-contracts-defaults.js +1 -1
  21. package/dest/l1_artifacts.d.ts +8176 -15179
  22. package/dest/l1_artifacts.d.ts.map +1 -1
  23. package/dest/l1_artifacts.js +9 -24
  24. package/dest/l1_contract_addresses.d.ts +1 -5
  25. package/dest/l1_contract_addresses.d.ts.map +1 -1
  26. package/dest/l1_contract_addresses.js +0 -6
  27. package/dest/l1_tx_utils/l1_tx_utils.d.ts +3 -1
  28. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  29. package/dest/l1_tx_utils/l1_tx_utils.js +34 -26
  30. package/dest/queries.js +3 -3
  31. package/package.json +5 -5
  32. package/src/config.ts +9 -13
  33. package/src/contracts/inbox.ts +4 -4
  34. package/src/contracts/index.ts +1 -2
  35. package/src/contracts/rollup.ts +171 -35
  36. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +14 -16
  37. package/src/deploy_aztec_l1_contracts.ts +1 -5
  38. package/src/generated/l1-contracts-defaults.ts +1 -1
  39. package/src/l1_artifacts.ts +12 -35
  40. package/src/l1_contract_addresses.ts +0 -7
  41. package/src/l1_tx_utils/l1_tx_utils.ts +24 -14
  42. package/src/queries.ts +3 -3
  43. package/dest/contracts/empire_slashing_proposer.d.ts +0 -69
  44. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  45. package/dest/contracts/empire_slashing_proposer.js +0 -216
  46. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  47. package/src/contracts/empire_slashing_proposer.ts +0 -265
@@ -2,6 +2,7 @@ import { maxBigint } from '@aztec/foundation/bigint';
2
2
  import { merge, pick } from '@aztec/foundation/collection';
3
3
  import { InterruptError, TimeoutError } from '@aztec/foundation/error';
4
4
  import { createLogger } from '@aztec/foundation/log';
5
+ import { Semaphore } from '@aztec/foundation/queue';
5
6
  import { retryUntil } from '@aztec/foundation/retry';
6
7
  import { sleep } from '@aztec/foundation/sleep';
7
8
  import { DateProvider } from '@aztec/foundation/timer';
@@ -23,10 +24,11 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
23
24
  metrics;
24
25
  txs;
25
26
  /** Last nonce successfully sent to the chain. Used as a lower bound when a fallback RPC node returns a stale count. */ lastSentNonce;
27
+ /** Mutex to prevent concurrent sendTransaction calls from racing on the same nonce. */ sendMutex;
26
28
  /** Tx delayer for testing. Only set when enableDelayer config is true. */ delayer;
27
29
  /** KZG instance for blob operations. */ kzg;
28
30
  constructor(client, address, signer, logger = createLogger('ethereum:publisher'), dateProvider = new DateProvider(), config, debugMaxGasLimit = false, store, metrics, kzg, delayer){
29
- super(client, logger, dateProvider, config, debugMaxGasLimit), this.client = client, this.address = address, this.signer = signer, this.store = store, this.metrics = metrics, this.txs = [];
31
+ super(client, logger, dateProvider, config, debugMaxGasLimit), this.client = client, this.address = address, this.signer = signer, this.store = store, this.metrics = metrics, this.txs = [], this.sendMutex = new Semaphore(1);
30
32
  this.kzg = kzg;
31
33
  // Set up delayer: use provided one or create new
32
34
  if (config?.enableDelayer && config?.ethereumSlotDuration) {
@@ -171,34 +173,40 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
171
173
  if (gasConfig.txTimeoutAt && now > gasConfig.txTimeoutAt) {
172
174
  throw new TimeoutError(`Transaction timed out before sending (now ${now.toISOString()} > timeoutAt ${gasConfig.txTimeoutAt.toISOString()})`);
173
175
  }
174
- const chainNonce = await this.client.getTransactionCount({
175
- address: account,
176
- blockTag: 'pending'
177
- });
178
- // If a fallback RPC node returns a stale count (lower than what we last sent), use our
179
- // local lower bound to avoid sending a duplicate of an already-pending transaction.
180
- const nonce = this.lastSentNonce !== undefined && chainNonce <= this.lastSentNonce ? this.lastSentNonce + 1 : chainNonce;
181
- const baseState = {
182
- request,
183
- gasLimit,
184
- blobInputs,
185
- gasPrice,
186
- nonce
187
- };
188
- const txData = this.makeTxData(baseState, {
189
- isCancelTx: false
190
- });
191
- // Send the new tx
192
- const signedRequest = await this.prepareSignedTransaction(txData);
193
- const txHash = await this.client.sendRawTransaction({
194
- serializedTransaction: signedRequest
195
- });
196
- // Update after tx is sent successfully
197
- this.lastSentNonce = nonce;
176
+ let txHash;
177
+ let nonce;
178
+ let baseState;
179
+ await this.sendMutex.acquire();
180
+ try {
181
+ const chainNonce = await this.client.getTransactionCount({
182
+ address: account,
183
+ blockTag: 'pending'
184
+ });
185
+ // If a fallback RPC node returns a stale count (lower than what we last sent), use our
186
+ // local lower bound to avoid sending a duplicate of an already-pending transaction.
187
+ nonce = this.lastSentNonce !== undefined && chainNonce <= this.lastSentNonce ? this.lastSentNonce + 1 : chainNonce;
188
+ baseState = {
189
+ request,
190
+ gasLimit,
191
+ blobInputs,
192
+ gasPrice,
193
+ nonce
194
+ };
195
+ const txData = this.makeTxData(baseState, {
196
+ isCancelTx: false
197
+ });
198
+ const signedRequest = await this.prepareSignedTransaction(txData);
199
+ txHash = await this.client.sendRawTransaction({
200
+ serializedTransaction: signedRequest
201
+ });
202
+ this.lastSentNonce = nonce;
203
+ } finally{
204
+ this.sendMutex.release();
205
+ }
198
206
  // Create the new state for monitoring
199
207
  const l1TxState = {
200
208
  ...baseState,
201
- id: await this.store?.consumeNextStateId(account) ?? Math.max(...this.txs.map((tx)=>tx.id), 0),
209
+ id: await this.store?.consumeNextStateId(account) ?? Math.max(...this.txs.map((tx)=>tx.id), -1) + 1,
202
210
  txHashes: [
203
211
  txHash
204
212
  ],
package/dest/queries.js CHANGED
@@ -33,8 +33,8 @@ import { RollupContract } from './contracts/rollup.js';
33
33
  slasherProposer?.getRoundSize() ?? 0n,
34
34
  slasherProposer?.getLifetimeInRounds() ?? 0n,
35
35
  slasherProposer?.getExecutionDelayInRounds() ?? 0n,
36
- slasherProposer?.type === 'tally' ? slasherProposer.getSlashOffsetInRounds() : 0n,
37
- slasherProposer?.type === 'tally' ? slasherProposer.getSlashingAmounts() : [
36
+ slasherProposer?.getSlashOffsetInRounds() ?? 0n,
37
+ slasherProposer?.getSlashingAmounts() ?? [
38
38
  0n,
39
39
  0n,
40
40
  0n
@@ -74,7 +74,7 @@ import { RollupContract } from './contracts/rollup.js';
74
74
  rollupVersion: Number(rollupVersion),
75
75
  genesisArchiveTreeRoot: genesisArchiveTreeRoot.toString(),
76
76
  exitDelaySeconds: Number(exitDelay),
77
- slasherFlavor: slasherProposer?.type ?? 'tally',
77
+ slasherEnabled: !!slasherProposer,
78
78
  slashingOffsetInRounds: Number(slashingOffsetInRounds),
79
79
  slashAmountSmall: slashingAmounts[0],
80
80
  slashAmountMedium: slashingAmounts[1],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/ethereum",
3
- "version": "0.0.1-commit.e0f15ab9b",
3
+ "version": "0.0.1-commit.e304674f1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./account": "./dest/account.js",
@@ -50,10 +50,10 @@
50
50
  "../package.common.json"
51
51
  ],
52
52
  "dependencies": {
53
- "@aztec/blob-lib": "0.0.1-commit.e0f15ab9b",
54
- "@aztec/constants": "0.0.1-commit.e0f15ab9b",
55
- "@aztec/foundation": "0.0.1-commit.e0f15ab9b",
56
- "@aztec/l1-artifacts": "0.0.1-commit.e0f15ab9b",
53
+ "@aztec/blob-lib": "0.0.1-commit.e304674f1",
54
+ "@aztec/constants": "0.0.1-commit.e304674f1",
55
+ "@aztec/foundation": "0.0.1-commit.e304674f1",
56
+ "@aztec/l1-artifacts": "0.0.1-commit.e304674f1",
57
57
  "dotenv": "^16.0.3",
58
58
  "lodash.chunk": "^4.2.0",
59
59
  "lodash.pickby": "^4.5.0",
package/src/config.ts CHANGED
@@ -2,7 +2,6 @@ import {
2
2
  type ConfigMappingsType,
3
3
  bigintConfigHelper,
4
4
  booleanConfigHelper,
5
- enumConfigHelper,
6
5
  getConfigFromMappings,
7
6
  getDefaultConfig,
8
7
  numberConfigHelper,
@@ -60,13 +59,13 @@ export type L1ContractsConfig = {
60
59
  slashingOffsetInRounds: number;
61
60
  /** How long slashing can be disabled for in seconds when vetoer disables it */
62
61
  slashingDisableDuration: number;
63
- /** Type of slasher proposer */
64
- slasherFlavor: 'empire' | 'tally' | 'none';
65
- /** Minimum amount that can be slashed in tally slashing */
62
+ /** Whether to deploy a slasher proposer */
63
+ slasherEnabled: boolean;
64
+ /** Minimum amount that can be slashed */
66
65
  slashAmountSmall: bigint;
67
- /** Medium amount to slash in tally slashing */
66
+ /** Medium amount to slash */
68
67
  slashAmountMedium: bigint;
69
- /** Largest amount that can be slashed per round in tally slashing */
68
+ /** Largest amount that can be slashed per round */
70
69
  slashAmountLarge: bigint;
71
70
  /** Governance proposing quorum (defaults to roundSize/2 + 1) */
72
71
  governanceProposerQuorum?: number;
@@ -152,13 +151,10 @@ export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> =
152
151
  'How many slashing rounds back we slash (ie when slashing in round N, we slash for offenses committed during epochs of round N-offset)',
153
152
  ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_SLASHING_OFFSET_IN_ROUNDS),
154
153
  },
155
- slasherFlavor: {
156
- env: 'AZTEC_SLASHER_FLAVOR',
157
- description: 'Type of slasher proposer (empire, tally, or none)',
158
- ...enumConfigHelper(
159
- ['empire', 'tally', 'none'] as const,
160
- l1ContractsDefaultEnv.AZTEC_SLASHER_FLAVOR as 'empire' | 'tally' | 'none',
161
- ),
154
+ slasherEnabled: {
155
+ env: 'AZTEC_SLASHER_ENABLED',
156
+ description: 'Whether to deploy a slasher proposer',
157
+ ...booleanConfigHelper(true),
162
158
  },
163
159
  slashAmountSmall: {
164
160
  env: 'AZTEC_SLASH_AMOUNT_SMALL',
@@ -82,10 +82,10 @@ export class InboxContract {
82
82
  .map(log => this.mapMessageSentLog(log));
83
83
  }
84
84
 
85
- /** Fetches MessageSent events for a specific message hash within the given block range. */
86
- async getMessageSentEventByHash(hash: Hex, fromBlock: bigint, toBlock: bigint): Promise<MessageSentLog[]> {
87
- const logs = await this.inbox.getEvents.MessageSent({ hash }, { fromBlock, toBlock });
88
- return logs.map(log => this.mapMessageSentLog(log));
85
+ /** Fetches MessageSent events for a specific message hash at a specific block. */
86
+ async getMessageSentEventByHash(msgHash: Hex, l1BlockHash: Hex): Promise<MessageSentLog> {
87
+ const [log] = await this.inbox.getEvents.MessageSent({ hash: msgHash }, { blockHash: l1BlockHash });
88
+ return log && this.mapMessageSentLog(log);
89
89
  }
90
90
 
91
91
  private mapMessageSentLog(log: {
@@ -12,6 +12,5 @@ export * from './multicall.js';
12
12
  export * from './outbox.js';
13
13
  export * from './registry.js';
14
14
  export * from './rollup.js';
15
- export * from './empire_slashing_proposer.js';
16
- export * from './tally_slashing_proposer.js';
15
+ export * from './slashing_proposer.js';
17
16
  export * from './slasher_contract.js';
@@ -4,6 +4,7 @@ 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';
8
9
  import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
9
10
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
@@ -16,6 +17,7 @@ import {
16
17
  type Hex,
17
18
  type StateOverride,
18
19
  type WatchContractEventReturnType,
20
+ encodeAbiParameters,
19
21
  encodeFunctionData,
20
22
  getContract,
21
23
  hexToBigInt,
@@ -29,11 +31,10 @@ import type { L1ReaderConfig } from '../l1_reader.js';
29
31
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
30
32
  import type { ViemClient } from '../types.js';
31
33
  import { formatViemError } from '../utils.js';
32
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
33
34
  import { GSEContract } from './gse.js';
34
35
  import type { L1EventLog } from './log.js';
35
36
  import { SlasherContract } from './slasher_contract.js';
36
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
37
+ import { SlashingProposerContract } from './slashing_proposer.js';
37
38
  import { checkBlockTag } from './utils.js';
38
39
 
39
40
  export type ViemCommitteeAttestation = {
@@ -55,7 +56,6 @@ export type L1RollupContractAddresses = Pick<
55
56
  | 'feeJuiceAddress'
56
57
  | 'stakingAssetAddress'
57
58
  | 'rewardDistributorAddress'
58
- | 'slashFactoryAddress'
59
59
  | 'gseAddress'
60
60
  >;
61
61
 
@@ -85,12 +85,6 @@ export type ViemGasFees = {
85
85
  feePerL2Gas: bigint;
86
86
  };
87
87
 
88
- export enum SlashingProposerType {
89
- None = 0,
90
- Tally = 1,
91
- Empire = 2,
92
- }
93
-
94
88
  /**
95
89
  * Status of a validator/attester in the staking system.
96
90
  * Matches the Status enum in StakingLib.sol
@@ -212,6 +206,7 @@ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
212
206
 
213
207
  export class RollupContract {
214
208
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
209
+ private readonly logger = createLogger('ethereum:rollup');
215
210
 
216
211
  private static cachedStfStorageSlot: Hex | undefined;
217
212
  private cachedEscapeHatch?: {
@@ -267,34 +262,18 @@ export class RollupContract {
267
262
  return this.rollup;
268
263
  }
269
264
 
270
- public async getSlashingProposer(): Promise<
271
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
272
- > {
265
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
273
266
  const slasher = await this.getSlasherContract();
274
267
  if (!slasher) {
275
268
  return undefined;
276
269
  }
277
270
 
278
271
  const proposerAddress = await slasher.getProposer();
279
- const proposerAbi = [
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}`);
272
+ if (proposerAddress.isZero()) {
273
+ return undefined;
297
274
  }
275
+
276
+ return new SlashingProposerContract(this.client, proposerAddress);
298
277
  }
299
278
 
300
279
  @memoize
@@ -495,7 +474,11 @@ export class RollupContract {
495
474
 
496
475
  const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
497
476
  return isOpen;
498
- } catch {
477
+ } catch (err) {
478
+ this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
479
+ epoch: Number(epoch),
480
+ error: err,
481
+ });
499
482
  return false;
500
483
  }
501
484
  }
@@ -782,11 +765,19 @@ export class RollupContract {
782
765
  archive: Buffer,
783
766
  account: `0x${string}` | Account,
784
767
  timestamp: bigint,
785
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
768
+ opts: {
769
+ forcePendingCheckpointNumber?: CheckpointNumber;
770
+ forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
771
+ } = {},
786
772
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
787
773
  const timeOfNextL1Slot = timestamp;
788
774
  const who = typeof account === 'string' ? account : account.address;
789
775
 
776
+ const stateOverride = RollupContract.mergeStateOverrides(
777
+ await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
778
+ opts.forceArchive ? this.makeArchiveOverride(opts.forceArchive.checkpointNumber, opts.forceArchive.archive) : [],
779
+ );
780
+
790
781
  try {
791
782
  const {
792
783
  result: [slot, checkpointNumber],
@@ -796,7 +787,7 @@ export class RollupContract {
796
787
  functionName: 'canProposeAtTime',
797
788
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
798
789
  account,
799
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
790
+ stateOverride,
800
791
  });
801
792
 
802
793
  return {
@@ -832,6 +823,151 @@ export class RollupContract {
832
823
  ];
833
824
  }
834
825
 
826
+ /**
827
+ * Returns a state override that sets tempCheckpointLogs[checkpointNumber].feeHeader to the compressed fee header.
828
+ * Used when simulating a propose call where the parent checkpoint hasn't landed on L1 yet (pipelining).
829
+ */
830
+ public async makeFeeHeaderOverride(checkpointNumber: CheckpointNumber, feeHeader: FeeHeader): Promise<StateOverride> {
831
+ const { epochDuration, proofSubmissionEpochs } = await this.getRollupConstants();
832
+ const roundaboutSize = BigInt(epochDuration * (proofSubmissionEpochs + 1) + 1);
833
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
834
+
835
+ // tempCheckpointLogs is at offset 2 in RollupStore
836
+ const tempCheckpointLogsMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 2n;
837
+
838
+ // Solidity mapping slot: keccak256(abi.encode(key, baseSlot))
839
+ const structBaseSlot = hexToBigInt(
840
+ keccak256(
841
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsMappingBase]),
842
+ ),
843
+ );
844
+
845
+ // feeHeader is the 7th field (offset 6) in CompressedTempCheckpointLog
846
+ const feeHeaderSlot = structBaseSlot + 6n;
847
+ const compressed = RollupContract.compressFeeHeader(feeHeader);
848
+
849
+ return [
850
+ {
851
+ address: this.address,
852
+ stateDiff: [
853
+ {
854
+ slot: `0x${feeHeaderSlot.toString(16).padStart(64, '0')}`,
855
+ value: `0x${compressed.toString(16).padStart(64, '0')}`,
856
+ },
857
+ ],
858
+ },
859
+ ];
860
+ }
861
+
862
+ /**
863
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
864
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
865
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
866
+ */
867
+ public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
868
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
869
+ const archiveSlot = hexToBigInt(
870
+ keccak256(
871
+ encodeAbiParameters(
872
+ [{ type: 'uint256' }, { type: 'uint256' }],
873
+ [BigInt(checkpointNumber), archivesMappingBase],
874
+ ),
875
+ ),
876
+ );
877
+ return [
878
+ {
879
+ address: this.address,
880
+ stateDiff: [
881
+ {
882
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
883
+ value: archive.toString(),
884
+ },
885
+ ],
886
+ },
887
+ ];
888
+ }
889
+
890
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
891
+ public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
892
+ type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
893
+ const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
894
+ for (const override of overrides) {
895
+ for (const entry of override) {
896
+ const key = entry.address.toLowerCase();
897
+ const existing = byAddress.get(key);
898
+ if (existing) {
899
+ existing.stateDiff.push(...(entry.stateDiff ?? []));
900
+ if (entry.balance !== undefined) {
901
+ existing.balance = entry.balance;
902
+ }
903
+ } else {
904
+ byAddress.set(key, {
905
+ address: entry.address,
906
+ balance: entry.balance,
907
+ stateDiff: [...(entry.stateDiff ?? [])],
908
+ });
909
+ }
910
+ }
911
+ }
912
+ return [...byAddress.values()];
913
+ }
914
+
915
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
916
+ public static compressFeeHeader(feeHeader: FeeHeader): bigint {
917
+ const MASK_48_BITS = (1n << 48n) - 1n;
918
+ const MASK_64_BITS = (1n << 64n) - 1n;
919
+ const MASK_63_BITS = (1n << 63n) - 1n;
920
+
921
+ let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
922
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
923
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
924
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
925
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
926
+ value |= 1n << 255n; // preheat flag
927
+ return value;
928
+ }
929
+
930
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
931
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
932
+ public static computeChildFeeHeader(
933
+ parentFeeHeader: FeeHeader,
934
+ childManaUsed: bigint,
935
+ feeAssetPriceModifier: bigint,
936
+ manaTarget: bigint,
937
+ ): FeeHeader {
938
+ const MIN_ETH_PER_FEE_ASSET = 100n;
939
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
940
+
941
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
942
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
943
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
944
+
945
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
946
+ const parentPrice =
947
+ parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
948
+ let newPrice: bigint;
949
+ if (feeAssetPriceModifier >= 0n) {
950
+ newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
951
+ } else {
952
+ const absMod = -feeAssetPriceModifier;
953
+ newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
954
+ }
955
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
956
+ newPrice = MIN_ETH_PER_FEE_ASSET;
957
+ }
958
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
959
+ newPrice = MAX_ETH_PER_FEE_ASSET;
960
+ }
961
+
962
+ return {
963
+ excessMana,
964
+ manaUsed: childManaUsed,
965
+ ethPerFeeAsset: newPrice,
966
+ congestionCost: 0n,
967
+ proverCost: 0n,
968
+ };
969
+ }
970
+
835
971
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
836
972
  public buildInvalidateBadAttestationRequest(
837
973
  checkpointNumber: CheckpointNumber,
@@ -880,8 +1016,8 @@ export class RollupContract {
880
1016
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
881
1017
  }
882
1018
 
883
- getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
884
- return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
1019
+ getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
1020
+ return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
885
1021
  }
886
1022
 
887
1023
  async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
@@ -7,7 +7,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
7
7
  import { Signature } from '@aztec/foundation/eth-signature';
8
8
  import { hexToBuffer } from '@aztec/foundation/string';
9
9
  import { SlasherAbi } from '@aztec/l1-artifacts/SlasherAbi';
10
- import { TallySlashingProposerAbi } from '@aztec/l1-artifacts/TallySlashingProposerAbi';
10
+ import { SlashingProposerAbi } from '@aztec/l1-artifacts/SlashingProposerAbi';
11
11
 
12
12
  import {
13
13
  type GetContractReturnType,
@@ -19,13 +19,11 @@ import {
19
19
  } from 'viem';
20
20
 
21
21
  /**
22
- * Wrapper around the TallySlashingProposer contract that provides
22
+ * Wrapper around the SlashingProposer contract that provides
23
23
  * a TypeScript interface for interacting with the consensus-based slashing system.
24
24
  */
25
- export class TallySlashingProposerContract {
26
- private readonly contract: GetContractReturnType<typeof TallySlashingProposerAbi, ViemClient>;
27
-
28
- public readonly type = 'tally' as const;
25
+ export class SlashingProposerContract {
26
+ private readonly contract: GetContractReturnType<typeof SlashingProposerAbi, ViemClient>;
29
27
 
30
28
  constructor(
31
29
  public readonly client: ViemClient,
@@ -33,7 +31,7 @@ export class TallySlashingProposerContract {
33
31
  ) {
34
32
  this.contract = getContract({
35
33
  address: typeof address === 'string' ? address : address.toString(),
36
- abi: TallySlashingProposerAbi,
34
+ abi: SlashingProposerAbi,
37
35
  client,
38
36
  });
39
37
  }
@@ -136,12 +134,12 @@ export class TallySlashingProposerContract {
136
134
 
137
135
  /** Tries to extract a VoteCast event from the given logs. */
138
136
  public tryExtractVoteCastEvent(logs: Log[]) {
139
- return tryExtractEvent(logs, this.address.toString(), TallySlashingProposerAbi, 'VoteCast');
137
+ return tryExtractEvent(logs, this.address.toString(), SlashingProposerAbi, 'VoteCast');
140
138
  }
141
139
 
142
140
  /** Tries to extract a RoundExecuted event from the given logs. */
143
141
  public tryExtractRoundExecutedEvent(logs: Log[]) {
144
- return tryExtractEvent(logs, this.address.toString(), TallySlashingProposerAbi, 'RoundExecuted');
142
+ return tryExtractEvent(logs, this.address.toString(), SlashingProposerAbi, 'RoundExecuted');
145
143
  }
146
144
 
147
145
  /**
@@ -161,9 +159,9 @@ export class TallySlashingProposerContract {
161
159
 
162
160
  return {
163
161
  to: this.contract.address,
164
- abi: TallySlashingProposerAbi,
162
+ abi: SlashingProposerAbi,
165
163
  data: encodeFunctionData({
166
- abi: TallySlashingProposerAbi,
164
+ abi: SlashingProposerAbi,
167
165
  functionName: 'vote',
168
166
  args: [votes, signature.toViemSignature()],
169
167
  }),
@@ -173,7 +171,7 @@ export class TallySlashingProposerContract {
173
171
  /** Returns the typed data definition to EIP712-sign for voting */
174
172
  public buildVoteTypedData(votes: Hex, slot: SlotNumber): TypedDataDefinition {
175
173
  const domain = {
176
- name: 'TallySlashingProposer',
174
+ name: 'SlashingProposer',
177
175
  version: '1',
178
176
  chainId: this.client.chain.id,
179
177
  verifyingContract: this.contract.address,
@@ -209,9 +207,9 @@ export class TallySlashingProposerContract {
209
207
  public buildVoteRequestWithSignature(votes: Hex, signature: { v: number; r: Hex; s: Hex }): L1TxRequest {
210
208
  return {
211
209
  to: this.contract.address,
212
- abi: TallySlashingProposerAbi,
210
+ abi: SlashingProposerAbi,
213
211
  data: encodeFunctionData({
214
- abi: TallySlashingProposerAbi,
212
+ abi: SlashingProposerAbi,
215
213
  functionName: 'vote',
216
214
  args: [votes, signature],
217
215
  }),
@@ -227,9 +225,9 @@ export class TallySlashingProposerContract {
227
225
  public buildExecuteRoundRequest(round: bigint, committees: EthAddress[][]): L1TxRequest {
228
226
  return {
229
227
  to: this.contract.address,
230
- abi: mergeAbis([TallySlashingProposerAbi, SlasherAbi]),
228
+ abi: mergeAbis([SlashingProposerAbi, SlasherAbi]),
231
229
  data: encodeFunctionData({
232
- abi: TallySlashingProposerAbi,
230
+ abi: SlashingProposerAbi,
233
231
  functionName: 'executeRound',
234
232
  args: [round, committees.map(c => c.map(addr => addr.toString()))],
235
233
  }),
@@ -230,7 +230,6 @@ export function computeValidatorData(operator: Operator): ValidatorJson {
230
230
  export interface RollupUpgradeAddresses {
231
231
  rollupAddress: string;
232
232
  verifierAddress: string;
233
- slashFactoryAddress: string;
234
233
  inboxAddress: string;
235
234
  outboxAddress: string;
236
235
  feeJuicePortalAddress: string;
@@ -243,7 +242,6 @@ export interface RollupUpgradeAddresses {
243
242
  export interface ForgeRollupUpgradeResult {
244
243
  rollupAddress: Hex;
245
244
  verifierAddress: Hex;
246
- slashFactoryAddress: Hex;
247
245
  inboxAddress: Hex;
248
246
  outboxAddress: Hex;
249
247
  feeJuicePortalAddress: Hex;
@@ -403,7 +401,6 @@ export async function deployAztecL1Contracts(
403
401
  governanceProposerAddress: EthAddress.fromString(result.governanceProposerAddress),
404
402
  governanceAddress: EthAddress.fromString(result.governanceAddress),
405
403
  stakingAssetAddress: EthAddress.fromString(result.stakingAssetAddress),
406
- slashFactoryAddress: result.slashFactoryAddress ? EthAddress.fromString(result.slashFactoryAddress) : undefined,
407
404
  feeAssetHandlerAddress: result.feeAssetHandlerAddress
408
405
  ? EthAddress.fromString(result.feeAssetHandlerAddress)
409
406
  : undefined,
@@ -585,7 +582,7 @@ export function getDeployRollupForUpgradeEnvVars(
585
582
  AZTEC_EXIT_DELAY_SECONDS: args.exitDelaySeconds.toString(),
586
583
  AZTEC_PROVING_COST_PER_MANA: args.provingCostPerMana.toString(),
587
584
  AZTEC_INITIAL_ETH_PER_FEE_ASSET: args.initialEthPerFeeAsset.toString(),
588
- AZTEC_SLASHER_FLAVOR: args.slasherFlavor,
585
+ AZTEC_SLASHER_ENABLED: args.slasherEnabled ? 'true' : 'false',
589
586
  AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS: args.slashingRoundSizeInEpochs.toString(),
590
587
  AZTEC_SLASHING_QUORUM: args.slashingQuorum?.toString(),
591
588
  AZTEC_SLASHING_OFFSET_IN_ROUNDS: args.slashingOffsetInRounds.toString(),
@@ -645,6 +642,5 @@ export const deployRollupForUpgrade = async (
645
642
 
646
643
  return {
647
644
  rollup,
648
- slashFactoryAddress: result.slashFactoryAddress,
649
645
  };
650
646
  };
@@ -18,7 +18,7 @@ export const l1ContractsDefaultEnv = {
18
18
  AZTEC_MANA_TARGET: 100000000,
19
19
  AZTEC_PROVING_COST_PER_MANA: 100,
20
20
  AZTEC_INITIAL_ETH_PER_FEE_ASSET: 10000000,
21
- AZTEC_SLASHER_FLAVOR: 'tally',
21
+ AZTEC_SLASHER_ENABLED: true,
22
22
  AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS: 4,
23
23
  AZTEC_SLASHING_LIFETIME_IN_ROUNDS: 5,
24
24
  AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS: 0,