@aztec/ethereum 0.0.1-commit.6d63667d → 0.0.1-commit.7ac86ea28

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 (86) hide show
  1. package/dest/config.d.ts +1 -1
  2. package/dest/config.d.ts.map +1 -1
  3. package/dest/config.js +5 -3
  4. package/dest/contracts/empire_base.d.ts +3 -1
  5. package/dest/contracts/empire_base.d.ts.map +1 -1
  6. package/dest/contracts/empire_slashing_proposer.d.ts +3 -1
  7. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
  8. package/dest/contracts/empire_slashing_proposer.js +9 -0
  9. package/dest/contracts/fee_asset_price_oracle.d.ts +101 -0
  10. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  11. package/dest/contracts/fee_asset_price_oracle.js +651 -0
  12. package/dest/contracts/governance.js +3 -3
  13. package/dest/contracts/governance_proposer.d.ts +3 -1
  14. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  15. package/dest/contracts/governance_proposer.js +9 -0
  16. package/dest/contracts/index.d.ts +2 -1
  17. package/dest/contracts/index.d.ts.map +1 -1
  18. package/dest/contracts/index.js +1 -0
  19. package/dest/contracts/rollup.d.ts +2 -1
  20. package/dest/contracts/rollup.d.ts.map +1 -1
  21. package/dest/contracts/rollup.js +5 -3
  22. package/dest/deploy_aztec_l1_contracts.d.ts +2 -3
  23. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  24. package/dest/deploy_aztec_l1_contracts.js +33 -19
  25. package/dest/deploy_l1_contract.js +3 -3
  26. package/dest/l1_artifacts.d.ts +1051 -42
  27. package/dest/l1_artifacts.d.ts.map +1 -1
  28. package/dest/l1_tx_utils/config.d.ts +7 -1
  29. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  30. package/dest/l1_tx_utils/config.js +14 -1
  31. package/dest/l1_tx_utils/factory.d.ts +18 -10
  32. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  33. package/dest/l1_tx_utils/factory.js +17 -7
  34. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
  35. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
  36. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
  37. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
  38. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
  39. package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
  40. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
  41. package/dest/l1_tx_utils/index-blobs.js +2 -2
  42. package/dest/l1_tx_utils/index.d.ts +2 -1
  43. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  44. package/dest/l1_tx_utils/index.js +1 -0
  45. package/dest/l1_tx_utils/l1_tx_utils.d.ts +15 -5
  46. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  47. package/dest/l1_tx_utils/l1_tx_utils.js +41 -7
  48. package/dest/l1_tx_utils/tx_delayer.d.ts +56 -0
  49. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  50. package/dest/{test → l1_tx_utils}/tx_delayer.js +62 -34
  51. package/dest/test/index.d.ts +1 -3
  52. package/dest/test/index.d.ts.map +1 -1
  53. package/dest/test/index.js +0 -2
  54. package/dest/test/upgrade_utils.js +2 -2
  55. package/package.json +5 -5
  56. package/src/config.ts +3 -2
  57. package/src/contracts/empire_base.ts +2 -0
  58. package/src/contracts/empire_slashing_proposer.ts +6 -0
  59. package/src/contracts/fee_asset_price_oracle.ts +280 -0
  60. package/src/contracts/governance.ts +3 -3
  61. package/src/contracts/governance_proposer.ts +6 -0
  62. package/src/contracts/index.ts +1 -0
  63. package/src/contracts/rollup.ts +11 -7
  64. package/src/deploy_aztec_l1_contracts.ts +56 -29
  65. package/src/deploy_l1_contract.ts +3 -3
  66. package/src/l1_tx_utils/config.ts +20 -0
  67. package/src/l1_tx_utils/factory.ts +31 -31
  68. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
  69. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
  70. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
  71. package/src/l1_tx_utils/index-blobs.ts +2 -2
  72. package/src/l1_tx_utils/index.ts +1 -0
  73. package/src/l1_tx_utils/l1_tx_utils.ts +46 -11
  74. package/src/{test → l1_tx_utils}/tx_delayer.ts +78 -50
  75. package/src/test/index.ts +0 -2
  76. package/src/test/upgrade_utils.ts +2 -2
  77. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  78. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  79. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  80. package/dest/test/delayed_tx_utils.d.ts +0 -13
  81. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  82. package/dest/test/delayed_tx_utils.js +0 -28
  83. package/dest/test/tx_delayer.d.ts +0 -36
  84. package/dest/test/tx_delayer.d.ts.map +0 -1
  85. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  86. package/src/test/delayed_tx_utils.ts +0 -52
