@aztec/ethereum 1.0.0 → 1.1.0

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 (44) hide show
  1. package/dest/config.d.ts +27 -5
  2. package/dest/config.d.ts.map +1 -1
  3. package/dest/config.js +90 -9
  4. package/dest/contracts/empire_base.d.ts +3 -4
  5. package/dest/contracts/empire_base.d.ts.map +1 -1
  6. package/dest/contracts/empire_base.js +4 -8
  7. package/dest/contracts/governance_proposer.d.ts +2 -2
  8. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  9. package/dest/contracts/governance_proposer.js +4 -9
  10. package/dest/contracts/multicall.d.ts +2 -1
  11. package/dest/contracts/multicall.d.ts.map +1 -1
  12. package/dest/contracts/multicall.js +79 -48
  13. package/dest/contracts/rollup.d.ts +13 -1
  14. package/dest/contracts/rollup.d.ts.map +1 -1
  15. package/dest/contracts/rollup.js +64 -0
  16. package/dest/contracts/slashing_proposer.d.ts +2 -2
  17. package/dest/contracts/slashing_proposer.d.ts.map +1 -1
  18. package/dest/contracts/slashing_proposer.js +5 -10
  19. package/dest/deploy_l1_contracts.d.ts +837 -534
  20. package/dest/deploy_l1_contracts.d.ts.map +1 -1
  21. package/dest/deploy_l1_contracts.js +35 -19
  22. package/dest/l1_tx_utils.d.ts +7 -2
  23. package/dest/l1_tx_utils.d.ts.map +1 -1
  24. package/dest/l1_tx_utils.js +41 -13
  25. package/dest/l1_tx_utils_with_blobs.d.ts +2 -1
  26. package/dest/l1_tx_utils_with_blobs.d.ts.map +1 -1
  27. package/dest/l1_tx_utils_with_blobs.js +5 -3
  28. package/dest/queries.d.ts.map +1 -1
  29. package/dest/queries.js +5 -3
  30. package/dest/test/start_anvil.d.ts +2 -0
  31. package/dest/test/start_anvil.d.ts.map +1 -1
  32. package/dest/test/start_anvil.js +3 -0
  33. package/package.json +6 -4
  34. package/src/config.ts +102 -8
  35. package/src/contracts/empire_base.ts +10 -16
  36. package/src/contracts/governance_proposer.ts +10 -10
  37. package/src/contracts/multicall.ts +73 -46
  38. package/src/contracts/rollup.ts +82 -1
  39. package/src/contracts/slashing_proposer.ts +11 -11
  40. package/src/deploy_l1_contracts.ts +45 -18
  41. package/src/l1_tx_utils.ts +56 -19
  42. package/src/l1_tx_utils_with_blobs.ts +14 -3
  43. package/src/queries.ts +3 -0
  44. package/src/test/start_anvil.ts +5 -3
