@aztec/ethereum 0.0.1-commit.f2ce05ee → 0.0.1-commit.f5a9928
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 +27 -3
- package/dest/client.d.ts.map +1 -1
- package/dest/client.js +63 -8
- package/dest/config.d.ts +17 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +38 -10
- package/dest/contracts/chain_state_override.d.ts +78 -0
- package/dest/contracts/chain_state_override.d.ts.map +1 -0
- package/dest/contracts/chain_state_override.js +137 -0
- package/dest/contracts/fee_asset_price_oracle.d.ts +105 -0
- package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
- package/dest/contracts/fee_asset_price_oracle.js +656 -0
- package/dest/contracts/governance.d.ts +135 -35
- package/dest/contracts/governance.d.ts.map +1 -1
- package/dest/contracts/governance.js +229 -18
- package/dest/contracts/governance_proposer.d.ts +33 -2
- package/dest/contracts/governance_proposer.d.ts.map +1 -1
- package/dest/contracts/governance_proposer.js +54 -6
- package/dest/contracts/gse.d.ts +4 -2
- package/dest/contracts/gse.d.ts.map +1 -1
- package/dest/contracts/gse.js +2 -2
- package/dest/contracts/inbox.d.ts +3 -3
- package/dest/contracts/inbox.d.ts.map +1 -1
- package/dest/contracts/inbox.js +12 -7
- package/dest/contracts/index.d.ts +4 -3
- package/dest/contracts/index.d.ts.map +1 -1
- package/dest/contracts/index.js +3 -2
- package/dest/contracts/multicall.d.ts +127 -12
- package/dest/contracts/multicall.d.ts.map +1 -1
- package/dest/contracts/multicall.js +201 -94
- package/dest/contracts/outbox.d.ts +27 -4
- package/dest/contracts/outbox.d.ts.map +1 -1
- package/dest/contracts/outbox.js +34 -7
- 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 +5437 -34
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +448 -104
- package/dest/contracts/slashing_proposer.d.ts +171 -0
- package/dest/contracts/slashing_proposer.d.ts.map +1 -0
- package/dest/contracts/slashing_proposer.js +753 -0
- package/dest/contracts/watch_event.d.ts +36 -0
- package/dest/contracts/watch_event.d.ts.map +1 -0
- package/dest/contracts/watch_event.js +66 -0
- package/dest/deploy_aztec_l1_contracts.d.ts +5 -9
- package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_aztec_l1_contracts.js +48 -27
- package/dest/deploy_l1_contract.js +3 -3
- package/dest/foundry_binary.d.ts +13 -0
- package/dest/foundry_binary.d.ts.map +1 -0
- package/dest/foundry_binary.js +52 -0
- package/dest/generated/l1-contracts-defaults.d.ts +3 -3
- package/dest/generated/l1-contracts-defaults.js +3 -3
- package/dest/l1_artifacts.d.ts +15524 -15847
- package/dest/l1_artifacts.d.ts.map +1 -1
- package/dest/l1_artifacts.js +9 -24
- package/dest/l1_contract_addresses.d.ts +61 -65
- package/dest/l1_contract_addresses.d.ts.map +1 -1
- package/dest/l1_contract_addresses.js +101 -82
- package/dest/l1_reader.d.ts +5 -5
- package/dest/l1_reader.d.ts.map +1 -1
- package/dest/l1_reader.js +9 -7
- package/dest/l1_tx_utils/config.d.ts +7 -1
- package/dest/l1_tx_utils/config.d.ts.map +1 -1
- package/dest/l1_tx_utils/config.js +17 -4
- package/dest/l1_tx_utils/factory.d.ts +18 -10
- package/dest/l1_tx_utils/factory.d.ts.map +1 -1
- package/dest/l1_tx_utils/factory.js +17 -7
- package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
- package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
- package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
- package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
- package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
- package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
- package/dest/l1_tx_utils/index-blobs.js +2 -2
- package/dest/l1_tx_utils/index.d.ts +2 -1
- package/dest/l1_tx_utils/index.d.ts.map +1 -1
- package/dest/l1_tx_utils/index.js +1 -0
- package/dest/l1_tx_utils/l1_tx_utils.d.ts +26 -7
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils.js +156 -79
- 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 +57 -23
- package/dest/l1_tx_utils/tx_delayer.d.ts +58 -0
- package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
- package/dest/{test → l1_tx_utils}/tx_delayer.js +67 -37
- package/dest/publisher_manager.d.ts +34 -8
- package/dest/publisher_manager.d.ts.map +1 -1
- package/dest/publisher_manager.js +119 -8
- package/dest/queries.d.ts +13 -2
- package/dest/queries.d.ts.map +1 -1
- package/dest/queries.js +62 -3
- package/dest/test/blob_kzg_warmup.d.ts +19 -0
- package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
- package/dest/test/blob_kzg_warmup.js +64 -0
- package/dest/test/chain_monitor.d.ts +55 -4
- package/dest/test/chain_monitor.d.ts.map +1 -1
- package/dest/test/chain_monitor.js +104 -3
- package/dest/test/eth_cheat_codes.d.ts +31 -6
- package/dest/test/eth_cheat_codes.d.ts.map +1 -1
- package/dest/test/eth_cheat_codes.js +67 -44
- package/dest/test/index.d.ts +2 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +1 -2
- package/dest/test/recording_rpc_server.d.ts +18 -0
- package/dest/test/recording_rpc_server.d.ts.map +1 -0
- package/dest/test/recording_rpc_server.js +68 -0
- package/dest/test/rollup_cheat_codes.d.ts +60 -3
- package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
- package/dest/test/rollup_cheat_codes.js +86 -7
- 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 +182 -32
- package/dest/test/upgrade_utils.js +2 -2
- package/dest/utils.d.ts +9 -2
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +59 -24
- package/package.json +6 -8
- package/src/client.ts +72 -2
- package/src/config.ts +58 -14
- package/src/contracts/chain_state_override.ts +200 -0
- package/src/contracts/fee_asset_price_oracle.ts +285 -0
- package/src/contracts/governance.ts +313 -13
- package/src/contracts/governance_proposer.ts +58 -9
- package/src/contracts/gse.ts +7 -2
- package/src/contracts/inbox.ts +13 -5
- package/src/contracts/index.ts +3 -2
- package/src/contracts/multicall.ts +243 -105
- package/src/contracts/outbox.ts +42 -8
- package/src/contracts/registry.ts +31 -1
- package/src/contracts/rollup.ts +507 -109
- package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +119 -45
- package/src/contracts/watch_event.ts +110 -0
- package/src/deploy_aztec_l1_contracts.ts +75 -39
- package/src/deploy_l1_contract.ts +3 -3
- package/src/foundry_binary.ts +57 -0
- package/src/generated/l1-contracts-defaults.ts +3 -3
- package/src/l1_artifacts.ts +12 -35
- package/src/l1_contract_addresses.ts +120 -94
- package/src/l1_reader.ts +17 -10
- package/src/l1_tx_utils/config.ts +23 -3
- package/src/l1_tx_utils/factory.ts +31 -31
- package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
- package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
- package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
- package/src/l1_tx_utils/index-blobs.ts +2 -2
- package/src/l1_tx_utils/index.ts +1 -0
- package/src/l1_tx_utils/l1_tx_utils.ts +149 -64
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +57 -17
- package/src/{test → l1_tx_utils}/tx_delayer.ts +93 -56
- package/src/publisher_manager.ts +143 -12
- package/src/queries.ts +70 -4
- package/src/test/blob_kzg_warmup.ts +65 -0
- package/src/test/chain_monitor.ts +148 -3
- package/src/test/eth_cheat_codes.ts +63 -47
- package/src/test/index.ts +1 -2
- package/src/test/recording_rpc_server.ts +69 -0
- package/src/test/rollup_cheat_codes.ts +115 -6
- package/src/test/start_anvil.ts +212 -32
- package/src/test/upgrade_utils.ts +2 -2
- package/src/utils.ts +56 -29
- package/dest/contracts/empire_slashing_proposer.d.ts +0 -67
- package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
- package/dest/contracts/empire_slashing_proposer.js +0 -207
- package/dest/contracts/tally_slashing_proposer.d.ts +0 -140
- package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
- package/dest/contracts/tally_slashing_proposer.js +0 -320
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
- package/dest/test/delayed_tx_utils.d.ts +0 -13
- package/dest/test/delayed_tx_utils.d.ts.map +0 -1
- package/dest/test/delayed_tx_utils.js +0 -28
- package/dest/test/tx_delayer.d.ts +0 -36
- package/dest/test/tx_delayer.d.ts.map +0 -1
- package/src/contracts/empire_slashing_proposer.ts +0 -259
- package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
- package/src/test/delayed_tx_utils.ts +0 -52
|
@@ -14,15 +14,30 @@ import {
|
|
|
14
14
|
} from 'viem';
|
|
15
15
|
|
|
16
16
|
import type { L1ContractAddresses } from '../l1_contract_addresses.js';
|
|
17
|
-
import {
|
|
17
|
+
import { createL1TxUtils } from '../l1_tx_utils/index.js';
|
|
18
18
|
import { type ExtendedViemWalletClient, type ViemClient, isExtendedClient } from '../types.js';
|
|
19
19
|
|
|
20
|
+
// Minimal ABI for IProposerPayload (`l1-contracts/src/governance/interfaces/IProposerPayload.sol`).
|
|
21
|
+
// The GovernanceProposer wraps every original payload in a GSEPayload before submitting it to
|
|
22
|
+
// Governance, so `Proposal.payload` on the Governance contract is the wrapper address rather than
|
|
23
|
+
// the original. We only need `getOriginalPayload` to recover the underlying payload, so we inline
|
|
24
|
+
// the ABI here instead of generating a full artifact.
|
|
25
|
+
const ProposerPayloadAbi = [
|
|
26
|
+
{
|
|
27
|
+
type: 'function',
|
|
28
|
+
name: 'getOriginalPayload',
|
|
29
|
+
inputs: [],
|
|
30
|
+
outputs: [{ type: 'address' }],
|
|
31
|
+
stateMutability: 'view',
|
|
32
|
+
},
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
20
35
|
export type L1GovernanceContractAddresses = Pick<
|
|
21
36
|
L1ContractAddresses,
|
|
22
37
|
'governanceAddress' | 'rollupAddress' | 'registryAddress' | 'governanceProposerAddress'
|
|
23
38
|
>;
|
|
24
39
|
|
|
25
|
-
// NOTE: Must be kept in sync with
|
|
40
|
+
// NOTE: Must be kept in sync with IGovernance.ProposalState in l1-contracts
|
|
26
41
|
export enum ProposalState {
|
|
27
42
|
Pending,
|
|
28
43
|
Active,
|
|
@@ -30,10 +45,124 @@ export enum ProposalState {
|
|
|
30
45
|
Executable,
|
|
31
46
|
Rejected,
|
|
32
47
|
Executed,
|
|
48
|
+
Droppable,
|
|
33
49
|
Dropped,
|
|
34
50
|
Expired,
|
|
35
51
|
}
|
|
36
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Outcome of {@link ReadOnlyGovernanceContract.getPayloadProposalStatus} for a queried payload.
|
|
55
|
+
* - `'live'`: a proposal referencing the payload is still progressing (Pending/Active/Queued/
|
|
56
|
+
* Executable) or is `Droppable`, so signalling for it again is redundant or premature.
|
|
57
|
+
* - `'executed'`: no live proposal references the payload, but one was already executed within the
|
|
58
|
+
* bounded lookback (or a prior sweep observed the execution and memoized it).
|
|
59
|
+
* - `'none'`: no live or executed proposal references the payload within the bounded lookback.
|
|
60
|
+
*/
|
|
61
|
+
export type PayloadProposalStatus = 'live' | 'executed' | 'none';
|
|
62
|
+
|
|
63
|
+
/** Vote tallies on a single proposal. Both fields are mutated by `Governance.vote`. */
|
|
64
|
+
export interface Ballot {
|
|
65
|
+
yea: bigint;
|
|
66
|
+
nay: bigint;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Snapshot of the timing/quorum parameters that govern a single proposal's lifecycle. Each proposal
|
|
71
|
+
* stores its own copy at creation time (see `_propose` in Governance.sol), so this snapshot is
|
|
72
|
+
* immutable for the lifetime of the proposal even if the global `Configuration` changes later.
|
|
73
|
+
*/
|
|
74
|
+
export interface ProposalConfiguration {
|
|
75
|
+
votingDelay: bigint;
|
|
76
|
+
votingDuration: bigint;
|
|
77
|
+
executionDelay: bigint;
|
|
78
|
+
gracePeriod: bigint;
|
|
79
|
+
quorum: bigint;
|
|
80
|
+
requiredYeaMargin: bigint;
|
|
81
|
+
minimumVotes: bigint;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Parameters for `Governance.proposeWithLock`. Stored only in the global Configuration, never on a proposal. */
|
|
85
|
+
export interface ProposeWithLockConfiguration {
|
|
86
|
+
lockDelay: bigint;
|
|
87
|
+
lockAmount: bigint;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Live, mutable governance configuration. `proposeConfig` is the lock configuration used by
|
|
92
|
+
* `proposeWithLock`; the remaining fields are the same shape as `ProposalConfiguration` and are
|
|
93
|
+
* snapshotted onto each new proposal at creation time.
|
|
94
|
+
*/
|
|
95
|
+
export interface GovernanceConfiguration extends ProposalConfiguration {
|
|
96
|
+
proposeConfig: ProposeWithLockConfiguration;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A governance proposal augmented with its live (computed) state.
|
|
101
|
+
*
|
|
102
|
+
* Mutability:
|
|
103
|
+
* - `config`, `payload`, `proposer`, `creation` are immutable for the lifetime of the proposal.
|
|
104
|
+
* - `cachedState` is the raw value stored on-chain. It is only written when a proposal is explicitly
|
|
105
|
+
* executed or dropped, so for time-derived terminal states (Rejected, Expired) it remains at the
|
|
106
|
+
* value that was current when the proposal was created.
|
|
107
|
+
* - `state` is the computed state returned by `Governance.getProposalState`. This is the value
|
|
108
|
+
* callers almost always want -- it reflects time-derived transitions that `cachedState` does not.
|
|
109
|
+
* - `summedBallot` is mutated by every `Governance.vote` call while the proposal is Active.
|
|
110
|
+
*
|
|
111
|
+
* Once `state` is in a terminal phase (`Executed`/`Rejected`/`Dropped`/`Expired`) the entire struct
|
|
112
|
+
* is provably immutable on-chain (no further votes can be cast and no state-mutating call to
|
|
113
|
+
* `execute`/`dropProposal` can succeed), and the wrapper memoizes it.
|
|
114
|
+
*/
|
|
115
|
+
export interface Proposal {
|
|
116
|
+
config: ProposalConfiguration;
|
|
117
|
+
cachedState: ProposalState;
|
|
118
|
+
state: ProposalState;
|
|
119
|
+
payload: EthAddress;
|
|
120
|
+
proposer: EthAddress;
|
|
121
|
+
creation: bigint;
|
|
122
|
+
summedBallot: Ballot;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Set of `ProposalState` values for which a proposal is fully immutable on-chain. */
|
|
126
|
+
const TERMINAL_PROPOSAL_STATES: ReadonlySet<ProposalState> = new Set([
|
|
127
|
+
ProposalState.Executed,
|
|
128
|
+
ProposalState.Rejected,
|
|
129
|
+
ProposalState.Dropped,
|
|
130
|
+
ProposalState.Expired,
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
// Set of `ProposalState` values in which a proposal is still progressing towards execution.
|
|
134
|
+
// `Droppable` is deliberately excluded: it is neither live (it cannot progress on its own) nor
|
|
135
|
+
// terminal (it can resume its lifecycle if the governanceProposer is restored), so it is handled
|
|
136
|
+
// separately and is never memoized as immutable.
|
|
137
|
+
const LIVE_PROPOSAL_STATES: ReadonlySet<ProposalState> = new Set([
|
|
138
|
+
ProposalState.Pending,
|
|
139
|
+
ProposalState.Active,
|
|
140
|
+
ProposalState.Queued,
|
|
141
|
+
ProposalState.Executable,
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
// Hard upper bound on the wall-clock lifetime of any Governance proposal, in seconds.
|
|
145
|
+
// Each proposal stores its own snapshot of `ProposalConfiguration` at creation time and progresses
|
|
146
|
+
// through Pending -> Active -> Queued -> Executable using those frozen durations
|
|
147
|
+
// (see `ProposalLib.{pendingThrough,activeThrough,queuedThrough,executableThrough}`). Each of those
|
|
148
|
+
// four durations is bounded by `ConfigurationLib.TIME_UPPER = 90 days` (validated in
|
|
149
|
+
// `ConfigurationLib.assertValid`), so no proposal can be live for more than 4 * 90 days regardless
|
|
150
|
+
// of what config it was created under. Once past this point, the proposal is guaranteed to be in a
|
|
151
|
+
// terminal state (Executed / Rejected / Dropped / Expired).
|
|
152
|
+
export const MAX_PROPOSAL_LIFETIME_SECONDS = 4n * 90n * 24n * 3600n;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Validates a number returned by an on-chain `ProposalState` enum field and narrows it to the
|
|
156
|
+
* `ProposalState` enum. Throws if the value is out of range.
|
|
157
|
+
*/
|
|
158
|
+
function asProposalState(raw: number): ProposalState {
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
160
|
+
if (raw < 0 || raw > ProposalState.Expired) {
|
|
161
|
+
throw new Error(`Invalid proposal state: ${raw}`);
|
|
162
|
+
}
|
|
163
|
+
return raw as ProposalState;
|
|
164
|
+
}
|
|
165
|
+
|
|
37
166
|
export function extractProposalIdFromLogs(logs: Log[]): bigint {
|
|
38
167
|
const parsedLogs = parseEventLogs({
|
|
39
168
|
abi: GovernanceAbi,
|
|
@@ -50,6 +179,30 @@ export function extractProposalIdFromLogs(logs: Log[]): bigint {
|
|
|
50
179
|
export class ReadOnlyGovernanceContract {
|
|
51
180
|
protected readonly governanceContract: GetContractReturnType<typeof GovernanceAbi, ViemClient>;
|
|
52
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Cache of fully-resolved proposals keyed by id. We populate this lazily and only retain entries
|
|
184
|
+
* whose state is provably terminal -- once `cachedState` is `Executed` or `Dropped` the on-chain
|
|
185
|
+
* proposal struct is frozen and safe to memoize indefinitely. Other state transitions (e.g.
|
|
186
|
+
* Pending -> Active, or accumulating votes) leave the cache untouched and force a fresh fetch.
|
|
187
|
+
*/
|
|
188
|
+
private readonly proposalCache: Map<bigint, Proposal> = new Map();
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Cache of `IProposerPayload.getOriginalPayload()` results keyed by wrapper address. The wrapper
|
|
192
|
+
* contract's bytecode is immutable, so this mapping never changes -- a value of `undefined`
|
|
193
|
+
* encodes a proposal whose payload doesn't implement `getOriginalPayload` (e.g. proposeWithLock
|
|
194
|
+
* proposals) and should be treated as "no original".
|
|
195
|
+
*/
|
|
196
|
+
private readonly originalPayloadCache: Map<Hex, Hex | undefined> = new Map();
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Payloads (lowercased hex) observed as the subject of an `Executed` Governance proposal. Execution
|
|
200
|
+
* is immutable on-chain, so this verdict never expires within a process. It restores the
|
|
201
|
+
* `'executed'` classification for payloads whose executed proposal has since aged past the bounded
|
|
202
|
+
* lookback below. Lost on restart, at which point the bounded-lookback limitation applies again.
|
|
203
|
+
*/
|
|
204
|
+
private readonly executedPayloads: Set<string> = new Set();
|
|
205
|
+
|
|
53
206
|
constructor(
|
|
54
207
|
address: Hex,
|
|
55
208
|
public readonly client: ViemClient,
|
|
@@ -65,21 +218,168 @@ export class ReadOnlyGovernanceContract {
|
|
|
65
218
|
return EthAddress.fromString(await this.governanceContract.read.governanceProposer());
|
|
66
219
|
}
|
|
67
220
|
|
|
68
|
-
public getConfiguration() {
|
|
69
|
-
|
|
221
|
+
public async getConfiguration(): Promise<GovernanceConfiguration> {
|
|
222
|
+
const raw = await this.governanceContract.read.getConfiguration();
|
|
223
|
+
return {
|
|
224
|
+
proposeConfig: {
|
|
225
|
+
lockDelay: raw.proposeConfig.lockDelay,
|
|
226
|
+
lockAmount: raw.proposeConfig.lockAmount,
|
|
227
|
+
},
|
|
228
|
+
votingDelay: raw.votingDelay,
|
|
229
|
+
votingDuration: raw.votingDuration,
|
|
230
|
+
executionDelay: raw.executionDelay,
|
|
231
|
+
gracePeriod: raw.gracePeriod,
|
|
232
|
+
quorum: raw.quorum,
|
|
233
|
+
requiredYeaMargin: raw.requiredYeaMargin,
|
|
234
|
+
minimumVotes: raw.minimumVotes,
|
|
235
|
+
};
|
|
70
236
|
}
|
|
71
237
|
|
|
72
|
-
|
|
73
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Fetches a proposal by id together with its live state, returning the mapped {@link Proposal}
|
|
240
|
+
* type. Issues `getProposal` and `getProposalState` in parallel so the result carries both the
|
|
241
|
+
* raw stored `cachedState` and the time-derived `state` -- callers can use whichever they need
|
|
242
|
+
* without an extra round-trip.
|
|
243
|
+
*
|
|
244
|
+
* Backed by an in-memory cache that retains entries only when `state` is in one of the four
|
|
245
|
+
* terminal phases (`Executed` / `Rejected` / `Dropped` / `Expired`). At that point the entire
|
|
246
|
+
* proposal struct is provably immutable on-chain (no further votes can be cast and no
|
|
247
|
+
* state-mutating call can succeed), so caching is safe forever. Non-terminal states force a
|
|
248
|
+
* fresh fetch on every call so callers always see fresh `state` and `summedBallot` values.
|
|
249
|
+
*/
|
|
250
|
+
public async getProposal(proposalId: bigint): Promise<Proposal> {
|
|
251
|
+
const cached = this.proposalCache.get(proposalId);
|
|
252
|
+
if (cached !== undefined) {
|
|
253
|
+
return cached;
|
|
254
|
+
}
|
|
255
|
+
const [raw, rawState] = await Promise.all([
|
|
256
|
+
this.governanceContract.read.getProposal([proposalId]),
|
|
257
|
+
this.governanceContract.read.getProposalState([proposalId]),
|
|
258
|
+
]);
|
|
259
|
+
const proposal: Proposal = {
|
|
260
|
+
config: {
|
|
261
|
+
votingDelay: raw.config.votingDelay,
|
|
262
|
+
votingDuration: raw.config.votingDuration,
|
|
263
|
+
executionDelay: raw.config.executionDelay,
|
|
264
|
+
gracePeriod: raw.config.gracePeriod,
|
|
265
|
+
quorum: raw.config.quorum,
|
|
266
|
+
requiredYeaMargin: raw.config.requiredYeaMargin,
|
|
267
|
+
minimumVotes: raw.config.minimumVotes,
|
|
268
|
+
},
|
|
269
|
+
cachedState: asProposalState(raw.cachedState),
|
|
270
|
+
state: asProposalState(rawState),
|
|
271
|
+
payload: EthAddress.fromString(raw.payload),
|
|
272
|
+
proposer: EthAddress.fromString(raw.proposer),
|
|
273
|
+
creation: raw.creation,
|
|
274
|
+
summedBallot: { yea: raw.summedBallot.yea, nay: raw.summedBallot.nay },
|
|
275
|
+
};
|
|
276
|
+
if (TERMINAL_PROPOSAL_STATES.has(proposal.state)) {
|
|
277
|
+
this.proposalCache.set(proposalId, proposal);
|
|
278
|
+
}
|
|
279
|
+
return proposal;
|
|
74
280
|
}
|
|
75
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Returns the live state of a proposal as computed by `Governance.getProposalState`. Prefer
|
|
284
|
+
* {@link getProposal} when you also need any other proposal data -- it returns this same value
|
|
285
|
+
* via {@link Proposal.state} alongside the rest of the struct in a single round-trip.
|
|
286
|
+
*/
|
|
76
287
|
public async getProposalState(proposalId: bigint): Promise<ProposalState> {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
288
|
+
return asProposalState(await this.governanceContract.read.getProposalState([proposalId]));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public getProposalCount(): Promise<bigint> {
|
|
292
|
+
return this.governanceContract.read.proposalCount();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Classifies the given original payload against the Governance proposal history. Distinguishes a
|
|
297
|
+
* payload that is still the subject of a live proposal (`'live'`) from one whose proposal was
|
|
298
|
+
* already executed (`'executed'`) from one that has no relevant proposal (`'none'`), so callers can
|
|
299
|
+
* stop re-signalling an executed payload while still re-signalling one whose proposal was merely
|
|
300
|
+
* rejected/dropped/expired (each GovernanceProposer round is independent and there is no
|
|
301
|
+
* payload-level uniqueness check on-chain).
|
|
302
|
+
*
|
|
303
|
+
* A proposal matches the payload either directly (its stored `payload` equals the target, as for
|
|
304
|
+
* `proposeWithLock` proposals) or via its `GSEPayload` wrapper unwrapping to the target. `'live'`
|
|
305
|
+
* (including `Droppable`) takes precedence over `'executed'`, so a payload re-submitted while a
|
|
306
|
+
* prior execution is still in the lookback window reads as `'live'`.
|
|
307
|
+
*
|
|
308
|
+
* Implemented as a bounded view-call sweep over `Governance.proposals` rather than an event scan,
|
|
309
|
+
* because `eth_getLogs` over the full deployment history of a long-lived rollup exceeds typical
|
|
310
|
+
* RPC block-range caps. The number of proposals (`proposalCount`) is small in practice, and we
|
|
311
|
+
* walk newest -> oldest with a hard early-stop on the protocol-wide proposal lifetime cap. This
|
|
312
|
+
* makes the `'executed'` verdict a *bounded lookback* rather than permanent suppression: a proposal
|
|
313
|
+
* executed longer ago than the lifetime cap is only reported once it has been observed and
|
|
314
|
+
* memoized in-process (memo is lost on restart).
|
|
315
|
+
*/
|
|
316
|
+
public async getPayloadProposalStatus(payload: Hex): Promise<PayloadProposalStatus> {
|
|
317
|
+
const target = payload.toLowerCase() as Hex;
|
|
318
|
+
|
|
319
|
+
const proposalCount = await this.getProposalCount();
|
|
320
|
+
if (proposalCount === 0n) {
|
|
321
|
+
return this.executedPayloads.has(target) ? 'executed' : 'none';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Anything created before this cutoff is guaranteed terminal regardless of its frozen config.
|
|
325
|
+
const block = await this.client.getBlock();
|
|
326
|
+
const hardCutoff = block.timestamp - MAX_PROPOSAL_LIFETIME_SECONDS;
|
|
327
|
+
|
|
328
|
+
// Proposals are append-only with monotonically non-decreasing creation timestamps, so iterating
|
|
329
|
+
// from newest -> oldest lets us early-stop as soon as we cross the lifetime cutoff.
|
|
330
|
+
for (let id = proposalCount - 1n; id >= 0n; id--) {
|
|
331
|
+
const proposal = await this.getProposal(id);
|
|
332
|
+
|
|
333
|
+
// Hard early-stop: every older proposal is also older than the cutoff and therefore terminal.
|
|
334
|
+
if (proposal.creation < hardCutoff) {
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const proposalPayload = proposal.payload.toString().toLowerCase();
|
|
339
|
+
const original = await this.getOriginalPayload(proposal.payload);
|
|
340
|
+
const matches = proposalPayload === target || (original !== undefined && original.toLowerCase() === target);
|
|
341
|
+
if (!matches) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// A live or Droppable proposal blocks re-signalling and wins over any executed one.
|
|
346
|
+
if (LIVE_PROPOSAL_STATES.has(proposal.state) || proposal.state === ProposalState.Droppable) {
|
|
347
|
+
return 'live';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (proposal.state === ProposalState.Executed) {
|
|
351
|
+
this.executedPayloads.add(target);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Rejected/Dropped/Expired proposals allow re-proposing the same payload; keep scanning.
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return this.executedPayloads.has(target) ? 'executed' : 'none';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Resolves the original payload behind a `GSEPayload` wrapper. Returns `undefined` if the
|
|
362
|
+
* wrapper does not implement `IProposerPayload.getOriginalPayload` (e.g. proposals created via
|
|
363
|
+
* `Governance.proposeWithLock`, which bypass GSEPayload entirely and store the raw `IPayload`
|
|
364
|
+
* directly). Results are memoized indefinitely because deployed wrapper bytecode is immutable.
|
|
365
|
+
*/
|
|
366
|
+
private async getOriginalPayload(wrapper: EthAddress): Promise<Hex | undefined> {
|
|
367
|
+
const key = wrapper.toString();
|
|
368
|
+
if (this.originalPayloadCache.has(key)) {
|
|
369
|
+
return this.originalPayloadCache.get(key);
|
|
370
|
+
}
|
|
371
|
+
let original: Hex | undefined;
|
|
372
|
+
try {
|
|
373
|
+
original = await this.client.readContract({
|
|
374
|
+
address: key,
|
|
375
|
+
abi: ProposerPayloadAbi,
|
|
376
|
+
functionName: 'getOriginalPayload',
|
|
377
|
+
});
|
|
378
|
+
} catch {
|
|
379
|
+
original = undefined;
|
|
81
380
|
}
|
|
82
|
-
|
|
381
|
+
this.originalPayloadCache.set(key, original);
|
|
382
|
+
return original;
|
|
83
383
|
}
|
|
84
384
|
|
|
85
385
|
public async awaitProposalActive({ proposalId, logger }: { proposalId: bigint; logger: Logger }) {
|
|
@@ -194,7 +494,7 @@ export class GovernanceContract extends ReadOnlyGovernanceContract {
|
|
|
194
494
|
retries: number;
|
|
195
495
|
logger: Logger;
|
|
196
496
|
}) {
|
|
197
|
-
const l1TxUtils =
|
|
497
|
+
const l1TxUtils = createL1TxUtils(this.client, { logger });
|
|
198
498
|
const retryDelaySeconds = 12;
|
|
199
499
|
|
|
200
500
|
voteAmount = voteAmount ?? (await this.getPowerForProposal(proposalId));
|
|
@@ -252,7 +552,7 @@ export class GovernanceContract extends ReadOnlyGovernanceContract {
|
|
|
252
552
|
retries: number;
|
|
253
553
|
logger: Logger;
|
|
254
554
|
}) {
|
|
255
|
-
const l1TxUtils =
|
|
555
|
+
const l1TxUtils = createL1TxUtils(this.client, { logger });
|
|
256
556
|
const retryDelaySeconds = 12;
|
|
257
557
|
let success = false;
|
|
258
558
|
for (let i = 0; i < retries; i++) {
|
|
@@ -15,11 +15,19 @@ 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 { 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>;
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Cache of bytecode-existence checks keyed by payload address. The check is stable for a
|
|
25
|
+
* contract's lifetime -- a contract either has code or it does not, and code cannot be removed
|
|
26
|
+
* after deployment (selfdestruct aside, which is not relevant here). Safe to memoize
|
|
27
|
+
* indefinitely for the lifetime of this instance.
|
|
28
|
+
*/
|
|
29
|
+
private readonly emptyPayloadCache: Map<Hex, boolean> = new Map();
|
|
30
|
+
|
|
23
31
|
constructor(
|
|
24
32
|
public readonly client: ViemClient,
|
|
25
33
|
address: Hex | EthAddress,
|
|
@@ -30,16 +38,16 @@ export class GovernanceProposerContract implements IEmpireBase {
|
|
|
30
38
|
this.proposer = getContract({ address, abi: GovernanceProposerAbi, client });
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
public get address() {
|
|
41
|
+
public get address(): EthAddress {
|
|
34
42
|
return EthAddress.fromString(this.proposer.address);
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
public async getRollupAddress() {
|
|
45
|
+
public async getRollupAddress(): Promise<EthAddress> {
|
|
38
46
|
return EthAddress.fromString(await this.proposer.read.getInstance());
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
@memoize
|
|
42
|
-
public async getRegistryAddress() {
|
|
50
|
+
public async getRegistryAddress(): Promise<EthAddress> {
|
|
43
51
|
return EthAddress.fromString(await this.proposer.read.REGISTRY());
|
|
44
52
|
}
|
|
45
53
|
|
|
@@ -51,10 +59,6 @@ export class GovernanceProposerContract implements IEmpireBase {
|
|
|
51
59
|
return this.proposer.read.ROUND_SIZE();
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
public getInstance() {
|
|
55
|
-
return this.proposer.read.getInstance();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
62
|
public computeRound(slot: SlotNumber): Promise<bigint> {
|
|
59
63
|
return this.proposer.read.computeRound([BigInt(slot)]);
|
|
60
64
|
}
|
|
@@ -99,7 +103,7 @@ export class GovernanceProposerContract implements IEmpireBase {
|
|
|
99
103
|
signer,
|
|
100
104
|
payload,
|
|
101
105
|
slot,
|
|
102
|
-
await this.
|
|
106
|
+
(await this.getRollupAddress()).toString(),
|
|
103
107
|
this.address.toString(),
|
|
104
108
|
chainId,
|
|
105
109
|
);
|
|
@@ -110,6 +114,51 @@ export class GovernanceProposerContract implements IEmpireBase {
|
|
|
110
114
|
};
|
|
111
115
|
}
|
|
112
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Resolves the Governance contract this proposer submits winners to. Lazily reads
|
|
119
|
+
* `GovernanceProposer.getGovernance()` (which itself looks the address up via the registry) and
|
|
120
|
+
* memoizes the resulting wrapper.
|
|
121
|
+
*/
|
|
122
|
+
@memoize
|
|
123
|
+
public async getGovernance(): Promise<ReadOnlyGovernanceContract> {
|
|
124
|
+
const address = await this.proposer.read.getGovernance();
|
|
125
|
+
return new ReadOnlyGovernanceContract(address, this.client);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
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
|
|
133
|
+
* resolve the Governance address themselves.
|
|
134
|
+
*/
|
|
135
|
+
public async getPayloadProposalStatus(payload: Hex): Promise<PayloadProposalStatus> {
|
|
136
|
+
const governance = await this.getGovernance();
|
|
137
|
+
return governance.getPayloadProposalStatus(payload);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Returns true if the given payload address has no deployed bytecode. Used as a cheap
|
|
142
|
+
* pre-flight check before casting a governance signal — voting for a zero-code address
|
|
143
|
+
* is unrecoverable.
|
|
144
|
+
*
|
|
145
|
+
* We only cache the `false` result (address has bytecode). The `true` result is NOT
|
|
146
|
+
* cached because a CREATE2-redeployed address could go from empty to populated, and
|
|
147
|
+
* caching `true` would make us keep skipping a payload that later becomes valid.
|
|
148
|
+
*/
|
|
149
|
+
public async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
|
|
150
|
+
const key = payload.toString() as Hex;
|
|
151
|
+
if (this.emptyPayloadCache.get(key) === false) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const code = await this.client.getCode({ address: key });
|
|
155
|
+
const isEmpty = !code || code === '0x';
|
|
156
|
+
if (!isEmpty) {
|
|
157
|
+
this.emptyPayloadCache.set(key, false);
|
|
158
|
+
}
|
|
159
|
+
return isEmpty;
|
|
160
|
+
}
|
|
161
|
+
|
|
113
162
|
public async submitRoundWinner(
|
|
114
163
|
round: bigint,
|
|
115
164
|
l1TxUtils: L1TxUtils,
|
package/src/contracts/gse.ts
CHANGED
|
@@ -47,11 +47,16 @@ export class GSEContract {
|
|
|
47
47
|
return EthAddress.fromString(await this.gse.read.getGovernance());
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
getAttestersFromIndicesAtTime(
|
|
50
|
+
getAttestersFromIndicesAtTime(
|
|
51
|
+
instance: Hex | EthAddress,
|
|
52
|
+
ts: bigint,
|
|
53
|
+
indices: bigint[],
|
|
54
|
+
options?: { blockNumber?: bigint },
|
|
55
|
+
) {
|
|
51
56
|
if (instance instanceof EthAddress) {
|
|
52
57
|
instance = instance.toString();
|
|
53
58
|
}
|
|
54
|
-
return this.gse.read.getAttestersFromIndicesAtTime([instance, ts, indices]);
|
|
59
|
+
return this.gse.read.getAttestersFromIndicesAtTime([instance, ts, indices], options);
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
public async getRegistrationDigest(publicKey: ProjPointType<bigint>): Promise<ProjPointType<bigint>> {
|
package/src/contracts/inbox.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { maxBigint } from '@aztec/foundation/bigint';
|
|
1
2
|
import { CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
3
|
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
3
4
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
@@ -37,7 +38,7 @@ export class InboxContract {
|
|
|
37
38
|
|
|
38
39
|
static getFromConfig(config: L1ReaderConfig) {
|
|
39
40
|
const client = getPublicClient(config);
|
|
40
|
-
const address = config.
|
|
41
|
+
const address = config.inboxAddress.toString();
|
|
41
42
|
return new InboxContract(client, address);
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -82,10 +83,17 @@ export class InboxContract {
|
|
|
82
83
|
.map(log => this.mapMessageSentLog(log));
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
/** Fetches MessageSent events for a specific message hash
|
|
86
|
-
async getMessageSentEventByHash(
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
/** Fetches MessageSent events for a specific message hash around a specific block. */
|
|
87
|
+
async getMessageSentEventByHash(msgHash: Hex, aroundL1BlockNumber: bigint): Promise<MessageSentLog> {
|
|
88
|
+
// We don't use blockHash here because we don't want the query to throw if the L1 block number no longer exists on chain
|
|
89
|
+
// due to an L1 reorg. The use case for this method is usually checking if a message still exists on the Inbox after
|
|
90
|
+
// a reorg, so it's possible the message was moved one block up or down, and that the original L1 block where we
|
|
91
|
+
// saw it no longer exists, rendering the block-by-hash approach invalid.
|
|
92
|
+
const [log] = await this.inbox.getEvents.MessageSent(
|
|
93
|
+
{ hash: msgHash },
|
|
94
|
+
{ fromBlock: maxBigint(aroundL1BlockNumber - 5n, 1n), toBlock: aroundL1BlockNumber + 5n },
|
|
95
|
+
);
|
|
96
|
+
return log && this.mapMessageSentLog(log);
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
private mapMessageSentLog(log: {
|
package/src/contracts/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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';
|
|
5
|
+
export * from './fee_asset_price_oracle.js';
|
|
4
6
|
export * from './fee_juice.js';
|
|
5
7
|
export * from './governance.js';
|
|
6
8
|
export * from './governance_proposer.js';
|
|
@@ -11,6 +13,5 @@ export * from './multicall.js';
|
|
|
11
13
|
export * from './outbox.js';
|
|
12
14
|
export * from './registry.js';
|
|
13
15
|
export * from './rollup.js';
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './tally_slashing_proposer.js';
|
|
16
|
+
export * from './slashing_proposer.js';
|
|
16
17
|
export * from './slasher_contract.js';
|