@@ -3,13 +3,13 @@ import { GovernanceAbi } from '@aztec/l1-artifacts/GovernanceAbi';
3
3
  import { TestERC20Abi as StakingAssetAbi } from '@aztec/l1-artifacts/TestERC20Abi';
4
4
  import { getContract } from 'viem';
5
5
  import { extractProposalIdFromLogs } from '../contracts/governance.js';
6
- import { createL1TxUtilsFromViemWallet } from '../l1_tx_utils/index.js';
6
+ import { createL1TxUtils } from '../l1_tx_utils/index.js';
7
7
  import { EthCheatCodes } from './eth_cheat_codes.js';
8
8
  export async function executeGovernanceProposal(proposalId, governance, voteAmount, privateKey, l1Client, rpcUrls, logger) {
9
9
  const proposal = await governance.read.getProposal([
10
10
  proposalId
11
11
  ]);
12
- const l1TxUtils = createL1TxUtilsFromViemWallet(l1Client);
12
+ const l1TxUtils = createL1TxUtils(l1Client);
13
13
  const waitL1Block = async ()=>{
14
14
  await l1TxUtils.sendAndMonitorTransaction({
15
15
  to: l1Client.account.address,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/ethereum",
3
- "version": "0.0.1-commit.6d63667d",
3
+ "version": "0.0.1-commit.7ac86ea28",
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.6d63667d",
54
- "@aztec/constants": "0.0.1-commit.6d63667d",
55
- "@aztec/foundation": "0.0.1-commit.6d63667d",
56
- "@aztec/l1-artifacts": "0.0.1-commit.6d63667d",
53
+ "@aztec/blob-lib": "0.0.1-commit.7ac86ea28",
54
+ "@aztec/constants": "0.0.1-commit.7ac86ea28",
55
+ "@aztec/foundation": "0.0.1-commit.7ac86ea28",
56
+ "@aztec/l1-artifacts": "0.0.1-commit.7ac86ea28",
57
57
  "@viem/anvil": "^0.0.10",
58
58
  "dotenv": "^16.0.3",
59
59
  "lodash.chunk": "^4.2.0",
package/src/config.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  getConfigFromMappings,
7
7
  getDefaultConfig,
8
8
  numberConfigHelper,
9
+ omitConfigMappings,
9
10
  optionalNumberConfigHelper,
10
11
  } from '@aztec/foundation/config';
11
12
  import { EthAddress } from '@aztec/foundation/eth-address';
@@ -99,7 +100,7 @@ export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> =
99
100
  },
100
101
  aztecEpochDuration: {
101
102
  env: 'AZTEC_EPOCH_DURATION',
102
- description: `How many L2 slots an epoch lasts (maximum AZTEC_MAX_EPOCH_DURATION).`,
103
+ description: `How many L2 slots an epoch lasts (maximum MAX_CHECKPOINTS_PER_EPOCH).`,
103
104
  ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_EPOCH_DURATION),
104
105
  },
105
106
  aztecTargetCommitteeSize: {
@@ -238,7 +239,7 @@ export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> =
238
239
  description: 'The delay before a validator can exit the set',
239
240
  ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_EXIT_DELAY_SECONDS),
240
241
  },
241
- ...l1TxUtilsConfigMappings,
242
+ ...omitConfigMappings(l1TxUtilsConfigMappings, ['ethereumSlotDuration']),
242
243
  };
243
244
 
244
245
  /**
@@ -22,6 +22,8 @@ export interface IEmpireBase {
22
22
  signerAddress: Hex,
23
23
  signer: (msg: TypedDataDefinition) => Promise<Hex>,
24
24
  ): Promise<L1TxRequest>;
25
+ /** Checks if a payload was ever submitted to governance via submitRoundWinner. */
26
+ hasPayloadBeenProposed(payload: Hex, fromBlock: bigint): Promise<boolean>;
25
27
  }
26
28
 
27
29
  export function encodeSignal(payload: Hex): Hex {
@@ -126,6 +126,12 @@ export class EmpireSlashingProposerContract extends EventEmitter implements IEmp
126
126
  };
127
127
  }
128
128
 
