@aztec/ethereum 0.0.1-commit.54489865 → 0.0.1-commit.5914bae

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 (142) hide show
  1. package/dest/client.d.ts +10 -2
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +13 -7
  4. package/dest/config.d.ts +3 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +11 -3
  7. package/dest/contracts/empire_base.d.ts +3 -1
  8. package/dest/contracts/empire_base.d.ts.map +1 -1
  9. package/dest/contracts/empire_slashing_proposer.d.ts +3 -1
  10. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
  11. package/dest/contracts/empire_slashing_proposer.js +22 -15
  12. package/dest/contracts/fee_asset_handler.d.ts +1 -1
  13. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  14. package/dest/contracts/fee_asset_handler.js +2 -0
  15. package/dest/contracts/fee_asset_price_oracle.d.ts +101 -0
  16. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  17. package/dest/contracts/fee_asset_price_oracle.js +651 -0
  18. package/dest/contracts/governance.d.ts +1 -1
  19. package/dest/contracts/governance.d.ts.map +1 -1
  20. package/dest/contracts/governance.js +5 -3
  21. package/dest/contracts/governance_proposer.d.ts +3 -1
  22. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  23. package/dest/contracts/governance_proposer.js +13 -1
  24. package/dest/contracts/index.d.ts +2 -1
  25. package/dest/contracts/index.d.ts.map +1 -1
  26. package/dest/contracts/index.js +1 -0
  27. package/dest/contracts/multicall.d.ts +1 -1
  28. package/dest/contracts/multicall.d.ts.map +1 -1
  29. package/dest/contracts/multicall.js +2 -1
  30. package/dest/contracts/registry.d.ts +3 -1
  31. package/dest/contracts/registry.d.ts.map +1 -1
  32. package/dest/contracts/registry.js +30 -1
  33. package/dest/contracts/rollup.d.ts +22 -8
  34. package/dest/contracts/rollup.d.ts.map +1 -1
  35. package/dest/contracts/rollup.js +68 -12
  36. package/dest/contracts/tally_slashing_proposer.d.ts +1 -1
  37. package/dest/contracts/tally_slashing_proposer.d.ts.map +1 -1
  38. package/dest/contracts/tally_slashing_proposer.js +8 -1
  39. package/dest/deploy_aztec_l1_contracts.d.ts +2 -3
  40. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  41. package/dest/deploy_aztec_l1_contracts.js +37 -22
  42. package/dest/deploy_l1_contract.js +3 -3
  43. package/dest/generated/l1-contracts-defaults.d.ts +2 -2
  44. package/dest/generated/l1-contracts-defaults.js +2 -2
  45. package/dest/l1_artifacts.d.ts +1051 -42
  46. package/dest/l1_artifacts.d.ts.map +1 -1
  47. package/dest/l1_reader.d.ts +3 -1
  48. package/dest/l1_reader.d.ts.map +1 -1
  49. package/dest/l1_reader.js +6 -1
  50. package/dest/l1_tx_utils/config.d.ts +7 -1
  51. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  52. package/dest/l1_tx_utils/config.js +14 -1
  53. package/dest/l1_tx_utils/constants.d.ts +1 -1
  54. package/dest/l1_tx_utils/constants.js +2 -2
  55. package/dest/l1_tx_utils/factory.d.ts +18 -10
  56. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  57. package/dest/l1_tx_utils/factory.js +17 -7
  58. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
  59. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
  60. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
  61. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
  62. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
  63. package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
  64. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
  65. package/dest/l1_tx_utils/index-blobs.js +2 -2
  66. package/dest/l1_tx_utils/index.d.ts +2 -1
  67. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  68. package/dest/l1_tx_utils/index.js +1 -0
  69. package/dest/l1_tx_utils/l1_tx_utils.d.ts +18 -7
  70. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  71. package/dest/l1_tx_utils/l1_tx_utils.js +58 -42
  72. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  73. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  74. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +11 -7
  75. package/dest/l1_tx_utils/tx_delayer.d.ts +56 -0
  76. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  77. package/dest/{test → l1_tx_utils}/tx_delayer.js +62 -34
  78. package/dest/test/chain_monitor.d.ts +22 -3
  79. package/dest/test/chain_monitor.d.ts.map +1 -1
  80. package/dest/test/chain_monitor.js +33 -2
  81. package/dest/test/eth_cheat_codes.d.ts +6 -4
  82. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  83. package/dest/test/eth_cheat_codes.js +6 -4
  84. package/dest/test/index.d.ts +1 -3
  85. package/dest/test/index.d.ts.map +1 -1
  86. package/dest/test/index.js +0 -2
  87. package/dest/test/rollup_cheat_codes.d.ts +5 -1
  88. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  89. package/dest/test/rollup_cheat_codes.js +9 -1
  90. package/dest/test/start_anvil.d.ts +23 -3
  91. package/dest/test/start_anvil.d.ts.map +1 -1
  92. package/dest/test/start_anvil.js +143 -29
  93. package/dest/test/upgrade_utils.js +2 -2
  94. package/dest/utils.d.ts +2 -1
  95. package/dest/utils.d.ts.map +1 -1
  96. package/dest/utils.js +46 -0
  97. package/package.json +5 -5
  98. package/src/client.ts +10 -2
  99. package/src/config.ts +15 -2
  100. package/src/contracts/empire_base.ts +2 -0
  101. package/src/contracts/empire_slashing_proposer.ts +22 -27
  102. package/src/contracts/fee_asset_handler.ts +2 -0
  103. package/src/contracts/fee_asset_price_oracle.ts +280 -0
  104. package/src/contracts/governance.ts +5 -3
  105. package/src/contracts/governance_proposer.ts +10 -1
  106. package/src/contracts/index.ts +1 -0
  107. package/src/contracts/multicall.ts +5 -2
  108. package/src/contracts/registry.ts +31 -1
  109. package/src/contracts/rollup.ts +79 -16
  110. package/src/contracts/tally_slashing_proposer.ts +5 -1
  111. package/src/deploy_aztec_l1_contracts.ts +60 -29
  112. package/src/deploy_l1_contract.ts +3 -3
  113. package/src/generated/l1-contracts-defaults.ts +2 -2
  114. package/src/l1_reader.ts +13 -1
  115. package/src/l1_tx_utils/config.ts +20 -0
  116. package/src/l1_tx_utils/constants.ts +2 -2
  117. package/src/l1_tx_utils/factory.ts +31 -31
  118. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
  119. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
  120. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
  121. package/src/l1_tx_utils/index-blobs.ts +2 -2
  122. package/src/l1_tx_utils/index.ts +1 -0
  123. package/src/l1_tx_utils/l1_tx_utils.ts +60 -32
  124. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +11 -7
  125. package/src/{test → l1_tx_utils}/tx_delayer.ts +78 -50
  126. package/src/test/chain_monitor.ts +60 -3
  127. package/src/test/eth_cheat_codes.ts +6 -4
  128. package/src/test/index.ts +0 -2
  129. package/src/test/rollup_cheat_codes.ts +11 -1
  130. package/src/test/start_anvil.ts +177 -29
  131. package/src/test/upgrade_utils.ts +2 -2
  132. package/src/utils.ts +53 -0
  133. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  134. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  135. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  136. package/dest/test/delayed_tx_utils.d.ts +0 -13
  137. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  138. package/dest/test/delayed_tx_utils.js +0 -28
  139. package/dest/test/tx_delayer.d.ts +0 -36
  140. package/dest/test/tx_delayer.d.ts.map +0 -1
  141. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  142. package/src/test/delayed_tx_utils.ts +0 -52
