@aztec/ethereum 0.0.1-commit.ec5f612 → 0.0.1-commit.ec7ac5448
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client.d.ts +10 -2
- package/dest/client.d.ts.map +1 -1
- package/dest/client.js +13 -7
- package/dest/config.d.ts +8 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +11 -9
- package/dest/contracts/chain_state_override.d.ts +38 -0
- package/dest/contracts/chain_state_override.d.ts.map +1 -0
- package/dest/contracts/chain_state_override.js +100 -0
- package/dest/contracts/inbox.d.ts +3 -3
- package/dest/contracts/inbox.d.ts.map +1 -1
- package/dest/contracts/inbox.js +5 -6
- package/dest/contracts/index.d.ts +3 -3
- package/dest/contracts/index.d.ts.map +1 -1
- package/dest/contracts/index.js +2 -2
- package/dest/contracts/multicall.d.ts +51 -2
- package/dest/contracts/multicall.d.ts.map +1 -1
- package/dest/contracts/multicall.js +85 -0
- package/dest/contracts/registry.d.ts +3 -1
- package/dest/contracts/registry.d.ts.map +1 -1
- package/dest/contracts/registry.js +30 -1
- package/dest/contracts/rollup.d.ts +40 -16
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +200 -50
- package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +3 -4
- package/dest/contracts/slashing_proposer.d.ts.map +1 -0
- package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +13 -15
- package/dest/deploy_aztec_l1_contracts.d.ts +3 -6
- package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_aztec_l1_contracts.js +2 -4
- package/dest/generated/l1-contracts-defaults.d.ts +1 -1
- package/dest/generated/l1-contracts-defaults.js +1 -1
- package/dest/l1_artifacts.d.ts +8640 -15423
- package/dest/l1_artifacts.d.ts.map +1 -1
- package/dest/l1_artifacts.js +9 -24
- package/dest/l1_contract_addresses.d.ts +1 -5
- package/dest/l1_contract_addresses.d.ts.map +1 -1
- package/dest/l1_contract_addresses.js +0 -6
- package/dest/l1_reader.d.ts +3 -1
- package/dest/l1_reader.d.ts.map +1 -1
- package/dest/l1_reader.js +6 -1
- package/dest/l1_tx_utils/l1_tx_utils.d.ts +5 -1
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils.js +40 -21
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/readonly_l1_tx_utils.js +8 -4
- package/dest/publisher_manager.d.ts +21 -7
- package/dest/publisher_manager.d.ts.map +1 -1
- package/dest/publisher_manager.js +81 -7
- package/dest/queries.js +3 -3
- package/dest/test/chain_monitor.d.ts +22 -3
- package/dest/test/chain_monitor.d.ts.map +1 -1
- package/dest/test/chain_monitor.js +33 -2
- package/dest/test/eth_cheat_codes.d.ts +6 -4
- package/dest/test/eth_cheat_codes.d.ts.map +1 -1
- package/dest/test/eth_cheat_codes.js +6 -4
- package/dest/test/rollup_cheat_codes.d.ts +5 -1
- package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
- package/dest/test/rollup_cheat_codes.js +9 -1
- package/dest/test/start_anvil.d.ts +23 -3
- package/dest/test/start_anvil.d.ts.map +1 -1
- package/dest/test/start_anvil.js +143 -29
- package/dest/utils.d.ts +1 -1
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +16 -12
- package/package.json +5 -7
- package/src/client.ts +10 -2
- package/src/config.ts +21 -13
- package/src/contracts/chain_state_override.ts +147 -0
- package/src/contracts/inbox.ts +4 -4
- package/src/contracts/index.ts +2 -2
- package/src/contracts/multicall.ts +65 -1
- package/src/contracts/registry.ts +31 -1
- package/src/contracts/rollup.ts +222 -53
- package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +14 -16
- package/src/deploy_aztec_l1_contracts.ts +1 -5
- package/src/generated/l1-contracts-defaults.ts +1 -1
- package/src/l1_artifacts.ts +12 -35
- package/src/l1_contract_addresses.ts +0 -7
- package/src/l1_reader.ts +13 -1
- package/src/l1_tx_utils/l1_tx_utils.ts +31 -8
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +8 -4
- package/src/publisher_manager.ts +105 -10
- package/src/queries.ts +3 -3
- package/src/test/chain_monitor.ts +60 -3
- package/src/test/eth_cheat_codes.ts +6 -4
- package/src/test/rollup_cheat_codes.ts +11 -1
- package/src/test/start_anvil.ts +177 -29
- package/src/utils.ts +17 -14
- package/dest/contracts/empire_slashing_proposer.d.ts +0 -69
- package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
- package/dest/contracts/empire_slashing_proposer.js +0 -216
- package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
- package/src/contracts/empire_slashing_proposer.ts +0 -265
package/src/contracts/inbox.ts
CHANGED
|
@@ -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
|
|
86
|
-
async getMessageSentEventByHash(
|
|
87
|
-
const
|
|
88
|
-
return
|
|
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: {
|
package/src/contracts/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './chain_state_override.js';
|
|
1
2
|
export * from './empire_base.js';
|
|
2
3
|
export * from './errors.js';
|
|
3
4
|
export * from './fee_asset_handler.js';
|
|
@@ -12,6 +13,5 @@ export * from './multicall.js';
|
|
|
12
13
|
export * from './outbox.js';
|
|
13
14
|
export * from './registry.js';
|
|
14
15
|
export * from './rollup.js';
|
|
15
|
-
export * from './
|
|
16
|
-
export * from './tally_slashing_proposer.js';
|
|
16
|
+
export * from './slashing_proposer.js';
|
|
17
17
|
export * from './slasher_contract.js';
|
|
@@ -2,7 +2,7 @@ import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
|
2
2
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
3
3
|
import type { Logger } from '@aztec/foundation/log';
|
|
4
4
|
|
|
5
|
-
import { type EncodeFunctionDataParameters, type Hex, encodeFunctionData, multicall3Abi } from 'viem';
|
|
5
|
+
import { type Address, type EncodeFunctionDataParameters, type Hex, encodeFunctionData, multicall3Abi } from 'viem';
|
|
6
6
|
|
|
7
7
|
import type { L1BlobInputs, L1TxConfig, L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
|
|
8
8
|
import type { ExtendedViemWalletClient } from '../types.js';
|
|
@@ -11,6 +11,39 @@ import { RollupContract } from './rollup.js';
|
|
|
11
11
|
|
|
12
12
|
export const MULTI_CALL_3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11' as const;
|
|
13
13
|
|
|
14
|
+
/** ABI fragment for aggregate3Value — not included in viem's multicall3Abi. */
|
|
15
|
+
export const aggregate3ValueAbi = [
|
|
16
|
+
{
|
|
17
|
+
inputs: [
|
|
18
|
+
{
|
|
19
|
+
components: [
|
|
20
|
+
{ internalType: 'address', name: 'target', type: 'address' },
|
|
21
|
+
{ internalType: 'bool', name: 'allowFailure', type: 'bool' },
|
|
22
|
+
{ internalType: 'uint256', name: 'value', type: 'uint256' },
|
|
23
|
+
{ internalType: 'bytes', name: 'callData', type: 'bytes' },
|
|
24
|
+
],
|
|
25
|
+
internalType: 'struct Multicall3.Call3Value[]',
|
|
26
|
+
name: 'calls',
|
|
27
|
+
type: 'tuple[]',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
name: 'aggregate3Value',
|
|
31
|
+
outputs: [
|
|
32
|
+
{
|
|
33
|
+
components: [
|
|
34
|
+
{ internalType: 'bool', name: 'success', type: 'bool' },
|
|
35
|
+
{ internalType: 'bytes', name: 'returnData', type: 'bytes' },
|
|
36
|
+
],
|
|
37
|
+
internalType: 'struct Multicall3.Result[]',
|
|
38
|
+
name: 'returnData',
|
|
39
|
+
type: 'tuple[]',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
stateMutability: 'payable',
|
|
43
|
+
type: 'function',
|
|
44
|
+
},
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
14
47
|
export class Multicall3 {
|
|
15
48
|
static async forward(
|
|
16
49
|
requests: L1TxRequest[],
|
|
@@ -122,6 +155,37 @@ export class Multicall3 {
|
|
|
122
155
|
throw err;
|
|
123
156
|
}
|
|
124
157
|
}
|
|
158
|
+
|
|
159
|
+
/** Batch multiple value transfers into a single aggregate3Value call on Multicall3. */
|
|
160
|
+
static async forwardValue(calls: { to: Address; value: bigint }[], l1TxUtils: L1TxUtils, logger: Logger) {
|
|
161
|
+
const args = calls.map(c => ({
|
|
162
|
+
target: c.to,
|
|
163
|
+
allowFailure: false,
|
|
164
|
+
value: c.value,
|
|
165
|
+
callData: '0x' as Hex,
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
const data = encodeFunctionData({
|
|
169
|
+
abi: aggregate3ValueAbi,
|
|
170
|
+
functionName: 'aggregate3Value',
|
|
171
|
+
args: [args],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const totalValue = calls.reduce((sum, c) => sum + c.value, 0n);
|
|
175
|
+
|
|
176
|
+
logger.info(`Sending aggregate3Value with ${calls.length} calls`, { totalValue });
|
|
177
|
+
const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
|
|
178
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
179
|
+
data,
|
|
180
|
+
value: totalValue,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (receipt.status !== 'success') {
|
|
184
|
+
throw new Error(`aggregate3Value transaction reverted: ${receipt.transactionHash}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { receipt };
|
|
188
|
+
}
|
|
125
189
|
}
|
|
126
190
|
|
|
127
191
|
export async function deployMulticall3(l1Client: ExtendedViemWalletClient, logger: Logger) {
|
|
@@ -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
|
}
|
package/src/contracts/rollup.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
@@ -134,6 +128,14 @@ export type L1FeeData = {
|
|
|
134
128
|
blobFee: bigint;
|
|
135
129
|
};
|
|
136
130
|
|
|
131
|
+
/** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
|
|
132
|
+
export type ManaMinFeeComponents = {
|
|
133
|
+
sequencerCost: bigint;
|
|
134
|
+
proverCost: bigint;
|
|
135
|
+
congestionCost: bigint;
|
|
136
|
+
congestionMultiplier: bigint;
|
|
137
|
+
};
|
|
138
|
+
|
|
137
139
|
/**
|
|
138
140
|
* Reward configuration for the rollup
|
|
139
141
|
*/
|
|
@@ -204,6 +206,7 @@ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
|
|
|
204
206
|
|
|
205
207
|
export class RollupContract {
|
|
206
208
|
private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
|
|
209
|
+
private readonly logger = createLogger('ethereum:rollup');
|
|
207
210
|
|
|
208
211
|
private static cachedStfStorageSlot: Hex | undefined;
|
|
209
212
|
private cachedEscapeHatch?: {
|
|
@@ -259,34 +262,18 @@ export class RollupContract {
|
|
|
259
262
|
return this.rollup;
|
|
260
263
|
}
|
|
261
264
|
|
|
262
|
-
public async getSlashingProposer(): Promise<
|
|
263
|
-
EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
|
|
264
|
-
> {
|
|
265
|
+
public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
|
|
265
266
|
const slasher = await this.getSlasherContract();
|
|
266
267
|
if (!slasher) {
|
|
267
268
|
return undefined;
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
const proposerAddress = await slasher.getProposer();
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
type: 'function',
|
|
274
|
-
name: 'SLASHING_PROPOSER_TYPE',
|
|
275
|
-
inputs: [],
|
|
276
|
-
outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
|
|
277
|
-
stateMutability: 'view',
|
|
278
|
-
},
|
|
279
|
-
] as const;
|
|
280
|
-
|
|
281
|
-
const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
|
|
282
|
-
const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
|
|
283
|
-
if (proposerType === SlashingProposerType.Tally.valueOf()) {
|
|
284
|
-
return new TallySlashingProposerContract(this.client, proposerAddress);
|
|
285
|
-
} else if (proposerType === SlashingProposerType.Empire.valueOf()) {
|
|
286
|
-
return new EmpireSlashingProposerContract(this.client, proposerAddress);
|
|
287
|
-
} else {
|
|
288
|
-
throw new Error(`Unknown slashing proposer type: ${proposerType}`);
|
|
272
|
+
if (proposerAddress.isZero()) {
|
|
273
|
+
return undefined;
|
|
289
274
|
}
|
|
275
|
+
|
|
276
|
+
return new SlashingProposerContract(this.client, proposerAddress);
|
|
290
277
|
}
|
|
291
278
|
|
|
292
279
|
@memoize
|
|
@@ -379,6 +366,20 @@ export class RollupContract {
|
|
|
379
366
|
return Fr.fromString(await this.rollup.read.archiveAt([0n]));
|
|
380
367
|
}
|
|
381
368
|
|
|
369
|
+
@memoize
|
|
370
|
+
async getVkTreeRoot(): Promise<Fr> {
|
|
371
|
+
const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
|
|
372
|
+
const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
|
|
373
|
+
return Fr.fromString(value ?? '0x0');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@memoize
|
|
377
|
+
async getProtocolContractsHash(): Promise<Fr> {
|
|
378
|
+
const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
|
|
379
|
+
const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
|
|
380
|
+
return Fr.fromString(value ?? '0x0');
|
|
381
|
+
}
|
|
382
|
+
|
|
382
383
|
/**
|
|
383
384
|
* Returns rollup constants used for epoch queries.
|
|
384
385
|
* Return type is `L1RollupConstants` which is defined in stdlib,
|
|
@@ -392,16 +393,25 @@ export class RollupContract {
|
|
|
392
393
|
epochDuration: number;
|
|
393
394
|
proofSubmissionEpochs: number;
|
|
394
395
|
targetCommitteeSize: number;
|
|
396
|
+
rollupManaLimit: number;
|
|
395
397
|
}> {
|
|
396
|
-
const [
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
398
|
+
const [
|
|
399
|
+
l1StartBlock,
|
|
400
|
+
l1GenesisTime,
|
|
401
|
+
slotDuration,
|
|
402
|
+
epochDuration,
|
|
403
|
+
proofSubmissionEpochs,
|
|
404
|
+
targetCommitteeSize,
|
|
405
|
+
rollupManaLimit,
|
|
406
|
+
] = await Promise.all([
|
|
407
|
+
this.getL1StartBlock(),
|
|
408
|
+
this.getL1GenesisTime(),
|
|
409
|
+
this.getSlotDuration(),
|
|
410
|
+
this.getEpochDuration(),
|
|
411
|
+
this.getProofSubmissionEpochs(),
|
|
412
|
+
this.getTargetCommitteeSize(),
|
|
413
|
+
this.getManaLimit(),
|
|
414
|
+
]);
|
|
405
415
|
return {
|
|
406
416
|
l1StartBlock,
|
|
407
417
|
l1GenesisTime,
|
|
@@ -409,6 +419,7 @@ export class RollupContract {
|
|
|
409
419
|
epochDuration: Number(epochDuration),
|
|
410
420
|
proofSubmissionEpochs: Number(proofSubmissionEpochs),
|
|
411
421
|
targetCommitteeSize,
|
|
422
|
+
rollupManaLimit: Number(rollupManaLimit),
|
|
412
423
|
};
|
|
413
424
|
}
|
|
414
425
|
|
|
@@ -463,7 +474,11 @@ export class RollupContract {
|
|
|
463
474
|
|
|
464
475
|
const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
|
|
465
476
|
return isOpen;
|
|
466
|
-
} 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
|
+
});
|
|
467
482
|
return false;
|
|
468
483
|
}
|
|
469
484
|
}
|
|
@@ -503,8 +518,9 @@ export class RollupContract {
|
|
|
503
518
|
return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
|
|
504
519
|
}
|
|
505
520
|
|
|
506
|
-
async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
|
|
507
|
-
|
|
521
|
+
async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
522
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
523
|
+
return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
|
|
508
524
|
}
|
|
509
525
|
|
|
510
526
|
async getSlotNumber(): Promise<SlotNumber> {
|
|
@@ -745,14 +761,13 @@ export class RollupContract {
|
|
|
745
761
|
* timestamp of the next L1 block
|
|
746
762
|
* @throws otherwise
|
|
747
763
|
*/
|
|
748
|
-
public async
|
|
764
|
+
public async canProposeAt(
|
|
749
765
|
archive: Buffer,
|
|
750
766
|
account: `0x${string}` | Account,
|
|
751
|
-
|
|
752
|
-
|
|
767
|
+
timestamp: bigint,
|
|
768
|
+
stateOverride: StateOverride = [],
|
|
753
769
|
): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
|
|
754
|
-
const
|
|
755
|
-
const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
|
|
770
|
+
const timeOfNextL1Slot = timestamp;
|
|
756
771
|
const who = typeof account === 'string' ? account : account.address;
|
|
757
772
|
|
|
758
773
|
try {
|
|
@@ -764,7 +779,7 @@ export class RollupContract {
|
|
|
764
779
|
functionName: 'canProposeAtTime',
|
|
765
780
|
args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
|
|
766
781
|
account,
|
|
767
|
-
stateOverride
|
|
782
|
+
stateOverride,
|
|
768
783
|
});
|
|
769
784
|
|
|
770
785
|
return {
|
|
@@ -800,6 +815,151 @@ export class RollupContract {
|
|
|
800
815
|
];
|
|
801
816
|
}
|
|
802
817
|
|
|
818
|
+
/**
|
|
819
|
+
* Returns a state override that sets tempCheckpointLogs[checkpointNumber].feeHeader to the compressed fee header.
|
|
820
|
+
* Used when simulating a propose call where the parent checkpoint hasn't landed on L1 yet (pipelining).
|
|
821
|
+
*/
|
|
822
|
+
public async makeFeeHeaderOverride(checkpointNumber: CheckpointNumber, feeHeader: FeeHeader): Promise<StateOverride> {
|
|
823
|
+
const { epochDuration, proofSubmissionEpochs } = await this.getRollupConstants();
|
|
824
|
+
const roundaboutSize = BigInt(epochDuration * (proofSubmissionEpochs + 1) + 1);
|
|
825
|
+
const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
|
|
826
|
+
|
|
827
|
+
// tempCheckpointLogs is at offset 2 in RollupStore
|
|
828
|
+
const tempCheckpointLogsMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 2n;
|
|
829
|
+
|
|
830
|
+
// Solidity mapping slot: keccak256(abi.encode(key, baseSlot))
|
|
831
|
+
const structBaseSlot = hexToBigInt(
|
|
832
|
+
keccak256(
|
|
833
|
+
encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsMappingBase]),
|
|
834
|
+
),
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
// feeHeader is the 7th field (offset 6) in CompressedTempCheckpointLog
|
|
838
|
+
const feeHeaderSlot = structBaseSlot + 6n;
|
|
839
|
+
const compressed = RollupContract.compressFeeHeader(feeHeader);
|
|
840
|
+
|
|
841
|
+
return [
|
|
842
|
+
{
|
|
843
|
+
address: this.address,
|
|
844
|
+
stateDiff: [
|
|
845
|
+
{
|
|
846
|
+
slot: `0x${feeHeaderSlot.toString(16).padStart(64, '0')}`,
|
|
847
|
+
value: `0x${compressed.toString(16).padStart(64, '0')}`,
|
|
848
|
+
},
|
|
849
|
+
],
|
|
850
|
+
},
|
|
851
|
+
];
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Returns a state override that sets archives[checkpointNumber] to the given archive value.
|
|
856
|
+
* Used when simulating a canProposeAtTime call where the local archive differs from L1
|
|
857
|
+
* (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
|
|
858
|
+
*/
|
|
859
|
+
public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
|
|
860
|
+
const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
|
|
861
|
+
const archiveSlot = hexToBigInt(
|
|
862
|
+
keccak256(
|
|
863
|
+
encodeAbiParameters(
|
|
864
|
+
[{ type: 'uint256' }, { type: 'uint256' }],
|
|
865
|
+
[BigInt(checkpointNumber), archivesMappingBase],
|
|
866
|
+
),
|
|
867
|
+
),
|
|
868
|
+
);
|
|
869
|
+
return [
|
|
870
|
+
{
|
|
871
|
+
address: this.address,
|
|
872
|
+
stateDiff: [
|
|
873
|
+
{
|
|
874
|
+
slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
|
|
875
|
+
value: archive.toString(),
|
|
876
|
+
},
|
|
877
|
+
],
|
|
878
|
+
},
|
|
879
|
+
];
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
|
|
883
|
+
public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
|
|
884
|
+
type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
|
|
885
|
+
const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
|
|
886
|
+
for (const override of overrides) {
|
|
887
|
+
for (const entry of override) {
|
|
888
|
+
const key = entry.address.toLowerCase();
|
|
889
|
+
const existing = byAddress.get(key);
|
|
890
|
+
if (existing) {
|
|
891
|
+
existing.stateDiff.push(...(entry.stateDiff ?? []));
|
|
892
|
+
if (entry.balance !== undefined) {
|
|
893
|
+
existing.balance = entry.balance;
|
|
894
|
+
}
|
|
895
|
+
} else {
|
|
896
|
+
byAddress.set(key, {
|
|
897
|
+
address: entry.address,
|
|
898
|
+
balance: entry.balance,
|
|
899
|
+
stateDiff: [...(entry.stateDiff ?? [])],
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return [...byAddress.values()];
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
|
|
908
|
+
public static compressFeeHeader(feeHeader: FeeHeader): bigint {
|
|
909
|
+
const MASK_48_BITS = (1n << 48n) - 1n;
|
|
910
|
+
const MASK_64_BITS = (1n << 64n) - 1n;
|
|
911
|
+
const MASK_63_BITS = (1n << 63n) - 1n;
|
|
912
|
+
|
|
913
|
+
let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
|
|
914
|
+
value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
|
|
915
|
+
value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
|
|
916
|
+
value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
|
|
917
|
+
value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
|
|
918
|
+
value |= 1n << 255n; // preheat flag
|
|
919
|
+
return value;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/** Computes the fee header for a child checkpoint given parent fee header and child data.
|
|
923
|
+
* Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
|
|
924
|
+
public static computeChildFeeHeader(
|
|
925
|
+
parentFeeHeader: FeeHeader,
|
|
926
|
+
childManaUsed: bigint,
|
|
927
|
+
feeAssetPriceModifier: bigint,
|
|
928
|
+
manaTarget: bigint,
|
|
929
|
+
): FeeHeader {
|
|
930
|
+
const MIN_ETH_PER_FEE_ASSET = 100n;
|
|
931
|
+
const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
|
|
932
|
+
|
|
933
|
+
// excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
|
|
934
|
+
const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
|
|
935
|
+
const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
|
|
936
|
+
|
|
937
|
+
// ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
|
|
938
|
+
const parentPrice =
|
|
939
|
+
parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
|
|
940
|
+
let newPrice: bigint;
|
|
941
|
+
if (feeAssetPriceModifier >= 0n) {
|
|
942
|
+
newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
|
|
943
|
+
} else {
|
|
944
|
+
const absMod = -feeAssetPriceModifier;
|
|
945
|
+
newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
|
|
946
|
+
}
|
|
947
|
+
if (newPrice < MIN_ETH_PER_FEE_ASSET) {
|
|
948
|
+
newPrice = MIN_ETH_PER_FEE_ASSET;
|
|
949
|
+
}
|
|
950
|
+
if (newPrice > MAX_ETH_PER_FEE_ASSET) {
|
|
951
|
+
newPrice = MAX_ETH_PER_FEE_ASSET;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
return {
|
|
955
|
+
excessMana,
|
|
956
|
+
manaUsed: childManaUsed,
|
|
957
|
+
ethPerFeeAsset: newPrice,
|
|
958
|
+
congestionCost: 0n,
|
|
959
|
+
proverCost: 0n,
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
|
|
803
963
|
/** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
|
|
804
964
|
public buildInvalidateBadAttestationRequest(
|
|
805
965
|
checkpointNumber: CheckpointNumber,
|
|
@@ -848,8 +1008,18 @@ export class RollupContract {
|
|
|
848
1008
|
return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
|
|
849
1009
|
}
|
|
850
1010
|
|
|
851
|
-
getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean): Promise<bigint> {
|
|
852
|
-
return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset]);
|
|
1011
|
+
getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
|
|
1012
|
+
return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
|
|
1016
|
+
const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
|
|
1017
|
+
return {
|
|
1018
|
+
sequencerCost: result.sequencerCost,
|
|
1019
|
+
proverCost: result.proverCost,
|
|
1020
|
+
congestionCost: result.congestionCost,
|
|
1021
|
+
congestionMultiplier: result.congestionMultiplier,
|
|
1022
|
+
};
|
|
853
1023
|
}
|
|
854
1024
|
|
|
855
1025
|
async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
|
|
@@ -895,11 +1065,10 @@ export class RollupContract {
|
|
|
895
1065
|
return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
|
|
896
1066
|
}
|
|
897
1067
|
|
|
898
|
-
async getAttesters(): Promise<EthAddress[]> {
|
|
1068
|
+
async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
|
|
899
1069
|
const attesterSize = await this.getActiveAttesterCount();
|
|
900
1070
|
const gse = new GSEContract(this.client, await this.getGSE());
|
|
901
|
-
const ts = (await this.client.getBlock()).timestamp;
|
|
902
|
-
|
|
1071
|
+
const ts = timestamp ?? (await this.client.getBlock()).timestamp;
|
|
903
1072
|
const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
|
|
904
1073
|
const chunks = chunk(indices, 1000);
|
|
905
1074
|
|
|
@@ -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 {
|
|
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
|
|
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
|
|
26
|
-
private readonly contract: GetContractReturnType<typeof
|
|
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:
|
|
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(),
|
|
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(),
|
|
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:
|
|
162
|
+
abi: SlashingProposerAbi,
|
|
165
163
|
data: encodeFunctionData({
|
|
166
|
-
abi:
|
|
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: '
|
|
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:
|
|
210
|
+
abi: SlashingProposerAbi,
|
|
213
211
|
data: encodeFunctionData({
|
|
214
|
-
abi:
|
|
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([
|
|
228
|
+
abi: mergeAbis([SlashingProposerAbi, SlasherAbi]),
|
|
231
229
|
data: encodeFunctionData({
|
|
232
|
-
abi:
|
|
230
|
+
abi: SlashingProposerAbi,
|
|
233
231
|
functionName: 'executeRound',
|
|
234
232
|
args: [round, committees.map(c => c.map(addr => addr.toString()))],
|
|
235
233
|
}),
|