129
+ /** Checks if a payload was ever submitted to governance via submitRoundWinner. */
130
+ public async hasPayloadBeenProposed(payload: Hex, fromBlock: bigint): Promise<boolean> {
131
+ const events = await this.proposer.getEvents.PayloadSubmitted({ payload }, { fromBlock, strict: true });
132
+ return events.length > 0;
133
+ }
134
+
129
135
  public listenToSubmittablePayloads(callback: (args: { payload: `0x${string}`; round: bigint }) => unknown) {
130
136
  return this.proposer.watchEvent.PayloadSubmittable(
131
137
  {},
@@ -0,0 +1,280 @@
1
+ import { memoize } from '@aztec/foundation/decorators';
2
+ import { EthAddress } from '@aztec/foundation/eth-address';
3
+ import { type Logger, createLogger } from '@aztec/foundation/log';
4
+
5
+ import { type Hex, encodeAbiParameters, getContract, keccak256, parseAbiParameters } from 'viem';
6
+
7
+ import type { ViemClient } from '../types.js';
8
+ import { RollupContract } from './rollup.js';
9
+
10
+ /** Maximum price modifier per checkpoint in basis points. ±100 bps = ±1% */
11
+ export const MAX_FEE_ASSET_PRICE_MODIFIER_BPS = 100n;
12
+
13
+ /**
14
+ * Validates that a fee asset price modifier is within the allowed range.
15
+ * Validators should call this before attesting to a checkpoint proposal.
16
+ *
17
+ * @param modifier - The fee asset price modifier in basis points
18
+ * @returns true if the modifier is valid (between -100 and +100 bps)
19
+ */
20
+ export function validateFeeAssetPriceModifier(modifier: bigint): boolean {
21
+ return modifier >= -MAX_FEE_ASSET_PRICE_MODIFIER_BPS && modifier <= MAX_FEE_ASSET_PRICE_MODIFIER_BPS;
22
+ }
23
+
24
+ /**
25
+ * Oracle for computing fee asset price modifiers based on Uniswap V4 pool prices.
26
+ * Only active on Ethereum mainnet - returns 0 on other chains.
27
+ */
28
+ export class FeeAssetPriceOracle {
29
+ constructor(
30
+ private client: ViemClient,
31
+ private readonly rollupContract: RollupContract,
32
+ private log: Logger = createLogger('fee-asset-price-oracle'),
33
+ ) {}
34
+
35
+ @memoize
36
+ async getUniswapOracle(): Promise<UniswapPriceOracle | undefined> {
37
+ const code = await this.client.getCode({ address: STATE_VIEW_ADDRESS.toString() });
38
+ if (code === undefined || code === '0x') {
39
+ this.log.warn('Uniswap V4 StateView contract not found, skipping fee asset price oracle');
40
+ return undefined;
41
+ }
42
+ this.log.info('Uniswap V4 StateView contract found, initializing fee asset price oracle');
43
+ const oracle = new UniswapPriceOracle(this.client, this.log);
44
+
45
+ try {
46
+ if (!(await oracle.isPoolInitialized())) {
47
+ this.log.warn('Uniswap V4 pool not initialized, skipping fee asset price oracle');
48
+ return undefined;
49
+ }
50
+ } catch (err) {
51
+ this.log.warn(`Failed to check if Uniswap V4 pool is initialized: ${err}`);
52
+ return undefined;
53
+ }
54
+
55
+ return oracle;
56
+ }
57
+
58
+ /**
59
+ * Computes the fee asset price modifier to be used in the next checkpoint proposal.
60
+ *
61
+ * The modifier adjusts the on-chain fee asset price toward the oracle price,
62
+ * clamped to ±1% (±100 basis points) per checkpoint.
63
+ *
64
+ * Returns 0 if not on mainnet or if the oracle query fails.
65
+ *
66
+ * @returns The price modifier in basis points (positive to increase price, negative to decrease)
67
+ */
68
+ async computePriceModifier(): Promise<bigint> {
69
+ const uniswapOracle = await this.getUniswapOracle();
70
+ if (!uniswapOracle) {
71
+ return 0n;
72
+ }
73
+
74
+ try {
75
+ // Get current on-chain price (ETH per fee asset, E12)
76
+ const currentPriceE12 = await this.rollupContract.getEthPerFeeAsset();
77
+
78
+ // Get oracle price (median of last N blocks, ETH per fee asset, E12)
79
+ const oraclePriceE12 = await uniswapOracle.getMeanEthPerFeeAssetE12();
80
+
81
+ // Compute modifier in basis points
82
+ const modifier = this.computePriceModifierBps(currentPriceE12, oraclePriceE12);
83
+
84
+ this.log.debug('Computed price modifier', {
85
+ currentPriceE12: currentPriceE12.toString(),
86
+ oraclePriceE12: oraclePriceE12.toString(),
87
+ modifierBps: modifier.toString(),
88
+ });
89
+
90
+ return modifier;
91
+ } catch (err) {
92
+ this.log.warn(`Failed to compute price modifier, using 0: ${err}`);
93
+ return 0n;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Gets the current oracle price (ETH per fee asset, scaled by 1e12).
99
+ * Returns undefined if not on mainnet or if the oracle query fails.
100
+ */
101
+ async getOraclePrice(): Promise<bigint | undefined> {
102
+ const uniswapOracle = await this.getUniswapOracle();
103
+ if (!uniswapOracle) {
104
+ return undefined;
105
+ }
106
+
107
+ try {
108
+ return await uniswapOracle.getMeanEthPerFeeAssetE12();
109
+ } catch (err) {
110
+ this.log.warn(`Failed to get oracle price: ${err}`);
111
+ return undefined;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Computes the basis points modifier needed to move from current price toward target price.
117
+ *
118
+ * @param currentPrice - Current ETH per fee asset (E12 scale)
119
+ * @param targetPrice - Target ETH per fee asset (E12 scale)
120
+ * @returns Basis points modifier clamped to ±100 (±1%)
121
+ */
122
+ computePriceModifierBps(currentPrice: bigint, targetPrice: bigint): bigint {
123
+ if (currentPrice === 0n) {
124
+ return MAX_FEE_ASSET_PRICE_MODIFIER_BPS;
125
+ }
126
+
127
+ // Calculate percentage difference in basis points
128
+ // modifierBps = ((targetPrice - currentPrice) / currentPrice) * 10000
129
+ const diff = targetPrice - currentPrice;
130
+ const rawModifierBps = (diff * 10_000n) / currentPrice;
131
+
132
+ // Clamp to ±MAX_FEE_ASSET_PRICE_MODIFIER_BPS
133
+ if (rawModifierBps > MAX_FEE_ASSET_PRICE_MODIFIER_BPS) {
134
+ return MAX_FEE_ASSET_PRICE_MODIFIER_BPS;
135
+ }
136
+ if (rawModifierBps < -MAX_FEE_ASSET_PRICE_MODIFIER_BPS) {
137
+ return -MAX_FEE_ASSET_PRICE_MODIFIER_BPS;
138
+ }
139
+ return rawModifierBps;
140
+ }
141
+ }
142
+
143
+ /** Mainnet Uniswap V4 StateView contract address */
144
+ export const STATE_VIEW_ADDRESS = EthAddress.fromString('0x7ffe42c4a5deea5b0fec41c94c136cf115597227');
145
+
146
+ const PRECISION_Q192 = 10n ** 12n * 2n ** 192n;
147
+
148
+ /**
149
+ * Converts Uniswap's sqrtPriceX96 directly to ETH per FeeAsset (E12).
150
+ *
151
+ * For an ETH/FeeAsset pool where ETH is currency0 and FeeAsset is currency1:
152
+ * - Uniswap's sqrtPriceX96 = sqrt(FeeAsset/ETH) * 2^96
153
+ * - We need: ETH/FeeAsset with 1e12 precision
154
+ *
155
+ * Math:
156
+ * price = (sqrtPriceX96 / 2^96)^2 = sqrtPriceX96^2 / 2^192 (FeeAsset per ETH)
157
+ * ethPerFeeAsset = 1 / price = 2^192 / sqrtPriceX96^2
158
+ * ethPerFeeAssetE12 = ethPerFeeAsset * 1e12 = 1e12 * 2^192 / sqrtPriceX96^2
159
+ */
160
+ export function sqrtPriceX96ToEthPerFeeAssetE12(sqrtPriceX96: bigint): bigint {
161
+ if (sqrtPriceX96 === 0n) {
162
+ throw new Error('Cannot convert zero sqrtPriceX96');
163
+ }
164
+ return PRECISION_Q192 / (sqrtPriceX96 * sqrtPriceX96);
165
+ }
166
+ /**
167
+ * Uniswap V4 StateView ABI - only the functions we need
168
+ */
169
+ const StateViewAbi = [
170
+ {
171
+ type: 'function',
172
+ name: 'getSlot0',
173
+ inputs: [{ name: 'poolId', type: 'bytes32', internalType: 'PoolId' }],
174
+ outputs: [
175
+ { name: 'sqrtPriceX96', type: 'uint160', internalType: 'uint160' },
176
+ { name: 'tick', type: 'int24', internalType: 'int24' },
177
+ { name: 'protocolFee', type: 'uint24', internalType: 'uint24' },
178
+ { name: 'lpFee', type: 'uint24', internalType: 'uint24' },
179
+ ],
180
+ stateMutability: 'view',
181
+ },
182
+ ] as const;
183
+
184
+ /**
185
+ * Client for querying the ETH/FeeAsset price from Uniswap V4.
186
+ * Returns prices in ETH per FeeAsset format (E12) to match the rollup contract.
187
+ */
188
+ class UniswapPriceOracle {
189
+ private readonly stateView;
190
+ private readonly poolId: Hex;
191
+ private readonly log: Logger;
192
+
193
+ constructor(
194
+ private readonly client: ViemClient,
195
+ log?: Logger,
196
+ ) {
197
+ this.log = log ?? createLogger('uniswap-price-oracle');
198
+ this.stateView = getContract({
199
+ address: STATE_VIEW_ADDRESS.toString(),
200
+ abi: StateViewAbi,
201
+ client,
202
+ });
203
+ this.poolId = this.computePoolId();
204
+ this.log.debug(`Initialized UniswapPriceOracle with poolId: ${this.poolId}`);
205
+ }
206
+
207
+ /**
208
+ * Computes the PoolId from the pool configuration by hashing its components.
209
+ * PoolId = keccak256(abi.encode(currency0, currency1, fee, tickSpacing, hooks))
210
+ * For mainnet, the value is expected to be: 0xce2899b16743cfd5a954d8122d5e07f410305b1aebee39fd73d9f3b9ebf10c2f
211
+ * Derived anyway to make it simpler to change if needed.
212
+ */
213
+ @memoize
214
+ computePoolId(): Hex {
215
+ /** ETH/FeeAsset pool configuration (hardcoded for mainnet) */
216
+ const encoded = encodeAbiParameters(parseAbiParameters('address, address, uint24, int24, address'), [
217
+ EthAddress.ZERO.toString(),
218
+ EthAddress.fromString('0xA27EC0006e59f245217Ff08CD52A7E8b169E62D2').toString(),
219
+ 500, // 0.05%
220
+ 10,
221
+ EthAddress.fromString('0xd53006d1e3110fD319a79AEEc4c527a0d265E080').toString(),
222
+ ]);
223
+ return keccak256(encoded);
224
+ }
225
+
226
+ async isPoolInitialized(): Promise<boolean> {
227
+ const [sqrtPriceX96] = await this.stateView.read.getSlot0([this.poolId], undefined);
228
+ return sqrtPriceX96 !== 0n;
229
+ }
230
+
231
+ /**
232
+ * Gets the price as ETH per FeeAsset, scaled by 1e12.
233
+ * This is the format expected by the rollup contract.
234
+ *
235
+ * @param blockNumber - Optional block number to query at (defaults to latest)
236
+ */
237
+ async getEthPerFeeAssetE12(blockNumber?: bigint): Promise<bigint> {
238
+ const [sqrtPriceX96] = await this.stateView.read.getSlot0(
239
+ [this.poolId],
240
+ blockNumber !== undefined ? { blockNumber } : undefined,
241
+ );
242
+ return sqrtPriceX96ToEthPerFeeAssetE12(sqrtPriceX96);
243
+ }
244
+
245
+ /**
246
+ * Gets the median price over the last N blocks as ETH per FeeAsset (E12).
247
+ * Using median helps protect against single-block manipulation.
248
+ *
249
+ * @param numBlocks - Number of recent blocks to sample (default: 5)
250
+ * @returns Median price as ETH per FeeAsset, scaled by 1e12
251
+ */
252
+ async getMeanEthPerFeeAssetE12(numBlocks: number = 5): Promise<bigint> {
253
+ const currentBlock = await this.client.getBlockNumber();
254
+ const prices: bigint[] = [];
255
+
256
+ for (let i = 0; i < numBlocks; i++) {
257
+ const blockNumber = currentBlock - BigInt(i);
258
+ if (blockNumber < 0n) {
259
+ break;
260
+ }
261
+
262
+ try {
263
+ const price = await this.getEthPerFeeAssetE12(blockNumber);
264
+ prices.push(price);
265
+ } catch (err) {
266
+ this.log.warn(`Failed to get price at block ${blockNumber}: ${err}`);
267
+ // Continue with fewer samples
268
+ }
269
+ }
270
+
271
+ const filteredPrices = prices.filter(price => price !== 0n);
272
+
273
+ if (filteredPrices.length === 0) {
274
+ throw new Error('Failed to get any price samples from Uniswap oracle');
275
+ }
276
+
277
+ const mean = filteredPrices.reduce((a, b) => a + b, 0n) / BigInt(filteredPrices.length);
278
+ return mean;
279
+ }
280
+ }
@@ -14,7 +14,7 @@ import {
14
14
  } from 'viem';
15
15
 
16
16
  import type { L1ContractAddresses } from '../l1_contract_addresses.js';
17
- import { createL1TxUtilsFromViemWallet } from '../l1_tx_utils/index.js';
17
+ import { createL1TxUtils } from '../l1_tx_utils/index.js';
18
18
  import { type ExtendedViemWalletClient, type ViemClient, isExtendedClient } from '../types.js';
19
19
 
20
20
  export type L1GovernanceContractAddresses = Pick<
@@ -194,7 +194,7 @@ export class GovernanceContract extends ReadOnlyGovernanceContract {
194
194
  retries: number;
195
195
  logger: Logger;
196
196
  }) {
197
- const l1TxUtils = createL1TxUtilsFromViemWallet(this.client, { logger });
197
+ const l1TxUtils = createL1TxUtils(this.client, { logger });
198
198
  const retryDelaySeconds = 12;
199
199
 
200
200
  voteAmount = voteAmount ?? (await this.getPowerForProposal(proposalId));
@@ -252,7 +252,7 @@ export class GovernanceContract extends ReadOnlyGovernanceContract {
252
252
  retries: number;
253
253
  logger: Logger;
254
254
  }) {
255
- const l1TxUtils = createL1TxUtilsFromViemWallet(this.client, { logger });
255
+ const l1TxUtils = createL1TxUtils(this.client, { logger });
256
256
  const retryDelaySeconds = 12;
257
257
  let success = false;
258
258
  for (let i = 0; i < retries; i++) {
@@ -110,6 +110,12 @@ export class GovernanceProposerContract implements IEmpireBase {
110
110
  };
111
111
  }
112
112
 
113
+ /** Checks if a payload was ever submitted to governance via submitRoundWinner. */
114
+ public async hasPayloadBeenProposed(payload: Hex, fromBlock: bigint): Promise<boolean> {
115
+ const events = await this.proposer.getEvents.PayloadSubmitted({ payload }, { fromBlock, strict: true });
116
+ return events.length > 0;
117
+ }
118
+
113
119
  public async submitRoundWinner(
114
120
  round: bigint,
115
121
  l1TxUtils: L1TxUtils,
@@ -1,6 +1,7 @@
1
1
  export * from './empire_base.js';
2
2
  export * from './errors.js';
3
3
  export * from './fee_asset_handler.js';
4
+ export * from './fee_asset_price_oracle.js';
4
5
  export * from './fee_juice.js';
5
6
  export * from './governance.js';
6
7
  export * from './governance_proposer.js';
@@ -391,20 +391,24 @@ export class RollupContract {
391
391
  slotDuration: number;
392
392
  epochDuration: number;
393
393
  proofSubmissionEpochs: number;
394
+ targetCommitteeSize: number;
394
395
  }> {
395
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
396
- this.getL1StartBlock(),
397
- this.getL1GenesisTime(),
398
- this.getSlotDuration(),
399
- this.getEpochDuration(),
400
- this.getProofSubmissionEpochs(),
401
- ]);
396
+ const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs, targetCommitteeSize] =
397
+ await Promise.all([
398
+ this.getL1StartBlock(),
399
+ this.getL1GenesisTime(),
400
+ this.getSlotDuration(),
401
+ this.getEpochDuration(),
402
+ this.getProofSubmissionEpochs(),
403
+ this.getTargetCommitteeSize(),
404
+ ]);
402
405
  return {
403
406
  l1StartBlock,
404
407
  l1GenesisTime,
405
408
  slotDuration,
406
409
  epochDuration: Number(epochDuration),
407
410
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
411
+ targetCommitteeSize,
408
412
  };
409
413
  }
410
414
 
@@ -23,7 +23,6 @@ import type { L1ContractsConfig } from './config.js';
23
23
  import { deployMulticall3 } from './contracts/multicall.js';
24
24
  import { RollupContract } from './contracts/rollup.js';
25
25
  import type { L1ContractAddresses } from './l1_contract_addresses.js';
26
- import type { L1TxUtilsConfig } from './l1_tx_utils/config.js';
27
26
  import type { ExtendedViemWalletClient } from './types.js';
28
27
 
29
28
  const logger = createLogger('ethereum:deploy_aztec_l1_contracts');
@@ -31,9 +30,9 @@ const logger = createLogger('ethereum:deploy_aztec_l1_contracts');
31
30
  const JSON_DEPLOY_RESULT_PREFIX = 'JSON DEPLOY RESULT:';
32
31
 
33
32
  /**
34
- * Runs a process with the given command, arguments, and environment.
35
- * If the process outputs a line starting with JSON_DEPLOY_RESULT_PREFIX,
36
- * the JSON is parsed and returned.
33
+ * Runs a process and parses JSON deploy results from stdout.
34
+ * Lines starting with JSON_DEPLOY_RESULT_PREFIX are parsed and returned.
35
+ * All other stdout goes to logger.info, stderr goes to logger.warn.
37
36
  */
38
37
  function runProcess<T>(
39
38
  command: string,
@@ -49,26 +48,41 @@ function runProcess<T>(
49
48
  });
50
49
 
51
50
  let result: T | undefined;
51
+ let parseError: Error | undefined;
52
+ let settled = false;
52
53
 
53
54
  readline.createInterface({ input: proc.stdout }).on('line', line => {
54
55
  const trimmedLine = line.trim();
55
56
  if (trimmedLine.startsWith(JSON_DEPLOY_RESULT_PREFIX)) {
56
57
  const jsonStr = trimmedLine.slice(JSON_DEPLOY_RESULT_PREFIX.length).trim();
57
- // TODO(AD): should this be a zod parse?
58
- result = JSON.parse(jsonStr);
58
+ try {
59
+ result = JSON.parse(jsonStr);
60
+ } catch {
61
+ parseError = new Error(`Failed to parse deploy result JSON: ${jsonStr.slice(0, 200)}`);
62
+ }
59
63
  } else {
60
64
  logger.info(line);
61
65
  }
62
66
  });
63
- readline.createInterface({ input: proc.stderr }).on('line', logger.error.bind(logger));
67
+ readline.createInterface({ input: proc.stderr }).on('line', logger.warn.bind(logger));
64
68
 
65
69
  proc.on('error', error => {
70
+ if (settled) {
71
+ return;
72
+ }
73
+ settled = true;
66
74
  reject(new Error(`Failed to spawn ${command}: ${error.message}`));
67
75
  });
68
76
 
69
77
  proc.on('close', code => {
78
+ if (settled) {
79
+ return;
80
+ }
81
+ settled = true;
70
82
  if (code !== 0) {
71
- reject(new Error(`${command} exited with code ${code}. See logs for details.\n`));
83
+ reject(new Error(`${command} exited with code ${code}`));
84
+ } else if (parseError) {
85
+ reject(parseError);
72
86
  } else {
73
87
  resolve(result);
74
88
  }
@@ -321,11 +335,8 @@ export async function deployAztecL1Contracts(
321
335
  );
322
336
  }
323
337
 
324
- // From heuristic testing. More caused issues with anvil.
325
- const MAGIC_ANVIL_BATCH_SIZE = 8;
326
- // Anvil seems to stall with unbounded batch size. Otherwise no max batch size is desirable.
338
+ const scriptPath = join(getL1ContractsPath(), 'scripts', 'forge_broadcast.js');
327
339
  const forgeArgs = [
328
- 'script',
329
340
  FORGE_SCRIPT,
330
341
  '--sig',
331
342
  'run()',
@@ -333,9 +344,6 @@ export async function deployAztecL1Contracts(
333
344
  privateKey,
334
345
  '--rpc-url',
335
346
  rpcUrl,
336
- '--broadcast',
337
- '--batch-size',
338
- MAGIC_ANVIL_BATCH_SIZE.toString(),
339
347
  ...(shouldVerify ? ['--verify'] : []),
340
348
  ];
341
349
  const forgeEnv = {
@@ -344,7 +352,12 @@ export async function deployAztecL1Contracts(
344
352
  FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
345
353
  ...getDeployAztecL1ContractsEnvVars(args),
346
354
  };
347
- const result = await runProcess<ForgeL1ContractsDeployResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
355
+ const result = await runProcess<ForgeL1ContractsDeployResult>(
356
+ process.execPath,
357
+ [scriptPath, ...forgeArgs],
358
+ forgeEnv,
359
+ l1ContractsPath,
360
+ );
348
361
  if (!result) {
349
362
  throw new Error('Forge script did not output deployment result');
350
363
  }
@@ -477,7 +490,25 @@ export type VerificationRecord = {
477
490
  libraries: VerificationLibraryEntry[];
478
491
  };
479
492
 
480
- export interface DeployAztecL1ContractsArgs extends Omit<L1ContractsConfig, keyof L1TxUtilsConfig> {
493
+ export interface DeployAztecL1ContractsArgs
494
+ extends Omit<
495
+ L1ContractsConfig,
496
+ | 'gasLimitBufferPercentage'
497
+ | 'maxGwei'
498
+ | 'maxBlobGwei'
499
+ | 'priorityFeeBumpPercentage'
500
+ | 'priorityFeeRetryBumpPercentage'
501
+ | 'minimumPriorityFeePerGas'
502
+ | 'maxSpeedUpAttempts'
503
+ | 'checkIntervalMs'
504
+ | 'stallTimeMs'
505
+ | 'txTimeoutMs'
506
+ | 'cancelTxOnTimeout'
507
+ | 'txCancellationFinalTimeoutMs'
508
+ | 'txUnseenConsideredDroppedMs'
509
+ | 'enableDelayer'
510
+ | 'txDelayerMaxInclusionTimeIntoSlot'
511
+ > {
481
512
  /** The vk tree root. */
482
513
  vkTreeRoot: Fr;
483
514
  /** The hash of the protocol contracts. */
@@ -587,17 +618,8 @@ export const deployRollupForUpgrade = async (
587
618
  const FORGE_SCRIPT = 'script/deploy/DeployRollupForUpgrade.s.sol';
588
619
  await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId);
589
620
 
590
- const forgeArgs = [
591
- 'script',
592
- FORGE_SCRIPT,
593
- '--sig',
594
- 'run()',
595
- '--private-key',
596
- privateKey,
597
- '--rpc-url',
598
- rpcUrl,
599
- '--broadcast',
600
- ];
621
+ const scriptPath = join(getL1ContractsPath(), 'scripts', 'forge_broadcast.js');
622
+ const forgeArgs = [FORGE_SCRIPT, '--sig', 'run()', '--private-key', privateKey, '--rpc-url', rpcUrl];
601
623
  const forgeEnv = {
602
624
  FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
603
625
  // Env vars required by l1-contracts/script/deploy/RollupConfiguration.sol.
@@ -606,7 +628,12 @@ export const deployRollupForUpgrade = async (
606
628
  ...getDeployRollupForUpgradeEnvVars(args),
607
629
  };
608
630
 
609
- const result = await runProcess<ForgeRollupUpgradeResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
631
+ const result = await runProcess<ForgeRollupUpgradeResult>(
632
+ process.execPath,
633
+ [scriptPath, ...forgeArgs],
634
+ forgeEnv,
635
+ l1ContractsPath,
636
+ );
610
637
  if (!result) {
611
638
  throw new Error('Forge script did not output deployment result');
612
639
  }
@@ -24,7 +24,7 @@ import {
24
24
  } from './deploy_aztec_l1_contracts.js';
25
25
  import { RegisterNewRollupVersionPayloadArtifact } from './l1_artifacts.js';
26
26
  import { type L1TxUtilsConfig, getL1TxUtilsConfigEnvVars } from './l1_tx_utils/config.js';
27
- import { createL1TxUtilsFromViemWallet } from './l1_tx_utils/factory.js';
27
+ import { createL1TxUtils } from './l1_tx_utils/factory.js';
28
28
  import type { L1TxUtils } from './l1_tx_utils/l1_tx_utils.js';
29
29
  import type { GasPrice, L1TxConfig, L1TxRequest } from './l1_tx_utils/types.js';
30
30
  import type { ExtendedViemWalletClient } from './types.js';
@@ -46,7 +46,7 @@ export class L1Deployer {
46
46
  private createVerificationJson: boolean = false,
47
47
  ) {
48
48
  this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), { size: 32 }) : undefined;
49
- this.l1TxUtils = createL1TxUtilsFromViemWallet(
49
+ this.l1TxUtils = createL1TxUtils(
50
50
  this.client,
51
51
  { logger: this.logger, dateProvider },
52
52
  { ...this.txUtilsConfig, debugMaxGasLimit: acceleratedTestDeployments },
@@ -179,7 +179,7 @@ export async function deployL1Contract(
179
179
 
180
180
  if (!l1TxUtils) {
181
181
  const config = getL1TxUtilsConfigEnvVars();
182
- l1TxUtils = createL1TxUtilsFromViemWallet(
182
+ l1TxUtils = createL1TxUtils(
183
183
  extendedClient,
184
184
  { logger },
185
185
  { ...config, debugMaxGasLimit: acceleratedTestDeployments },