@@ -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));
@@ -211,6 +211,7 @@ export class GovernanceContract extends ReadOnlyGovernanceContract {
211
211
 
212
212
  const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
213
213
  to: this.governanceContract.address,
214
+ abi: GovernanceAbi,
214
215
  data: encodedVoteData,
215
216
  });
216
217
 
@@ -251,7 +252,7 @@ export class GovernanceContract extends ReadOnlyGovernanceContract {
251
252
  retries: number;
252
253
  logger: Logger;
253
254
  }) {
254
- const l1TxUtils = createL1TxUtilsFromViemWallet(this.client, { logger });
255
+ const l1TxUtils = createL1TxUtils(this.client, { logger });
255
256
  const retryDelaySeconds = 12;
256
257
  let success = false;
257
258
  for (let i = 0; i < retries; i++) {
@@ -265,6 +266,7 @@ export class GovernanceContract extends ReadOnlyGovernanceContract {
265
266
 
266
267
  const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
267
268
  to: this.governanceContract.address,
269
+ abi: GovernanceAbi,
268
270
  data: encodedExecuteData,
269
271
  });
270
272
 
@@ -83,6 +83,7 @@ export class GovernanceProposerContract implements IEmpireBase {
83
83
  public createSignalRequest(payload: Hex): L1TxRequest {
84
84
  return {
85
85
  to: this.address.toString(),
86
+ abi: GovernanceProposerAbi,
86
87
  data: encodeSignal(payload),
87
88
  };
88
89
  }
@@ -104,10 +105,17 @@ export class GovernanceProposerContract implements IEmpireBase {
104
105
  );
105
106
  return {
106
107
  to: this.address.toString(),
108
+ abi: GovernanceProposerAbi,
107
109
  data: encodeSignalWithSignature(payload, signature),
108
110
  };
109
111
  }
110
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
+
111
119
  public async submitRoundWinner(
112
120
  round: bigint,
113
121
  l1TxUtils: L1TxUtils,
@@ -117,8 +125,9 @@ export class GovernanceProposerContract implements IEmpireBase {
117
125
  }> {
118
126
  const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
119
127
  to: this.address.toString(),
128
+ abi: GovernanceProposerAbi,
120
129
  data: encodeFunctionData({
121
- abi: this.proposer.abi,
130
+ abi: GovernanceProposerAbi,
122
131
  functionName: 'submitRoundWinner',
123
132
  args: [round],
124
133
  }),
@@ -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';
@@ -34,10 +34,13 @@ export class Multicall3 {
34
34
  };
35
35
 
36
36
  const encodedForwarderData = encodeFunctionData(forwarderFunctionData);
37
-
38
37
  try {
39
38
  const { receipt, state } = await l1TxUtils.sendAndMonitorTransaction(
40
- { to: MULTI_CALL_3_ADDRESS, data: encodedForwarderData },
39
+ {
40
+ to: MULTI_CALL_3_ADDRESS,
41
+ data: encodedForwarderData,
42
+ abi: multicall3Abi,
43
+ },
41
44
  gasConfig,
42
45
  blobConfig,
43
46
  );
@@ -3,7 +3,7 @@ import { createLogger } from '@aztec/foundation/log';
3
3
  import { RegistryAbi } from '@aztec/l1-artifacts/RegistryAbi';
4
4
  import { TestERC20Abi } from '@aztec/l1-artifacts/TestERC20Abi';
5
5
 
6
- import { type GetContractReturnType, type Hex, getContract } from 'viem';
6
+ import { type GetContractReturnType, type Hex, getAbiItem, getContract } from 'viem';
7
7
 
8
8
  import type { L1ContractAddresses } from '../l1_contract_addresses.js';
9
9
  import type { ViemClient } from '../types.js';
@@ -128,4 +128,34 @@ export class RegistryContract {
128
128
  public async getRewardDistributor(): Promise<EthAddress> {
129
129
  return EthAddress.fromString(await this.registry.read.getRewardDistributor());
130
130
  }
131
+
132
+ /** Returns the L1 timestamp at which the given rollup was registered via addRollup(). */
133
+ public async getCanonicalRollupRegistrationTimestamp(
134
+ rollupAddress: EthAddress,
135
+ fromBlock?: bigint,
136
+ ): Promise<bigint | undefined> {
137
+ const event = getAbiItem({ abi: RegistryAbi, name: 'CanonicalRollupUpdated' });
138
+ const start = fromBlock ?? 0n;
139
+ const latestBlock = await this.client.getBlockNumber();
140
+ const chunkSize = 1_000n;
141
+
142
+ for (let from = start; from <= latestBlock; from += chunkSize) {
143
+ const to = from + chunkSize - 1n > latestBlock ? latestBlock : from + chunkSize - 1n;
144
+ const logs = await this.client.getLogs({
145
+ address: this.address.toString(),
146
+ fromBlock: from,
147
+ toBlock: to,
148
+ strict: true,
149
+ event,
150
+ args: { instance: rollupAddress.toString() },
151
+ });
152
+
153
+ if (logs.length > 0) {
154
+ const block = await this.client.getBlock({ blockNumber: logs[0].blockNumber });
155
+ return block.timestamp;
156
+ }
157
+ }
158
+
159
+ return undefined;
160
+ }
131
161
  }
@@ -134,6 +134,14 @@ export type L1FeeData = {
134
134
  blobFee: bigint;
135
135
  };
136
136
 
137
+ /** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
138
+ export type ManaMinFeeComponents = {
139
+ sequencerCost: bigint;
140
+ proverCost: bigint;
141
+ congestionCost: bigint;
142
+ congestionMultiplier: bigint;
143
+ };
144
+
137
145
  /**
138
146
  * Reward configuration for the rollup
139
147
  */
@@ -193,10 +201,10 @@ export type CheckpointProposedArgs = {
193
201
  checkpointNumber: CheckpointNumber;
194
202
  archive: Fr;
195
203
  versionedBlobHashes: Buffer[];
196
- /** Hash of attestations. Undefined for older events (backwards compatibility). */
197
- attestationsHash?: Buffer32;
198
- /** Digest of the payload. Undefined for older events (backwards compatibility). */
199
- payloadDigest?: Buffer32;
204
+ /** Hash of attestations emitted in the CheckpointProposed event. */
205
+ attestationsHash: Buffer32;
206
+ /** Digest of the payload emitted in the CheckpointProposed event. */
207
+ payloadDigest: Buffer32;
200
208
  };
201
209
 
202
210
  /** Log type for CheckpointProposed events. */
@@ -379,6 +387,20 @@ export class RollupContract {
379
387
  return Fr.fromString(await this.rollup.read.archiveAt([0n]));
380
388
  }
381
389
 
390
+ @memoize
391
+ async getVkTreeRoot(): Promise<Fr> {
392
+ const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
393
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
394
+ return Fr.fromString(value ?? '0x0');
395
+ }
396
+
397
+ @memoize
398
+ async getProtocolContractsHash(): Promise<Fr> {
399
+ const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
400
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
401
+ return Fr.fromString(value ?? '0x0');
402
+ }
403
+
382
404
  /**
383
405
  * Returns rollup constants used for epoch queries.
384
406
  * Return type is `L1RollupConstants` which is defined in stdlib,
@@ -391,13 +413,25 @@ export class RollupContract {
391
413
  slotDuration: number;
392
414
  epochDuration: number;
393
415
  proofSubmissionEpochs: number;
416
+ targetCommitteeSize: number;
417
+ rollupManaLimit: number;
394
418
  }> {
395
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
419
+ const [
420
+ l1StartBlock,
421
+ l1GenesisTime,
422
+ slotDuration,
423
+ epochDuration,
424
+ proofSubmissionEpochs,
425
+ targetCommitteeSize,
426
+ rollupManaLimit,
427
+ ] = await Promise.all([
396
428
  this.getL1StartBlock(),
397
429
  this.getL1GenesisTime(),
398
430
  this.getSlotDuration(),
399
431
  this.getEpochDuration(),
400
432
  this.getProofSubmissionEpochs(),
433
+ this.getTargetCommitteeSize(),
434
+ this.getManaLimit(),
401
435
  ]);
402
436
  return {
403
437
  l1StartBlock,
@@ -405,6 +439,8 @@ export class RollupContract {
405
439
  slotDuration,
406
440
  epochDuration: Number(epochDuration),
407
441
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
442
+ targetCommitteeSize,
443
+ rollupManaLimit: Number(rollupManaLimit),
408
444
  };
409
445
  }
410
446
 
@@ -499,8 +535,9 @@ export class RollupContract {
499
535
  return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
500
536
  }
501
537
 
502
- async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
503
- return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber());
538
+ async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
539
+ await checkBlockTag(options?.blockNumber, this.client);
540
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
504
541
  }
505
542
 
506
543
  async getSlotNumber(): Promise<SlotNumber> {
@@ -741,14 +778,13 @@ export class RollupContract {
741
778
  * timestamp of the next L1 block
742
779
  * @throws otherwise
743
780
  */
744
- public async canProposeAtNextEthBlock(
781
+ public async canProposeAt(
745
782
  archive: Buffer,
746
783
  account: `0x${string}` | Account,
747
- slotDuration: number,
784
+ timestamp: bigint,
748
785
  opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
749
786
  ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
750
- const latestBlock = await this.client.getBlock();
751
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
787
+ const timeOfNextL1Slot = timestamp;
752
788
  const who = typeof account === 'string' ? account : account.address;
753
789
 
754
790
  try {
@@ -805,6 +841,7 @@ export class RollupContract {
805
841
  ): L1TxRequest {
806
842
  return {
807
843
  to: this.address,
844
+ abi: RollupAbi,
808
845
  data: encodeFunctionData({
809
846
  abi: RollupAbi,
810
847
  functionName: 'invalidateBadAttestation',
@@ -826,6 +863,7 @@ export class RollupContract {
826
863
  ): L1TxRequest {
827
864
  return {
828
865
  to: this.address,
866
+ abi: RollupAbi,
829
867
  data: encodeFunctionData({
830
868
  abi: RollupAbi,
831
869
  functionName: 'invalidateInsufficientAttestations',
@@ -846,6 +884,16 @@ export class RollupContract {
846
884
  return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
847
885
  }
848
886
 
887
+ async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
888
+ const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
889
+ return {
890
+ sequencerCost: result.sequencerCost,
891
+ proverCost: result.proverCost,
892
+ congestionCost: result.congestionCost,
893
+ congestionMultiplier: result.congestionMultiplier,
894
+ };
895
+ }
896
+
849
897
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
850
898
  return SlotNumber.fromBigInt(await this.rollup.read.getSlotAt([timestamp]));
851
899
  }
@@ -889,11 +937,10 @@ export class RollupContract {
889
937
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
890
938
  }
891
939
 
892
- async getAttesters(): Promise<EthAddress[]> {
940
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
893
941
  const attesterSize = await this.getActiveAttesterCount();
894
942
  const gse = new GSEContract(this.client, await this.getGSE());
895
- const ts = (await this.client.getBlock()).timestamp;
896
-
943
+ const ts = timestamp ?? (await this.client.getBlock()).timestamp;
897
944
  const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
898
945
  const chunks = chunk(indices, 1000);
899
946
 
@@ -959,6 +1006,7 @@ export class RollupContract {
959
1006
  setupEpoch(l1TxUtils: L1TxUtils) {
960
1007
  return l1TxUtils.sendAndMonitorTransaction({
961
1008
  to: this.address,
1009
+ abi: RollupAbi,
962
1010
  data: encodeFunctionData({
963
1011
  abi: RollupAbi,
964
1012
  functionName: 'setupEpoch',
@@ -970,6 +1018,7 @@ export class RollupContract {
970
1018
  vote(l1TxUtils: L1TxUtils, proposalId: bigint) {
971
1019
  return l1TxUtils.sendAndMonitorTransaction({
972
1020
  to: this.address,
1021
+ abi: RollupAbi,
973
1022
  data: encodeFunctionData({
974
1023
  abi: RollupAbi,
975
1024
  functionName: 'vote',
@@ -1052,8 +1101,22 @@ export class RollupContract {
1052
1101
  checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
1053
1102
  archive: Fr.fromString(log.args.archive!),
1054
1103
  versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
1055
- attestationsHash: log.args.attestationsHash ? Buffer32.fromString(log.args.attestationsHash) : undefined,
1056
- payloadDigest: log.args.payloadDigest ? Buffer32.fromString(log.args.payloadDigest) : undefined,
1104
+ attestationsHash: (() => {
1105
+ if (!log.args.attestationsHash) {
1106
+ throw new Error(
1107
+ `CheckpointProposed event missing attestationsHash for checkpoint ${log.args.checkpointNumber}`,
1108
+ );
1109
+ }
1110
+ return Buffer32.fromString(log.args.attestationsHash);
1111
+ })(),
1112
+ payloadDigest: (() => {
1113
+ if (!log.args.payloadDigest) {
1114
+ throw new Error(
1115
+ `CheckpointProposed event missing payloadDigest for checkpoint ${log.args.checkpointNumber}`,
1116
+ );
1117
+ }
1118
+ return Buffer32.fromString(log.args.payloadDigest);
1119
+ })(),
1057
1120
  },
1058
1121
  }));
1059
1122
  }
@@ -1,11 +1,12 @@
1
1
  import type { L1TxRequest } from '@aztec/ethereum/l1-tx-utils';
2
2
  import type { ViemClient } from '@aztec/ethereum/types';
3
- import { tryExtractEvent } from '@aztec/ethereum/utils';
3
+ import { mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
4
4
  import { SlotNumber } from '@aztec/foundation/branded-types';
5
5
  import { Buffer32 } from '@aztec/foundation/buffer';
6
6
  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
+ import { SlasherAbi } from '@aztec/l1-artifacts/SlasherAbi';
9
10
  import { TallySlashingProposerAbi } from '@aztec/l1-artifacts/TallySlashingProposerAbi';
10
11
 
11
12
  import {
@@ -160,6 +161,7 @@ export class TallySlashingProposerContract {
160
161
 
161
162
  return {
162
163
  to: this.contract.address,
164
+ abi: TallySlashingProposerAbi,
163
165
  data: encodeFunctionData({
164
166
  abi: TallySlashingProposerAbi,
165
167
  functionName: 'vote',
@@ -207,6 +209,7 @@ export class TallySlashingProposerContract {
207
209
  public buildVoteRequestWithSignature(votes: Hex, signature: { v: number; r: Hex; s: Hex }): L1TxRequest {
208
210
  return {
209
211
  to: this.contract.address,
212
+ abi: TallySlashingProposerAbi,
210
213
  data: encodeFunctionData({
211
214
  abi: TallySlashingProposerAbi,
212
215
  functionName: 'vote',
@@ -224,6 +227,7 @@ export class TallySlashingProposerContract {
224
227
  public buildExecuteRoundRequest(round: bigint, committees: EthAddress[][]): L1TxRequest {
225
228
  return {
226
229
  to: this.contract.address,
230
+ abi: mergeAbis([TallySlashingProposerAbi, SlasherAbi]),
227
231
  data: encodeFunctionData({
228
232
  abi: TallySlashingProposerAbi,
229
233
  functionName: 'executeRound',