@aztec/ethereum 0.0.1-commit.321f6a9 → 0.0.1-commit.330febf
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/contracts/governance.d.ts +36 -11
- package/dest/contracts/governance.d.ts.map +1 -1
- package/dest/contracts/governance.js +54 -24
- package/dest/contracts/governance_proposer.d.ts +7 -8
- package/dest/contracts/governance_proposer.d.ts.map +1 -1
- package/dest/contracts/governance_proposer.js +7 -10
- package/dest/contracts/gse.d.ts +5 -1
- package/dest/contracts/gse.d.ts.map +1 -1
- package/dest/contracts/gse.js +9 -0
- package/dest/contracts/rollup.d.ts +9 -1
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +8 -0
- package/dest/contracts/slashing_proposer.d.ts +37 -6
- package/dest/contracts/slashing_proposer.d.ts.map +1 -1
- package/dest/contracts/slashing_proposer.js +447 -17
- package/dest/l1_artifacts.d.ts +24 -19
- package/dest/l1_artifacts.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/contracts/governance.ts +63 -20
- package/src/contracts/governance_proposer.ts +11 -15
- package/src/contracts/gse.ts +12 -0
- package/src/contracts/rollup.ts +10 -0
- package/src/contracts/slashing_proposer.ts +78 -15
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
|
|
16
16
|
import type { ViemClient } from '../types.js';
|
|
17
17
|
import { type IEmpireBase, encodeSignal, encodeSignalWithSignature, signSignalWithSig } from './empire_base.js';
|
|
18
|
-
import { ReadOnlyGovernanceContract, extractProposalIdFromLogs } from './governance.js';
|
|
18
|
+
import { type PayloadProposalStatus, ReadOnlyGovernanceContract, extractProposalIdFromLogs } from './governance.js';
|
|
19
19
|
|
|
20
20
|
export class GovernanceProposerContract implements IEmpireBase {
|
|
21
21
|
private readonly proposer: GetContractReturnType<typeof GovernanceProposerAbi, ViemClient>;
|
|
@@ -38,16 +38,16 @@ export class GovernanceProposerContract implements IEmpireBase {
|
|
|
38
38
|
this.proposer = getContract({ address, abi: GovernanceProposerAbi, client });
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
public get address() {
|
|
41
|
+
public get address(): EthAddress {
|
|
42
42
|
return EthAddress.fromString(this.proposer.address);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
public async getRollupAddress() {
|
|
45
|
+
public async getRollupAddress(): Promise<EthAddress> {
|
|
46
46
|
return EthAddress.fromString(await this.proposer.read.getInstance());
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
@memoize
|
|
50
|
-
public async getRegistryAddress() {
|
|
50
|
+
public async getRegistryAddress(): Promise<EthAddress> {
|
|
51
51
|
return EthAddress.fromString(await this.proposer.read.REGISTRY());
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -59,10 +59,6 @@ export class GovernanceProposerContract implements IEmpireBase {
|
|
|
59
59
|
return this.proposer.read.ROUND_SIZE();
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
public getInstance() {
|
|
63
|
-
return this.proposer.read.getInstance();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
62
|
public computeRound(slot: SlotNumber): Promise<bigint> {
|
|
67
63
|
return this.proposer.read.computeRound([BigInt(slot)]);
|
|
68
64
|
}
|
|
@@ -107,7 +103,7 @@ export class GovernanceProposerContract implements IEmpireBase {
|
|
|
107
103
|
signer,
|
|
108
104
|
payload,
|
|
109
105
|
slot,
|
|
110
|
-
await this.
|
|
106
|
+
(await this.getRollupAddress()).toString(),
|
|
111
107
|
this.address.toString(),
|
|
112
108
|
chainId,
|
|
113
109
|
);
|
|
@@ -130,15 +126,15 @@ export class GovernanceProposerContract implements IEmpireBase {
|
|
|
130
126
|
}
|
|
131
127
|
|
|
132
128
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* implements the actual sweep against the Governance contract -- this method exists only as
|
|
136
|
-
* convenience wrapper so callers that already hold a GovernanceProposer reference don't have to
|
|
129
|
+
* Classifies the given original payload against the Governance proposal history (`'live'` /
|
|
130
|
+
* `'executed'` / `'none'`). Delegates to `ReadOnlyGovernanceContract.getPayloadProposalStatus`,
|
|
131
|
+
* which implements the actual sweep against the Governance contract -- this method exists only as
|
|
132
|
+
* a convenience wrapper so callers that already hold a GovernanceProposer reference don't have to
|
|
137
133
|
* resolve the Governance address themselves.
|
|
138
134
|
*/
|
|
139
|
-
public async
|
|
135
|
+
public async getPayloadProposalStatus(payload: Hex): Promise<PayloadProposalStatus> {
|
|
140
136
|
const governance = await this.getGovernance();
|
|
141
|
-
return governance.
|
|
137
|
+
return governance.getPayloadProposalStatus(payload);
|
|
142
138
|
}
|
|
143
139
|
|
|
144
140
|
/**
|
package/src/contracts/gse.ts
CHANGED
|
@@ -59,6 +59,18 @@ export class GSEContract {
|
|
|
59
59
|
return this.gse.read.getAttestersFromIndicesAtTime([instance, ts, indices], options);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** Number of attesters registered to `instance` as of the given L1 timestamp. */
|
|
63
|
+
public async getAttesterCountAtTime(
|
|
64
|
+
instance: Hex | EthAddress,
|
|
65
|
+
ts: bigint,
|
|
66
|
+
options?: { blockNumber?: bigint },
|
|
67
|
+
): Promise<number> {
|
|
68
|
+
if (instance instanceof EthAddress) {
|
|
69
|
+
instance = instance.toString();
|
|
70
|
+
}
|
|
71
|
+
return Number(await this.gse.read.getAttesterCountAtTime([instance, ts], options));
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
public async getRegistrationDigest(publicKey: ProjPointType<bigint>): Promise<ProjPointType<bigint>> {
|
|
63
75
|
const affinePublicKey = publicKey.toAffine();
|
|
64
76
|
const g1PointDigest = await this.gse.read.getRegistrationDigest([{ x: affinePublicKey.x, y: affinePublicKey.y }]);
|
package/src/contracts/rollup.ts
CHANGED
|
@@ -560,6 +560,16 @@ export class RollupContract {
|
|
|
560
560
|
return Number(await this.rollup.read.getActiveAttesterCount(options));
|
|
561
561
|
}
|
|
562
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Number of attesters that were staked at the given (historical) L1 timestamp. This is the count that
|
|
565
|
+
* validator-set sampling uses to decide whether an epoch gets a committee, and the historical counterpart
|
|
566
|
+
* of {@link getActiveAttesterCount} (which reads at the latest L1 block).
|
|
567
|
+
*/
|
|
568
|
+
async getAttesterCountAtTime(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<number> {
|
|
569
|
+
const gse = new GSEContract(this.client, await this.getGSE());
|
|
570
|
+
return gse.getAttesterCountAtTime(this.address, timestamp, options);
|
|
571
|
+
}
|
|
572
|
+
|
|
563
573
|
public async getSlashingProposerAddress() {
|
|
564
574
|
const slasher = await this.getSlasherContract();
|
|
565
575
|
if (!slasher) {
|
|
@@ -3,6 +3,7 @@ import type { ViemClient } from '@aztec/ethereum/types';
|
|
|
3
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
|
+
import { memoize } from '@aztec/foundation/decorators';
|
|
6
7
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
7
8
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
8
9
|
import { hexToBuffer } from '@aztec/foundation/string';
|
|
@@ -25,6 +26,14 @@ import {
|
|
|
25
26
|
export class SlashingProposerContract {
|
|
26
27
|
private readonly contract: GetContractReturnType<typeof SlashingProposerAbi, ViemClient>;
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Slash target validators of the last round asked for. Safe to cache because a round's targets are the committees
|
|
31
|
+
* of epochs that had already ended when the round opened (see SLASH_OFFSET_IN_ROUNDS), and those committees are
|
|
32
|
+
* sampled from validator set and randao snapshots taken before the epoch started, so they cannot change while the
|
|
33
|
+
* round is live. The promise is cached rather than the result, so concurrent callers share one in-flight read.
|
|
34
|
+
*/
|
|
35
|
+
private slashTargetValidators: { round: bigint; validators: Promise<EthAddress[]> } | undefined;
|
|
36
|
+
|
|
28
37
|
constructor(
|
|
29
38
|
public readonly client: ViemClient,
|
|
30
39
|
address: Hex | EthAddress,
|
|
@@ -64,6 +73,8 @@ export class SlashingProposerContract {
|
|
|
64
73
|
return this.contract.read.EXECUTION_DELAY_IN_ROUNDS();
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
/** Returns the slash amounts for the three slash unit levels. Immutable on the contract, so memoized. */
|
|
77
|
+
@memoize
|
|
67
78
|
public getSlashingAmounts(): Promise<[bigint, bigint, bigint]> {
|
|
68
79
|
return Promise.all([
|
|
69
80
|
this.contract.read.SLASH_AMOUNT_SMALL(),
|
|
@@ -234,35 +245,70 @@ export class SlashingProposerContract {
|
|
|
234
245
|
};
|
|
235
246
|
}
|
|
236
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Returns the validators eligible to be voted against in a round, in the order votes encode them. Cached for the
|
|
250
|
+
* last round asked for: reading them runs a committee sampling simulation on L1, and every vote of a round decodes
|
|
251
|
+
* against the same list, so a caller walking a round's votes would otherwise repeat that call per vote. Only the
|
|
252
|
+
* last round is kept since callers move forward round by round.
|
|
253
|
+
*/
|
|
254
|
+
public getSlashTargetValidators(round: bigint): Promise<EthAddress[]> {
|
|
255
|
+
const cached = this.slashTargetValidators;
|
|
256
|
+
if (cached?.round === round) {
|
|
257
|
+
return cached.validators;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const entry = { round, validators: this.fetchSlashTargetValidators(round) };
|
|
261
|
+
this.slashTargetValidators = entry;
|
|
262
|
+
// A failed read must not stay cached, or every later vote of the round would replay the same rejection
|
|
263
|
+
entry.validators.catch(() => {
|
|
264
|
+
if (this.slashTargetValidators === entry) {
|
|
265
|
+
this.slashTargetValidators = undefined;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
return entry.validators;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private async fetchSlashTargetValidators(round: bigint): Promise<EthAddress[]> {
|
|
272
|
+
const { result } = await this.contract.simulate.getSlashTargetCommittees([round]);
|
|
273
|
+
return result.flat().map(validator => EthAddress.fromString(validator));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Returns the slash amount voted for each target validator by a single vote of a round.
|
|
278
|
+
* @param index - Position of the vote within the round, from 0 (inclusive) to the round's vote count (exclusive)
|
|
279
|
+
*/
|
|
280
|
+
public async getVoteAt(round: bigint, index: bigint): Promise<SlashVoteTarget[]> {
|
|
281
|
+
const [validators, vote, slashAmounts] = await Promise.all([
|
|
282
|
+
this.getSlashTargetValidators(round),
|
|
283
|
+
this.contract.read.getVotes([round, index]),
|
|
284
|
+
this.getSlashingAmounts(),
|
|
285
|
+
]);
|
|
286
|
+
return decodeVote(vote, validators, slashAmounts);
|
|
287
|
+
}
|
|
288
|
+
|
|
237
289
|
/** Returns the last vote emitted for a given round */
|
|
238
290
|
public async getLastVote(round: bigint) {
|
|
239
291
|
const { voteCount } = await this.getRound(round);
|
|
240
|
-
|
|
241
|
-
const vote = await this.contract.read.getVotes([round, voteCount - 1n]);
|
|
242
|
-
const decoded = decodeSlashConsensusVotes(hexToBuffer(vote));
|
|
243
|
-
const slashAmounts = await this.getSlashingAmounts();
|
|
244
|
-
return decoded
|
|
245
|
-
.map((units, i) => ({
|
|
246
|
-
validator: EthAddress.fromString(validators[i]),
|
|
247
|
-
slashAmount: slashAmounts[units - 1] ?? 0n,
|
|
248
|
-
}))
|
|
249
|
-
.filter(v => v.slashAmount > 0n);
|
|
292
|
+
return await this.getVoteAt(round, voteCount - 1n);
|
|
250
293
|
}
|
|
251
294
|
|
|
252
295
|
/**
|
|
253
296
|
* Listen for VoteCast events
|
|
254
|
-
* @param callback - Callback
|
|
297
|
+
* @param callback - Callback receiving the round, the vote's index within the round as accepted by `getVoteAt`,
|
|
298
|
+
* and the proposer that cast it
|
|
255
299
|
* @returns Unwatch function
|
|
256
300
|
*/
|
|
257
|
-
public listenToVoteCast(
|
|
301
|
+
public listenToVoteCast(
|
|
302
|
+
callback: (args: { round: bigint; voteIndex: bigint; proposer: string }) => void,
|
|
303
|
+
): () => void {
|
|
258
304
|
return this.contract.watchEvent.VoteCast(
|
|
259
305
|
{},
|
|
260
306
|
{
|
|
261
307
|
onLogs: logs => {
|
|
262
308
|
for (const log of logs) {
|
|
263
|
-
const { round, proposer } = log.args;
|
|
264
|
-
if (round !== undefined && proposer) {
|
|
265
|
-
callback({ round, proposer });
|
|
309
|
+
const { round, voteIndex, proposer } = log.args;
|
|
310
|
+
if (round !== undefined && voteIndex !== undefined && proposer) {
|
|
311
|
+
callback({ round, voteIndex, proposer });
|
|
266
312
|
}
|
|
267
313
|
}
|
|
268
314
|
},
|
|
@@ -294,6 +340,23 @@ export class SlashingProposerContract {
|
|
|
294
340
|
}
|
|
295
341
|
}
|
|
296
342
|
|
|
343
|
+
/**
|
|
344
|
+
* A validator targeted by a slashing vote, with the amount voted. The position is the validator's index in the
|
|
345
|
+
* round's flattened slash target committees — the unit the contract tallies quorum by. A validator sitting in
|
|
346
|
+
* several of the round's committees holds several positions, each with its own tally.
|
|
347
|
+
*/
|
|
348
|
+
export type SlashVoteTarget = { validator: EthAddress; slashAmount: bigint; position: number };
|
|
349
|
+
|
|
350
|
+
function decodeVote(vote: Hex, validators: EthAddress[], slashAmounts: [bigint, bigint, bigint]): SlashVoteTarget[] {
|
|
351
|
+
return decodeSlashConsensusVotes(hexToBuffer(vote))
|
|
352
|
+
.map((units, position) => ({
|
|
353
|
+
validator: validators[position],
|
|
354
|
+
slashAmount: slashAmounts[units - 1] ?? 0n,
|
|
355
|
+
position,
|
|
356
|
+
}))
|
|
357
|
+
.filter(v => v.slashAmount > 0n);
|
|
358
|
+
}
|
|
359
|
+
|
|
297
360
|
/**
|
|
298
361
|
* Decodes a Buffer containing slash votes back into an array of numbers.
|
|
299
362
|
* Each vote is represented as a 2-bit value (0, 1, 2, or 3) representing slashing units.
|