@@ -5,8 +5,10 @@ import { type Anvil } from '@viem/anvil';
5
5
  export declare function startAnvil(opts?: {
6
6
  port?: number;
7
7
  l1BlockTime?: number;
8
+ captureMethodCalls?: boolean;
8
9
  }): Promise<{
9
10
  anvil: Anvil;
11
+ methodCalls?: string[];
10
12
  rpcUrl: string;
11
13
  stop: () => Promise<void>;
12
14
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"start_anvil.d.ts","sourceRoot":"","sources":["../../src/test/start_anvil.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,KAAK,EAAe,MAAM,aAAa,CAAC;AAGtD;;GAEG;AACH,wBAAsB,UAAU,CAC9B,IAAI,GAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,GACL,OAAO,CAAC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAuCtE"}
1
+ {"version":3,"file":"start_anvil.d.ts","sourceRoot":"","sources":["../../src/test/start_anvil.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,KAAK,EAAe,MAAM,aAAa,CAAC;AAGtD;;GAEG;AACH,wBAAsB,UAAU,CAC9B,IAAI,GAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CACzB,GACL,OAAO,CAAC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAwC9F"}
@@ -6,6 +6,7 @@ import { dirname, resolve } from 'path';
6
6
  * Ensures there's a running Anvil instance and returns the RPC URL.
7
7
  */ export async function startAnvil(opts = {}) {
8
8
  const anvilBinary = resolve(dirname(fileURLToPath(import.meta.url)), '../../', 'scripts/anvil_kill_wrapper.sh');
9
+ const methodCalls = opts.captureMethodCalls ? [] : undefined;
9
10
  let port;
10
11
  // Start anvil.
11
12
  // We go via a wrapper script to ensure if the parent dies, anvil dies.
@@ -19,6 +20,7 @@ import { dirname, resolve } from 'path';
19
20
  });
20
21
  // Listen to the anvil output to get the port.
21
22
  const removeHandler = anvil.on('message', (message)=>{
23
+ methodCalls?.push(...message.match(/eth_[^\s]+/g) || []);
22
24
  if (port === undefined && message.includes('Listening on')) {
23
25
  port = parseInt(message.match(/Listening on ([^:]+):(\d+)/)[2]);
24
26
  }
@@ -38,6 +40,7 @@ import { dirname, resolve } from 'path';
38
40
  // Object.defineProperty(anvil, 'port', { value: port, writable: false });
39
41
  return {
40
42
  anvil,
43
+ methodCalls,
41
44
  stop: ()=>anvil.stop(),
42
45
  rpcUrl: `http://127.0.0.1:${port}`
43
46
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/ethereum",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -32,11 +32,12 @@
32
32
  "../package.common.json"
33
33
  ],
34
34
  "dependencies": {
35
- "@aztec/blob-lib": "1.0.0",
36
- "@aztec/foundation": "1.0.0",
37
- "@aztec/l1-artifacts": "1.0.0",
35
+ "@aztec/blob-lib": "1.1.0",
36
+ "@aztec/foundation": "1.1.0",
37
+ "@aztec/l1-artifacts": "1.1.0",
38
38
  "@viem/anvil": "^0.0.10",
39
39
  "dotenv": "^16.0.3",
40
+ "lodash.pickby": "^4.5.0",
40
41
  "tslib": "^2.4.0",
41
42
  "viem": "2.23.7",
42
43
  "zod": "^3.23.8"
@@ -44,6 +45,7 @@
44
45
  "devDependencies": {
45
46
  "@jest/globals": "^30.0.0",
46
47
  "@types/jest": "^30.0.0",
48
+ "@types/lodash.pickby": "^4",
47
49
  "@types/node": "^22.15.17",
48
50
  "@viem/anvil": "^0.0.10",
49
51
  "get-port": "^7.1.0",
package/src/config.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  type ConfigMappingsType,
3
+ type NetworkNames,
3
4
  bigintConfigHelper,
4
5
  booleanConfigHelper,
5
6
  getConfigFromMappings,
6
7
  numberConfigHelper,
7
8
  } from '@aztec/foundation/config';
9
+ import { EthAddress } from '@aztec/foundation/eth-address';
8
10
 
9
11
  import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from './l1_tx_utils.js';
10
12
 
@@ -42,6 +44,8 @@ export type L1ContractsConfig = {
42
44
  manaTarget: bigint;
43
45
  /** The proving cost per mana */
44
46
  provingCostPerMana: bigint;
47
+ /** The number of seconds to wait for an exit */
48
+ exitDelaySeconds: number;
45
49
  } & L1TxUtilsConfig;
46
50
 
47
51
  export const DefaultL1ContractsConfig = {
@@ -52,19 +56,73 @@ export const DefaultL1ContractsConfig = {
52
56
  aztecProofSubmissionEpochs: 1, // you have a full epoch to submit a proof after the epoch to prove ends
53
57
  depositAmount: BigInt(100e18),
54
58
  minimumStake: BigInt(50e18),
55
- slashingQuorum: 6,
56
- slashingRoundSize: 10,
57
- governanceProposerQuorum: 51,
58
- governanceProposerRoundSize: 100,
59
+ slashingQuorum: 101,
60
+ slashingRoundSize: 200,
61
+ governanceProposerQuorum: 151,
62
+ governanceProposerRoundSize: 300,
59
63
  manaTarget: BigInt(1e10),
60
64
  provingCostPerMana: BigInt(100),
65
+ exitDelaySeconds: 2 * 24 * 60 * 60,
61
66
  } satisfies L1ContractsConfig;
62
67
 
68
+ const LocalGovernanceConfiguration = {
69
+ proposeConfig: {
70
+ lockDelay: 60n * 60n * 24n * 30n,
71
+ lockAmount: 1n * 10n ** 24n,
72
+ },
73
+ votingDelay: 60n,
74
+ votingDuration: 60n * 60n,
75
+ executionDelay: 60n,
76
+ gracePeriod: 60n * 60n * 24n * 7n,
77
+ quorum: 1n * 10n ** 17n,
78
+ voteDifferential: 4n * 10n ** 16n,
79
+ minimumVotes: 400n * 10n ** 18n,
80
+ };
81
+
82
+ const TestnetGovernanceConfiguration = {
83
+ proposeConfig: {
84
+ lockDelay: 60n * 60n * 24n,
85
+ lockAmount: DefaultL1ContractsConfig.depositAmount * 100n,
86
+ },
87
+ votingDelay: 60n,
88
+ votingDuration: 60n * 60n,
89
+ executionDelay: 60n * 60n * 24n,
90
+ gracePeriod: 60n * 60n * 24n * 7n,
91
+ quorum: 3n * 10n ** 17n,
92
+ voteDifferential: 4n * 10n ** 16n,
93
+ minimumVotes: DefaultL1ContractsConfig.minimumStake * 200n,
94
+ };
95
+
96
+ export const getGovernanceConfiguration = (networkName: NetworkNames) => {
97
+ if (networkName === 'alpha-testnet' || networkName === 'testnet') {
98
+ return TestnetGovernanceConfiguration;
99
+ }
100
+ return LocalGovernanceConfiguration;
101
+ };
102
+
63
103
  // Making a default config here as we are only using it thought the deployment
64
104
  // and do not expect to be using different setups, so having environment variables
65
105
  // for it seems overkill
66
- export const DefaultRewardConfig = {
106
+ const LocalRewardConfig = {
107
+ sequencerBps: 5000,
108
+ rewardDistributor: EthAddress.ZERO.toString(),
109
+ booster: EthAddress.ZERO.toString(),
110
+ };
111
+
112
+ const TestnetRewardConfig = {
67
113
  sequencerBps: 5000,
114
+ rewardDistributor: EthAddress.ZERO.toString(),
115
+ booster: EthAddress.ZERO.toString(),
116
+ };
117
+
118
+ export const getRewardConfig = (networkName: NetworkNames) => {
119
+ if (networkName === 'alpha-testnet' || networkName === 'testnet') {
120
+ return TestnetRewardConfig;
121
+ }
122
+ return LocalRewardConfig;
123
+ };
124
+
125
+ const LocalRewardBoostConfig = {
68
126
  increment: 200000,
69
127
  maxScore: 5000000,
70
128
  a: 5000,
@@ -72,10 +130,41 @@ export const DefaultRewardConfig = {
72
130
  minimum: 100000,
73
131
  };
74
132
 
133
+ const TestnetRewardBoostConfig = {
134
+ increment: 125000,
135
+ maxScore: 15000000,
136
+ a: 1000,
137
+ k: 1000000,
138
+ minimum: 100000,
139
+ };
140
+
141
+ export const getRewardBoostConfig = (networkName: NetworkNames) => {
142
+ if (networkName === 'alpha-testnet' || networkName === 'testnet') {
143
+ return TestnetRewardBoostConfig;
144
+ }
145
+ return LocalRewardBoostConfig;
146
+ };
147
+
75
148
  // Similar to the above, no need for environment variables for this.
76
- export const DefaultEntryQueueConfig = {
77
- flushSizeMin: 48,
78
- flushSizeQuotient: 2,
149
+ const LocalEntryQueueConfig = {
150
+ bootstrapValidatorSetSize: 0,
151
+ bootstrapFlushSize: 0,
152
+ normalFlushSizeMin: 48,
153
+ normalFlushSizeQuotient: 2,
154
+ };
155
+
156
+ const TestnetEntryQueueConfig = {
157
+ bootstrapValidatorSetSize: 750,
158
+ bootstrapFlushSize: 75,
159
+ normalFlushSizeMin: 1,
160
+ normalFlushSizeQuotient: 2475,
161
+ };
162
+
163
+ export const getEntryQueueConfig = (networkName: NetworkNames) => {
164
+ if (networkName === 'alpha-testnet' || networkName === 'testnet') {
165
+ return TestnetEntryQueueConfig;
166
+ }
167
+ return LocalEntryQueueConfig;
79
168
  };
80
169
 
81
170
  export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> = {
@@ -144,6 +233,11 @@ export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> =
144
233
  description: 'The proving cost per mana',
145
234
  ...bigintConfigHelper(DefaultL1ContractsConfig.provingCostPerMana),
146
235
  },
236
+ exitDelaySeconds: {
237
+ env: 'AZTEC_EXIT_DELAY_SECONDS',
238
+ description: 'The delay before a validator can exit the set',
239
+ ...numberConfigHelper(DefaultL1ContractsConfig.exitDelaySeconds),
240
+ },
147
241
  ...l1TxUtilsConfigMappings,
148
242
  };
149
243
 
@@ -1,16 +1,20 @@
1
1
  import { Signature } from '@aztec/foundation/eth-signature';
2
2
  import { EmpireBaseAbi } from '@aztec/l1-artifacts/EmpireBaseAbi';
3
3
 
4
- import { type Hex, type WalletClient, encodeFunctionData } from 'viem';
4
+ import { type Hex, encodeFunctionData, hashTypedData } from 'viem';
5
5
 
6
6
  import type { L1TxRequest } from '../l1_tx_utils.js';
7
- import type { ExtendedViemWalletClient } from '../types.js';
8
7
 
9
8
  export interface IEmpireBase {
10
9
  getRoundInfo(rollupAddress: Hex, round: bigint): Promise<{ lastVote: bigint; leader: Hex; executed: boolean }>;
11
10
  computeRound(slot: bigint): Promise<bigint>;
12
11
  createVoteRequest(payload: Hex): L1TxRequest;
13
- createVoteRequestWithSignature(payload: Hex, wallet: ExtendedViemWalletClient): Promise<L1TxRequest>;
12
+ createVoteRequestWithSignature(
13
+ payload: Hex,
14
+ chainId: number,
15
+ signerAddress: Hex,
16
+ signer: (msg: Hex) => Promise<Hex>,
17
+ ): Promise<L1TxRequest>;
14
18
  }
15
19
 
16
20
  export function encodeVote(payload: Hex): Hex {
@@ -39,7 +43,7 @@ export function encodeVoteWithSignature(payload: Hex, signature: Signature) {
39
43
  * @returns The EIP-712 signature
40
44
  */
41
45
  export async function signVoteWithSig(
42
- walletClient: WalletClient,
46
+ signer: (msg: Hex) => Promise<Hex>,
43
47
  proposal: Hex,
44
48
  nonce: bigint,
45
49
  verifyingContract: Hex,
@@ -64,16 +68,6 @@ export async function signVoteWithSig(
64
68
  nonce,
65
69
  };
66
70
 
67
- if (!walletClient.account) {
68
- throw new Error('Wallet client must be connected to an account');
69
- }
70
-
71
- const signatureHex = await walletClient.signTypedData({
72
- account: walletClient.account.address,
73
- domain,
74
- types,
75
- primaryType: 'Vote',
76
- message,
77
- });
78
- return Signature.fromString(signatureHex);
71
+ const msg = hashTypedData({ domain, types, primaryType: 'Vote', message });
72
+ return Signature.fromString(await signer(msg));
79
73
  }
@@ -5,7 +5,7 @@ import { GovernanceProposerAbi } from '@aztec/l1-artifacts/GovernanceProposerAbi
5
5
  import { type GetContractReturnType, type Hex, type TransactionReceipt, encodeFunctionData, getContract } from 'viem';
6
6
 
7
7
  import type { GasPrice, L1TxRequest, L1TxUtils } from '../l1_tx_utils.js';
8
- import type { ExtendedViemWalletClient, ViemClient } from '../types.js';
8
+ import type { ViemClient } from '../types.js';
9
9
  import { type IEmpireBase, encodeVote, encodeVoteWithSignature, signVoteWithSig } from './empire_base.js';
10
10
  import { extractProposalIdFromLogs } from './governance.js';
11
11
 
@@ -52,12 +52,7 @@ export class GovernanceProposerContract implements IEmpireBase {
52
52
  rollupAddress: Hex,
53
53
  round: bigint,
54
54
  ): Promise<{ lastVote: bigint; leader: Hex; executed: boolean }> {
55
- const roundInfo = await this.proposer.read.rounds([rollupAddress, round]);
56
- return {
57
- lastVote: roundInfo[0],
58
- leader: roundInfo[1],
59
- executed: roundInfo[2],
60
- };
55
+ return await this.proposer.read.getRoundData([rollupAddress, round]);
61
56
  }
62
57
 
63
58
  public getProposalVotes(rollupAddress: Hex, round: bigint, proposal: Hex): Promise<bigint> {
@@ -71,9 +66,14 @@ export class GovernanceProposerContract implements IEmpireBase {
71
66
  };
72
67
  }
73
68
 
74
- public async createVoteRequestWithSignature(payload: Hex, wallet: ExtendedViemWalletClient): Promise<L1TxRequest> {
75
- const nonce = await this.getNonce(wallet.account.address);
76
- const signature = await signVoteWithSig(wallet, payload, nonce, this.address.toString(), wallet.chain.id);
69
+ public async createVoteRequestWithSignature(
70
+ payload: Hex,
71
+ chainId: number,
72
+ signerAddress: Hex,
73
+ signer: (msg: Hex) => Promise<Hex>,
74
+ ): Promise<L1TxRequest> {
75
+ const nonce = await this.getNonce(signerAddress);
76
+ const signature = await signVoteWithSig(signer, payload, nonce, this.address.toString(), chainId);
77
77
  return {
78
78
  to: this.address.toString(),
79
79
  data: encodeVoteWithSignature(payload, signature),
@@ -5,6 +5,7 @@ import { type EncodeFunctionDataParameters, type Hex, encodeFunctionData, multic
5
5
 
6
6
  import type { L1BlobInputs, L1GasConfig, L1TxRequest, L1TxUtils } from '../l1_tx_utils.js';
7
7
  import type { ExtendedViemWalletClient } from '../types.js';
8
+ import { FormattedViemError, formatViemError } from '../utils.js';
8
9
  import { RollupContract } from './rollup.js';
9
10
 
10
11
  export const MULTI_CALL_3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11' as const;
@@ -32,61 +33,87 @@ export class Multicall3 {
32
33
 
33
34
  const encodedForwarderData = encodeFunctionData(forwarderFunctionData);
34
35
 
35
- const { receipt, gasPrice } = await l1TxUtils.sendAndMonitorTransaction(
36
- {
37
- to: MULTI_CALL_3_ADDRESS,
38
- data: encodedForwarderData,
39
- },
40
- gasConfig,
41
- blobConfig,
42
- );
36
+ try {
37
+ const { receipt, gasPrice } = await l1TxUtils.sendAndMonitorTransaction(
38
+ {
39
+ to: MULTI_CALL_3_ADDRESS,
40
+ data: encodedForwarderData,
41
+ },
42
+ gasConfig,
43
+ blobConfig,
44
+ );
43
45
 
44
- if (receipt.status === 'success') {
45
- const stats = await l1TxUtils.getTransactionStats(receipt.transactionHash);
46
- return { receipt, gasPrice, stats };
47
- } else {
48
- logger.error('Forwarder transaction failed', undefined, { receipt });
46
+ if (receipt.status === 'success') {
47
+ const stats = await l1TxUtils.getTransactionStats(receipt.transactionHash);
48
+ return { receipt, gasPrice, stats };
49
+ } else {
50
+ logger.error('Forwarder transaction failed', undefined, { receipt });
49
51
 
50
- const args = {
51
- ...forwarderFunctionData,
52
- address: MULTI_CALL_3_ADDRESS,
53
- };
52
+ const args = {
53
+ ...forwarderFunctionData,
54
+ address: MULTI_CALL_3_ADDRESS,
55
+ };
54
56
 
55
- let errorMsg: string | undefined;
57
+ let errorMsg: string | undefined;
56
58
 
57
- if (blobConfig) {
58
- const maxFeePerBlobGas = blobConfig.maxFeePerBlobGas ?? gasPrice.maxFeePerBlobGas;
59
- if (maxFeePerBlobGas === undefined) {
60
- errorMsg = 'maxFeePerBlobGas is required to get the error message';
61
- } else {
62
- logger.debug('Trying to get error from reverted tx with blob config');
63
- errorMsg = await l1TxUtils.tryGetErrorFromRevertedTx(
64
- encodedForwarderData,
65
- args,
66
- {
67
- blobs: blobConfig.blobs,
68
- kzg: blobConfig.kzg,
69
- maxFeePerBlobGas,
70
- },
71
- [
59
+ if (blobConfig) {
60
+ const maxFeePerBlobGas = blobConfig.maxFeePerBlobGas ?? gasPrice.maxFeePerBlobGas;
61
+ if (maxFeePerBlobGas === undefined) {
62
+ errorMsg = 'maxFeePerBlobGas is required to get the error message';
63
+ } else {
64
+ logger.debug('Trying to get error from reverted tx with blob config');
65
+ errorMsg = await l1TxUtils.tryGetErrorFromRevertedTx(
66
+ encodedForwarderData,
67
+ args,
72
68
  {
73
- address: rollupAddress,
74
- stateDiff: [
75
- {
76
- slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
77
- value: toPaddedHex(0n, true),
78
- },
79
- ],
69
+ blobs: blobConfig.blobs,
70
+ kzg: blobConfig.kzg,
71
+ maxFeePerBlobGas,
80
72
  },
81
- ],
82
- );
73
+ [
74
+ {
75
+ address: rollupAddress,
76
+ stateDiff: [
77
+ {
78
+ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
79
+ value: toPaddedHex(0n, true),
80
+ },
81
+ ],
82
+ },
83
+ ],
84
+ );
85
+ }
86
+ } else {
87
+ logger.debug('Trying to get error from reverted tx without blob config');
88
+ errorMsg = await l1TxUtils.tryGetErrorFromRevertedTx(encodedForwarderData, args, undefined, []);
83
89
  }
84
- } else {
85
- logger.debug('Trying to get error from reverted tx without blob config');
86
- errorMsg = await l1TxUtils.tryGetErrorFromRevertedTx(encodedForwarderData, args, undefined, []);
90
+
91
+ return { receipt, gasPrice, errorMsg };
87
92
  }
93
+ } catch (err) {
94
+ for (const request of requests) {
95
+ logger.debug('Simulating request', { request });
96
+ const result = await l1TxUtils
97
+ .simulate(request, undefined, [
98
+ {
99
+ address: rollupAddress,
100
+ stateDiff: [
101
+ { slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
102
+ ],
103
+ },
104
+ ])
105
+ .catch(err => formatViemError(err, request.abi));
106
+ if (result instanceof FormattedViemError) {
107
+ logger.error('Found error in simulation', result, {
108
+ to: request.to ?? 'null',
109
+ data: request.data,
110
+ });
88
111
 
89
- return { receipt, gasPrice, errorMsg };
112
+ return result;
113
+ }
114
+ }
115
+ logger.warn('Failed to get error from reverted tx', { err });
116
+ throw err;
90
117
  }
91
118
  }
92
119
  }
@@ -22,6 +22,11 @@ export type ViemCommitteeAttestation = {
22
22
  signature: ViemSignature;
23
23
  };
24
24
 
25
+ export type ViemCommitteeAttestations = {
26
+ signatureIndices: `0x${string}`;
27
+ signaturesOrAddresses: `0x${string}`;
28
+ };
29
+
25
30
  export type L1RollupContractAddresses = Pick<
26
31
  L1ContractAddresses,
27
32
  | 'rollupAddress'
@@ -174,6 +179,11 @@ export class RollupContract {
174
179
  return this.rollup.read.getDepositAmount();
175
180
  }
176
181
 
182
+ @memoize
183
+ getExitDelay() {
184
+ return this.rollup.read.getExitDelay();
185
+ }
186
+
177
187
  @memoize
178
188
  getManaTarget() {
179
189
  return this.rollup.read.getManaTarget();
@@ -376,7 +386,7 @@ export class RollupContract {
376
386
  public async validateHeader(
377
387
  args: readonly [
378
388
  ViemHeader,
379
- ViemCommitteeAttestation[],
389
+ ViemCommitteeAttestations,
380
390
  `0x${string}`,
381
391
  `0x${string}`,
382
392
  {
@@ -399,6 +409,77 @@ export class RollupContract {
399
409
  }
400
410
  }
401
411
 
412
+ /**
413
+ * Packs an array of committee attestations into the format expected by the Solidity contract
414
+ *
415
+ * @param attestations - Array of committee attestations with addresses and signatures
416
+ * @returns Packed attestations with bitmap and tightly packed signature/address data
417
+ */
418
+ static packAttestations(attestations: ViemCommitteeAttestation[]): ViemCommitteeAttestations {
419
+ const length = attestations.length;
420
+
421
+ // Calculate bitmap size (1 bit per attestation, rounded up to nearest byte)
422
+ const bitmapSize = Math.ceil(length / 8);
423
+ const signatureIndices = new Uint8Array(bitmapSize);
424
+
425
+ // Calculate total data size needed
426
+ let totalDataSize = 0;
427
+ for (let i = 0; i < length; i++) {
428
+ const signature = attestations[i].signature;
429
+ // Check if signature is empty (v = 0)
430
+ const isEmpty = signature.v === 0;
431
+
432
+ if (!isEmpty) {
433
+ totalDataSize += 65; // v (1) + r (32) + s (32)
434
+ } else {
435
+ totalDataSize += 20; // address only
436
+ }
437
+ }
438
+
439
+ const signaturesOrAddresses = new Uint8Array(totalDataSize);
440
+ let dataIndex = 0;
441
+
442
+ // Pack the data
443
+ for (let i = 0; i < length; i++) {
444
+ const attestation = attestations[i];
445
+ const signature = attestation.signature;
446
+
447
+ // Check if signature is empty
448
+ const isEmpty = signature.v === 0;
449
+
450
+ if (!isEmpty) {
451
+ // Set bit in bitmap (bit 7-0 in each byte, left to right)
452
+ const byteIndex = Math.floor(i / 8);
453
+ const bitIndex = 7 - (i % 8);
454
+ signatureIndices[byteIndex] |= 1 << bitIndex;
455
+
456
+ // Pack signature: v + r + s
457
+ signaturesOrAddresses[dataIndex] = signature.v;
458
+ dataIndex++;
459
+
460
+ // Pack r (32 bytes)
461
+ const rBytes = Buffer.from(signature.r.slice(2), 'hex');
462
+ signaturesOrAddresses.set(rBytes, dataIndex);
463
+ dataIndex += 32;
464
+
465
+ // Pack s (32 bytes)
466
+ const sBytes = Buffer.from(signature.s.slice(2), 'hex');
467
+ signaturesOrAddresses.set(sBytes, dataIndex);
468
+ dataIndex += 32;
469
+ } else {
470
+ // Pack address only (20 bytes)
471
+ const addrBytes = Buffer.from(attestation.addr.slice(2), 'hex');
472
+ signaturesOrAddresses.set(addrBytes, dataIndex);
473
+ dataIndex += 20;
474
+ }
475
+ }
476
+
477
+ return {
478
+ signatureIndices: `0x${Buffer.from(signatureIndices).toString('hex')}`,
479
+ signaturesOrAddresses: `0x${Buffer.from(signaturesOrAddresses).toString('hex')}`,
480
+ };
481
+ }
482
+
402
483
  /**
403
484
  * @notice Calls `canProposeAtTime` with the time of the next Ethereum block and the sender address
404
485
  *
@@ -12,7 +12,7 @@ import {
12
12
  } from 'viem';
13
13
 
14
14
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils.js';
15
- import type { ExtendedViemWalletClient, ViemClient } from '../types.js';
15
+ import type { ViemClient } from '../types.js';
16
16
  import { FormattedViemError } from '../utils.js';
17
17
  import { type IEmpireBase, encodeVote, encodeVoteWithSignature, signVoteWithSig } from './empire_base.js';
18
18
 
@@ -57,12 +57,7 @@ export class SlashingProposerContract extends EventEmitter implements IEmpireBas
57
57
  rollupAddress: Hex,
58
58
  round: bigint,
59
59
  ): Promise<{ lastVote: bigint; leader: Hex; executed: boolean }> {
60
- const roundInfo = await this.proposer.read.rounds([rollupAddress, round]);
61
- return {
62
- lastVote: roundInfo[0],
63
- leader: roundInfo[1],
64
- executed: roundInfo[2],
65
- };
60
+ return await this.proposer.read.getRoundData([rollupAddress, round]);
66
61
  }
67
62
 
68
63
  public getProposalVotes(rollupAddress: Hex, round: bigint, proposal: Hex): Promise<bigint> {
@@ -76,9 +71,14 @@ export class SlashingProposerContract extends EventEmitter implements IEmpireBas
76
71
  };
77
72
  }
78
73
 
79
- public async createVoteRequestWithSignature(payload: Hex, wallet: ExtendedViemWalletClient): Promise<L1TxRequest> {
80
- const nonce = await this.getNonce(wallet.account.address);
81
- const signature = await signVoteWithSig(wallet, payload, nonce, this.address.toString(), wallet.chain.id);
74
+ public async createVoteRequestWithSignature(
75
+ payload: Hex,
76
+ chainId: number,
77
+ signerAddress: Hex,
78
+ signer: (msg: Hex) => Promise<Hex>,
79
+ ): Promise<L1TxRequest> {
80
+ const nonce = await this.getNonce(signerAddress);
81
+ const signature = await signVoteWithSig(signer, payload, nonce, this.address.toString(), chainId);
82
82
  return {
83
83
  to: this.address.toString(),
84
84
  data: encodeVoteWithSignature(payload, signature),
@@ -152,7 +152,7 @@ export class SlashingProposerContract extends EventEmitter implements IEmpireBas
152
152
  {
153
153
  // Gas estimation is way off for this, likely because we are creating the contract/selector to call
154
154
  // for the actual slashing dynamically.
155
- gasLimitBufferPercentage: 1000,
155
+ gasLimitBufferPercentage: 50, // +50% gas
156
156
  },
157
157
  )
158
158
  .catch(err => {