@aztec/ethereum 4.2.0-rc.1 → 4.2.1
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/empire_base.d.ts +1 -3
- package/dest/contracts/empire_base.d.ts.map +1 -1
- package/dest/contracts/governance.d.ts +108 -33
- package/dest/contracts/governance.d.ts.map +1 -1
- package/dest/contracts/governance.js +193 -12
- package/dest/contracts/governance_proposer.d.ts +16 -3
- package/dest/contracts/governance_proposer.d.ts.map +1 -1
- package/dest/contracts/governance_proposer.js +23 -9
- package/package.json +5 -5
- package/src/contracts/empire_base.ts +0 -2
- package/src/contracts/governance.ts +266 -9
- package/src/contracts/governance_proposer.ts +22 -5
|
@@ -14,8 +14,6 @@ export interface IEmpireBase {
|
|
|
14
14
|
computeRound(slot: SlotNumber): Promise<bigint>;
|
|
15
15
|
createSignalRequest(payload: Hex): L1TxRequest;
|
|
16
16
|
createSignalRequestWithSignature(payload: Hex, slot: SlotNumber, chainId: number, signerAddress: Hex, signer: (msg: TypedDataDefinition) => Promise<Hex>): Promise<L1TxRequest>;
|
|
17
|
-
/** Checks if a payload was ever submitted to governance via submitRoundWinner. */
|
|
18
|
-
hasPayloadBeenProposed(payload: Hex, fromBlock: bigint): Promise<boolean>;
|
|
19
17
|
}
|
|
20
18
|
export declare function encodeSignal(payload: Hex): Hex;
|
|
21
19
|
export declare function encodeSignalWithSignature(payload: Hex, signature: Signature): `0x${string}`;
|
|
@@ -29,4 +27,4 @@ export declare function encodeSignalWithSignature(payload: Hex, signature: Signa
|
|
|
29
27
|
* @returns The EIP-712 signature
|
|
30
28
|
*/
|
|
31
29
|
export declare function signSignalWithSig(signer: (msg: TypedDataDefinition) => Promise<Hex>, payload: Hex, slot: SlotNumber, instance: Hex, verifyingContract: Hex, chainId: number): Promise<Signature>;
|
|
32
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1waXJlX2Jhc2UuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb250cmFjdHMvZW1waXJlX2Jhc2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDbEUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRzVELE9BQU8sRUFBRSxLQUFLLEdBQUcsRUFBRSxLQUFLLG1CQUFtQixFQUFzQixNQUFNLE1BQU0sQ0FBQztBQUU5RSxPQUFPLEtBQUssRUFBRSxXQUFXLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUUzRCxNQUFNLFdBQVcsV0FBVztJQUMxQixJQUFJLE9BQU8sSUFBSSxVQUFVLENBQUM7SUFDMUIsWUFBWSxDQUNWLGFBQWEsRUFBRSxHQUFHLEVBQ2xCLEtBQUssRUFBRSxNQUFNLEdBQ1osT0FBTyxDQUFDO1FBQUUsY0FBYyxFQUFFLFVBQVUsQ0FBQztRQUFDLHNCQUFzQixFQUFFLEdBQUcsQ0FBQztRQUFDLGFBQWEsRUFBRSxPQUFPLENBQUM7UUFBQyxRQUFRLEVBQUUsT0FBTyxDQUFBO0tBQUUsQ0FBQyxDQUFDO0lBQ25ILFlBQVksQ0FBQyxJQUFJLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNoRCxtQkFBbUIsQ0FBQyxPQUFPLEVBQUUsR0FBRyxHQUFHLFdBQVcsQ0FBQztJQUMvQyxnQ0FBZ0MsQ0FDOUIsT0FBTyxFQUFFLEdBQUcsRUFDWixJQUFJLEVBQUUsVUFBVSxFQUNoQixPQUFPLEVBQUUsTUFBTSxFQUNmLGFBQWEsRUFBRSxHQUFHLEVBQ2xCLE1BQU0sRUFBRSxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsS0FBSyxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQ2pELE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztDQUN6QjtBQUVELHdCQUFnQixZQUFZLENBQUMsT0FBTyxFQUFFLEdBQUcsR0FBRyxHQUFHLENBTTlDO0FBRUQsd0JBQWdCLHlCQUF5QixDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUUsU0FBUyxFQUFFLFNBQVMsaUJBTTNFO0FBRUQ7Ozs7Ozs7O0dBUUc7QUFDSCx3QkFBc0IsaUJBQWlCLENBQ3JDLE1BQU0sRUFBRSxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsS0FBSyxPQUFPLENBQUMsR0FBRyxDQUFDLEVBQ2xELE9BQU8sRUFBRSxHQUFHLEVBQ1osSUFBSSxFQUFFLFVBQVUsRUFDaEIsUUFBUSxFQUFFLEdBQUcsRUFDYixpQkFBaUIsRUFBRSxHQUFHLEVBQ3RCLE9BQU8sRUFBRSxNQUFNLEdBQ2QsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQThCcEIifQ==
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"empire_base.d.ts","sourceRoot":"","sources":["../../src/contracts/empire_base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAG5D,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,mBAAmB,EAAsB,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,IAAI,OAAO,IAAI,UAAU,CAAC;IAC1B,YAAY,CACV,aAAa,EAAE,GAAG,EAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,cAAc,EAAE,UAAU,CAAC;QAAC,sBAAsB,EAAE,GAAG,CAAC;QAAC,aAAa,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACnH,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,mBAAmB,CAAC,OAAO,EAAE,GAAG,GAAG,WAAW,CAAC;IAC/C,gCAAgC,CAC9B,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,GAAG,EAClB,MAAM,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,OAAO,CAAC,GAAG,CAAC,GACjD,OAAO,CAAC,WAAW,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"empire_base.d.ts","sourceRoot":"","sources":["../../src/contracts/empire_base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAG5D,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,mBAAmB,EAAsB,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,IAAI,OAAO,IAAI,UAAU,CAAC;IAC1B,YAAY,CACV,aAAa,EAAE,GAAG,EAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,cAAc,EAAE,UAAU,CAAC;QAAC,sBAAsB,EAAE,GAAG,CAAC;QAAC,aAAa,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACnH,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,mBAAmB,CAAC,OAAO,EAAE,GAAG,GAAG,WAAW,CAAC;IAC/C,gCAAgC,CAC9B,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,GAAG,EAClB,MAAM,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,OAAO,CAAC,GAAG,CAAC,GACjD,OAAO,CAAC,WAAW,CAAC,CAAC;CACzB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,GAAG,GAAG,CAM9C;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,iBAM3E;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,OAAO,CAAC,GAAG,CAAC,EAClD,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,GAAG,EACb,iBAAiB,EAAE,GAAG,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,SAAS,CAAC,CA8BpB"}
|
|
@@ -15,46 +15,121 @@ export declare enum ProposalState {
|
|
|
15
15
|
Dropped = 6,
|
|
16
16
|
Expired = 7
|
|
17
17
|
}
|
|
18
|
+
/** Vote tallies on a single proposal. Both fields are mutated by `Governance.vote`. */
|
|
19
|
+
export interface Ballot {
|
|
20
|
+
yea: bigint;
|
|
21
|
+
nay: bigint;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Snapshot of the timing/quorum parameters that govern a single proposal's lifecycle. Each proposal
|
|
25
|
+
* stores its own copy at creation time (see `_propose` in Governance.sol), so this snapshot is
|
|
26
|
+
* immutable for the lifetime of the proposal even if the global `Configuration` changes later.
|
|
27
|
+
*/
|
|
28
|
+
export interface ProposalConfiguration {
|
|
29
|
+
votingDelay: bigint;
|
|
30
|
+
votingDuration: bigint;
|
|
31
|
+
executionDelay: bigint;
|
|
32
|
+
gracePeriod: bigint;
|
|
33
|
+
quorum: bigint;
|
|
34
|
+
requiredYeaMargin: bigint;
|
|
35
|
+
minimumVotes: bigint;
|
|
36
|
+
}
|
|
37
|
+
/** Parameters for `Governance.proposeWithLock`. Stored only in the global Configuration, never on a proposal. */
|
|
38
|
+
export interface ProposeWithLockConfiguration {
|
|
39
|
+
lockDelay: bigint;
|
|
40
|
+
lockAmount: bigint;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Live, mutable governance configuration. `proposeConfig` is the lock configuration used by
|
|
44
|
+
* `proposeWithLock`; the remaining fields are the same shape as `ProposalConfiguration` and are
|
|
45
|
+
* snapshotted onto each new proposal at creation time.
|
|
46
|
+
*/
|
|
47
|
+
export interface GovernanceConfiguration extends ProposalConfiguration {
|
|
48
|
+
proposeConfig: ProposeWithLockConfiguration;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A governance proposal augmented with its live (computed) state.
|
|
52
|
+
*
|
|
53
|
+
* Mutability:
|
|
54
|
+
* - `config`, `payload`, `proposer`, `creation` are immutable for the lifetime of the proposal.
|
|
55
|
+
* - `cachedState` is the raw value stored on-chain. It is only written when a proposal is explicitly
|
|
56
|
+
* executed or dropped, so for time-derived terminal states (Rejected, Expired) it remains at the
|
|
57
|
+
* value that was current when the proposal was created.
|
|
58
|
+
* - `state` is the computed state returned by `Governance.getProposalState`. This is the value
|
|
59
|
+
* callers almost always want -- it reflects time-derived transitions that `cachedState` does not.
|
|
60
|
+
* - `summedBallot` is mutated by every `Governance.vote` call while the proposal is Active.
|
|
61
|
+
*
|
|
62
|
+
* Once `state` is in a terminal phase (`Executed`/`Rejected`/`Dropped`/`Expired`) the entire struct
|
|
63
|
+
* is provably immutable on-chain (no further votes can be cast and no state-mutating call to
|
|
64
|
+
* `execute`/`dropProposal` can succeed), and the wrapper memoizes it.
|
|
65
|
+
*/
|
|
66
|
+
export interface Proposal {
|
|
67
|
+
config: ProposalConfiguration;
|
|
68
|
+
cachedState: ProposalState;
|
|
69
|
+
state: ProposalState;
|
|
70
|
+
payload: EthAddress;
|
|
71
|
+
proposer: EthAddress;
|
|
72
|
+
creation: bigint;
|
|
73
|
+
summedBallot: Ballot;
|
|
74
|
+
}
|
|
75
|
+
export declare const MAX_PROPOSAL_LIFETIME_SECONDS: bigint;
|
|
18
76
|
export declare function extractProposalIdFromLogs(logs: Log[]): bigint;
|
|
19
77
|
export declare class ReadOnlyGovernanceContract {
|
|
20
78
|
readonly client: ViemClient;
|
|
21
79
|
protected readonly governanceContract: GetContractReturnType<typeof GovernanceAbi, ViemClient>;
|
|
80
|
+
/**
|
|
81
|
+
* Cache of fully-resolved proposals keyed by id. We populate this lazily and only retain entries
|
|
82
|
+
* whose state is provably terminal -- once `cachedState` is `Executed` or `Dropped` the on-chain
|
|
83
|
+
* proposal struct is frozen and safe to memoize indefinitely. Other state transitions (e.g.
|
|
84
|
+
* Pending -> Active, or accumulating votes) leave the cache untouched and force a fresh fetch.
|
|
85
|
+
*/
|
|
86
|
+
private readonly proposalCache;
|
|
87
|
+
/**
|
|
88
|
+
* Cache of `IProposerPayload.getOriginalPayload()` results keyed by wrapper address. The wrapper
|
|
89
|
+
* contract's bytecode is immutable, so this mapping never changes -- a value of `undefined`
|
|
90
|
+
* encodes a proposal whose payload doesn't implement `getOriginalPayload` (e.g. proposeWithLock
|
|
91
|
+
* proposals) and should be treated as "no original".
|
|
92
|
+
*/
|
|
93
|
+
private readonly originalPayloadCache;
|
|
22
94
|
constructor(address: Hex, client: ViemClient);
|
|
23
95
|
get address(): EthAddress;
|
|
24
96
|
getGovernanceProposerAddress(): Promise<EthAddress>;
|
|
25
|
-
getConfiguration(): Promise<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
getProposal(proposalId: bigint): Promise<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
quorum: bigint;
|
|
45
|
-
requiredYeaMargin: bigint;
|
|
46
|
-
votingDelay: bigint;
|
|
47
|
-
votingDuration: bigint;
|
|
48
|
-
};
|
|
49
|
-
creation: bigint;
|
|
50
|
-
payload: `0x${string}`;
|
|
51
|
-
proposer: `0x${string}`;
|
|
52
|
-
summedBallot: {
|
|
53
|
-
nay: bigint;
|
|
54
|
-
yea: bigint;
|
|
55
|
-
};
|
|
56
|
-
}>;
|
|
97
|
+
getConfiguration(): Promise<GovernanceConfiguration>;
|
|
98
|
+
/**
|
|
99
|
+
* Fetches a proposal by id together with its live state, returning the mapped {@link Proposal}
|
|
100
|
+
* type. Issues `getProposal` and `getProposalState` in parallel so the result carries both the
|
|
101
|
+
* raw stored `cachedState` and the time-derived `state` -- callers can use whichever they need
|
|
102
|
+
* without an extra round-trip.
|
|
103
|
+
*
|
|
104
|
+
* Backed by an in-memory cache that retains entries only when `state` is in one of the four
|
|
105
|
+
* terminal phases (`Executed` / `Rejected` / `Dropped` / `Expired`). At that point the entire
|
|
106
|
+
* proposal struct is provably immutable on-chain (no further votes can be cast and no
|
|
107
|
+
* state-mutating call can succeed), so caching is safe forever. Non-terminal states force a
|
|
108
|
+
* fresh fetch on every call so callers always see fresh `state` and `summedBallot` values.
|
|
109
|
+
*/
|
|
110
|
+
getProposal(proposalId: bigint): Promise<Proposal>;
|
|
111
|
+
/**
|
|
112
|
+
* Returns the live state of a proposal as computed by `Governance.getProposalState`. Prefer
|
|
113
|
+
* {@link getProposal} when you also need any other proposal data -- it returns this same value
|
|
114
|
+
* via {@link Proposal.state} alongside the rest of the struct in a single round-trip.
|
|
115
|
+
*/
|
|
57
116
|
getProposalState(proposalId: bigint): Promise<ProposalState>;
|
|
117
|
+
getProposalCount(): Promise<bigint>;
|
|
118
|
+
/**
|
|
119
|
+
* Checks whether the given original payload is currently the subject of a live (non-terminal)
|
|
120
|
+
* Governance proposal. Returns true only if a proposal references this payload and is still in
|
|
121
|
+
* Pending, Active, Queued, or Executable state. Terminal proposals (Executed, Rejected, Dropped,
|
|
122
|
+
* Expired) are ignored, because once a proposal reaches a terminal state the same original
|
|
123
|
+
* payload may legitimately be re-signaled and re-submitted via the GovernanceProposer (each round
|
|
124
|
+
* is independent and there is no payload-level uniqueness check on-chain).
|
|
125
|
+
*
|
|
126
|
+
* Implemented as a bounded view-call sweep over `Governance.proposals` rather than an event scan,
|
|
127
|
+
* because `eth_getLogs` over the full deployment history of a long-lived rollup exceeds typical
|
|
128
|
+
* RPC block-range caps. The number of proposals (`proposalCount`) is small in practice, and we
|
|
129
|
+
* walk newest -> oldest with a hard early-stop on the protocol-wide proposal lifetime cap.
|
|
130
|
+
*/
|
|
131
|
+
hasActiveProposalWithPayload(payload: Hex): Promise<boolean>;
|
|
132
|
+
private getOriginalPayload;
|
|
58
133
|
awaitProposalActive({ proposalId, logger }: {
|
|
59
134
|
proposalId: bigint;
|
|
60
135
|
logger: Logger;
|
|
@@ -89,4 +164,4 @@ export declare class GovernanceContract extends ReadOnlyGovernanceContract {
|
|
|
89
164
|
logger: Logger;
|
|
90
165
|
}): Promise<void>;
|
|
91
166
|
}
|
|
92
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
167
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ292ZXJuYW5jZS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbnRyYWN0cy9nb3Zlcm5hbmNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUMzRCxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUVwRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFFbEUsT0FBTyxFQUVMLEtBQUsscUJBQXFCLEVBQzFCLEtBQUssR0FBRyxFQUNSLEtBQUssR0FBRyxFQUlULE1BQU0sTUFBTSxDQUFDO0FBRWQsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUV2RSxPQUFPLEVBQUUsS0FBSyx3QkFBd0IsRUFBRSxLQUFLLFVBQVUsRUFBb0IsTUFBTSxhQUFhLENBQUM7QUFpQi9GLE1BQU0sTUFBTSw2QkFBNkIsR0FBRyxJQUFJLENBQzlDLG1CQUFtQixFQUNuQixtQkFBbUIsR0FBRyxlQUFlLEdBQUcsaUJBQWlCLEdBQUcsMkJBQTJCLENBQ3hGLENBQUM7QUFHRixvQkFBWSxhQUFhO0lBQ3ZCLE9BQU8sSUFBQTtJQUNQLE1BQU0sSUFBQTtJQUNOLE1BQU0sSUFBQTtJQUNOLFVBQVUsSUFBQTtJQUNWLFFBQVEsSUFBQTtJQUNSLFFBQVEsSUFBQTtJQUNSLE9BQU8sSUFBQTtJQUNQLE9BQU8sSUFBQTtDQUNSO0FBRUQsdUZBQXVGO0FBQ3ZGLE1BQU0sV0FBVyxNQUFNO0lBQ3JCLEdBQUcsRUFBRSxNQUFNLENBQUM7SUFDWixHQUFHLEVBQUUsTUFBTSxDQUFDO0NBQ2I7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxXQUFXLHFCQUFxQjtJQUNwQyxXQUFXLEVBQUUsTUFBTSxDQUFDO0lBQ3BCLGNBQWMsRUFBRSxNQUFNLENBQUM7SUFDdkIsY0FBYyxFQUFFLE1BQU0sQ0FBQztJ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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"governance.d.ts","sourceRoot":"","sources":["../../src/contracts/governance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,GAAG,EACR,KAAK,GAAG,EAIT,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,UAAU,EAAoB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"governance.d.ts","sourceRoot":"","sources":["../../src/contracts/governance.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,24 @@ import { GovernanceAbi } from '@aztec/l1-artifacts/GovernanceAbi';
|
|
|
4
4
|
import { encodeFunctionData, getContract, parseEventLogs } from 'viem';
|
|
5
5
|
import { createL1TxUtils } from '../l1_tx_utils/index.js';
|
|
6
6
|
import { isExtendedClient } from '../types.js';
|
|
7
|
+
// Minimal ABI for IProposerPayload (`l1-contracts/src/governance/interfaces/IProposerPayload.sol`).
|
|
8
|
+
// The GovernanceProposer wraps every original payload in a GSEPayload before submitting it to
|
|
9
|
+
// Governance, so `Proposal.payload` on the Governance contract is the wrapper address rather than
|
|
10
|
+
// the original. We only need `getOriginalPayload` to recover the underlying payload, so we inline
|
|
11
|
+
// the ABI here instead of generating a full artifact.
|
|
12
|
+
const ProposerPayloadAbi = [
|
|
13
|
+
{
|
|
14
|
+
type: 'function',
|
|
15
|
+
name: 'getOriginalPayload',
|
|
16
|
+
inputs: [],
|
|
17
|
+
outputs: [
|
|
18
|
+
{
|
|
19
|
+
type: 'address'
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
stateMutability: 'view'
|
|
23
|
+
}
|
|
24
|
+
];
|
|
7
25
|
// NOTE: Must be kept in sync with DataStructures.ProposalState in l1-contracts
|
|
8
26
|
export var ProposalState = /*#__PURE__*/ function(ProposalState) {
|
|
9
27
|
ProposalState[ProposalState["Pending"] = 0] = "Pending";
|
|
@@ -16,6 +34,31 @@ export var ProposalState = /*#__PURE__*/ function(ProposalState) {
|
|
|
16
34
|
ProposalState[ProposalState["Expired"] = 7] = "Expired";
|
|
17
35
|
return ProposalState;
|
|
18
36
|
}({});
|
|
37
|
+
/** Set of `ProposalState` values for which a proposal is fully immutable on-chain. */ const TERMINAL_PROPOSAL_STATES = new Set([
|
|
38
|
+
5,
|
|
39
|
+
4,
|
|
40
|
+
6,
|
|
41
|
+
7
|
|
42
|
+
]);
|
|
43
|
+
// Hard upper bound on the wall-clock lifetime of any Governance proposal, in seconds.
|
|
44
|
+
// Each proposal stores its own snapshot of `ProposalConfiguration` at creation time and progresses
|
|
45
|
+
// through Pending -> Active -> Queued -> Executable using those frozen durations
|
|
46
|
+
// (see `ProposalLib.{pendingThrough,activeThrough,queuedThrough,executableThrough}`). Each of those
|
|
47
|
+
// four durations is bounded by `ConfigurationLib.TIME_UPPER = 90 days` (validated in
|
|
48
|
+
// `ConfigurationLib.assertValid`), so no proposal can be live for more than 4 * 90 days regardless
|
|
49
|
+
// of what config it was created under. Once past this point, the proposal is guaranteed to be in a
|
|
50
|
+
// terminal state (Executed / Rejected / Dropped / Expired).
|
|
51
|
+
export const MAX_PROPOSAL_LIFETIME_SECONDS = 4n * 90n * 24n * 3600n;
|
|
52
|
+
/**
|
|
53
|
+
* Validates a number returned by an on-chain `ProposalState` enum field and narrows it to the
|
|
54
|
+
* `ProposalState` enum. Throws if the value is out of range.
|
|
55
|
+
*/ function asProposalState(raw) {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
57
|
+
if (raw < 0 || raw > 7) {
|
|
58
|
+
throw new Error(`Invalid proposal state: ${raw}`);
|
|
59
|
+
}
|
|
60
|
+
return raw;
|
|
61
|
+
}
|
|
19
62
|
export function extractProposalIdFromLogs(logs) {
|
|
20
63
|
const parsedLogs = parseEventLogs({
|
|
21
64
|
abi: GovernanceAbi,
|
|
@@ -30,8 +73,22 @@ export function extractProposalIdFromLogs(logs) {
|
|
|
30
73
|
export class ReadOnlyGovernanceContract {
|
|
31
74
|
client;
|
|
32
75
|
governanceContract;
|
|
76
|
+
/**
|
|
77
|
+
* Cache of fully-resolved proposals keyed by id. We populate this lazily and only retain entries
|
|
78
|
+
* whose state is provably terminal -- once `cachedState` is `Executed` or `Dropped` the on-chain
|
|
79
|
+
* proposal struct is frozen and safe to memoize indefinitely. Other state transitions (e.g.
|
|
80
|
+
* Pending -> Active, or accumulating votes) leave the cache untouched and force a fresh fetch.
|
|
81
|
+
*/ proposalCache;
|
|
82
|
+
/**
|
|
83
|
+
* Cache of `IProposerPayload.getOriginalPayload()` results keyed by wrapper address. The wrapper
|
|
84
|
+
* contract's bytecode is immutable, so this mapping never changes -- a value of `undefined`
|
|
85
|
+
* encodes a proposal whose payload doesn't implement `getOriginalPayload` (e.g. proposeWithLock
|
|
86
|
+
* proposals) and should be treated as "no original".
|
|
87
|
+
*/ originalPayloadCache;
|
|
33
88
|
constructor(address, client){
|
|
34
89
|
this.client = client;
|
|
90
|
+
this.proposalCache = new Map();
|
|
91
|
+
this.originalPayloadCache = new Map();
|
|
35
92
|
this.governanceContract = getContract({
|
|
36
93
|
address,
|
|
37
94
|
abi: GovernanceAbi,
|
|
@@ -44,23 +101,147 @@ export class ReadOnlyGovernanceContract {
|
|
|
44
101
|
async getGovernanceProposerAddress() {
|
|
45
102
|
return EthAddress.fromString(await this.governanceContract.read.governanceProposer());
|
|
46
103
|
}
|
|
47
|
-
getConfiguration() {
|
|
48
|
-
|
|
104
|
+
async getConfiguration() {
|
|
105
|
+
const raw = await this.governanceContract.read.getConfiguration();
|
|
106
|
+
return {
|
|
107
|
+
proposeConfig: {
|
|
108
|
+
lockDelay: raw.proposeConfig.lockDelay,
|
|
109
|
+
lockAmount: raw.proposeConfig.lockAmount
|
|
110
|
+
},
|
|
111
|
+
votingDelay: raw.votingDelay,
|
|
112
|
+
votingDuration: raw.votingDuration,
|
|
113
|
+
executionDelay: raw.executionDelay,
|
|
114
|
+
gracePeriod: raw.gracePeriod,
|
|
115
|
+
quorum: raw.quorum,
|
|
116
|
+
requiredYeaMargin: raw.requiredYeaMargin,
|
|
117
|
+
minimumVotes: raw.minimumVotes
|
|
118
|
+
};
|
|
49
119
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Fetches a proposal by id together with its live state, returning the mapped {@link Proposal}
|
|
122
|
+
* type. Issues `getProposal` and `getProposalState` in parallel so the result carries both the
|
|
123
|
+
* raw stored `cachedState` and the time-derived `state` -- callers can use whichever they need
|
|
124
|
+
* without an extra round-trip.
|
|
125
|
+
*
|
|
126
|
+
* Backed by an in-memory cache that retains entries only when `state` is in one of the four
|
|
127
|
+
* terminal phases (`Executed` / `Rejected` / `Dropped` / `Expired`). At that point the entire
|
|
128
|
+
* proposal struct is provably immutable on-chain (no further votes can be cast and no
|
|
129
|
+
* state-mutating call can succeed), so caching is safe forever. Non-terminal states force a
|
|
130
|
+
* fresh fetch on every call so callers always see fresh `state` and `summedBallot` values.
|
|
131
|
+
*/ async getProposal(proposalId) {
|
|
132
|
+
const cached = this.proposalCache.get(proposalId);
|
|
133
|
+
if (cached !== undefined) {
|
|
134
|
+
return cached;
|
|
135
|
+
}
|
|
136
|
+
const [raw, rawState] = await Promise.all([
|
|
137
|
+
this.governanceContract.read.getProposal([
|
|
138
|
+
proposalId
|
|
139
|
+
]),
|
|
140
|
+
this.governanceContract.read.getProposalState([
|
|
141
|
+
proposalId
|
|
142
|
+
])
|
|
53
143
|
]);
|
|
144
|
+
const proposal = {
|
|
145
|
+
config: {
|
|
146
|
+
votingDelay: raw.config.votingDelay,
|
|
147
|
+
votingDuration: raw.config.votingDuration,
|
|
148
|
+
executionDelay: raw.config.executionDelay,
|
|
149
|
+
gracePeriod: raw.config.gracePeriod,
|
|
150
|
+
quorum: raw.config.quorum,
|
|
151
|
+
requiredYeaMargin: raw.config.requiredYeaMargin,
|
|
152
|
+
minimumVotes: raw.config.minimumVotes
|
|
153
|
+
},
|
|
154
|
+
cachedState: asProposalState(raw.cachedState),
|
|
155
|
+
state: asProposalState(rawState),
|
|
156
|
+
payload: EthAddress.fromString(raw.payload),
|
|
157
|
+
proposer: EthAddress.fromString(raw.proposer),
|
|
158
|
+
creation: raw.creation,
|
|
159
|
+
summedBallot: {
|
|
160
|
+
yea: raw.summedBallot.yea,
|
|
161
|
+
nay: raw.summedBallot.nay
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
if (TERMINAL_PROPOSAL_STATES.has(proposal.state)) {
|
|
165
|
+
this.proposalCache.set(proposalId, proposal);
|
|
166
|
+
}
|
|
167
|
+
return proposal;
|
|
54
168
|
}
|
|
55
|
-
|
|
56
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Returns the live state of a proposal as computed by `Governance.getProposalState`. Prefer
|
|
171
|
+
* {@link getProposal} when you also need any other proposal data -- it returns this same value
|
|
172
|
+
* via {@link Proposal.state} alongside the rest of the struct in a single round-trip.
|
|
173
|
+
*/ async getProposalState(proposalId) {
|
|
174
|
+
return asProposalState(await this.governanceContract.read.getProposalState([
|
|
57
175
|
proposalId
|
|
58
|
-
]);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
176
|
+
]));
|
|
177
|
+
}
|
|
178
|
+
getProposalCount() {
|
|
179
|
+
return this.governanceContract.read.proposalCount();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Checks whether the given original payload is currently the subject of a live (non-terminal)
|
|
183
|
+
* Governance proposal. Returns true only if a proposal references this payload and is still in
|
|
184
|
+
* Pending, Active, Queued, or Executable state. Terminal proposals (Executed, Rejected, Dropped,
|
|
185
|
+
* Expired) are ignored, because once a proposal reaches a terminal state the same original
|
|
186
|
+
* payload may legitimately be re-signaled and re-submitted via the GovernanceProposer (each round
|
|
187
|
+
* is independent and there is no payload-level uniqueness check on-chain).
|
|
188
|
+
*
|
|
189
|
+
* Implemented as a bounded view-call sweep over `Governance.proposals` rather than an event scan,
|
|
190
|
+
* because `eth_getLogs` over the full deployment history of a long-lived rollup exceeds typical
|
|
191
|
+
* RPC block-range caps. The number of proposals (`proposalCount`) is small in practice, and we
|
|
192
|
+
* walk newest -> oldest with a hard early-stop on the protocol-wide proposal lifetime cap.
|
|
193
|
+
*/ async hasActiveProposalWithPayload(payload) {
|
|
194
|
+
const proposalCount = await this.getProposalCount();
|
|
195
|
+
if (proposalCount === 0n) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
// Anything created before this cutoff is guaranteed terminal regardless of its frozen config.
|
|
199
|
+
const block = await this.client.getBlock();
|
|
200
|
+
const hardCutoff = block.timestamp - MAX_PROPOSAL_LIFETIME_SECONDS;
|
|
201
|
+
const target = payload.toLowerCase();
|
|
202
|
+
// Proposals are append-only with monotonically non-decreasing creation timestamps, so iterating
|
|
203
|
+
// from newest -> oldest lets us early-stop as soon as we cross the lifetime cutoff.
|
|
204
|
+
for(let id = proposalCount - 1n; id >= 0n; id--){
|
|
205
|
+
const proposal = await this.getProposal(id);
|
|
206
|
+
// Hard early-stop: every older proposal is also older than the cutoff and therefore terminal.
|
|
207
|
+
if (proposal.creation < hardCutoff) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
const original = await this.getOriginalPayload(proposal.payload);
|
|
211
|
+
if (original === undefined || original.toLowerCase() !== target) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// The wrapper unwraps to our payload. Only treat this as "already proposed" if the proposal
|
|
215
|
+
// is still live -- terminal states allow re-proposing the same payload in a later round.
|
|
216
|
+
if (TERMINAL_PROPOSAL_STATES.has(proposal.state)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Resolves the original payload behind a `GSEPayload` wrapper. Returns `undefined` if the
|
|
225
|
+
* wrapper does not implement `IProposerPayload.getOriginalPayload` (e.g. proposals created via
|
|
226
|
+
* `Governance.proposeWithLock`, which bypass GSEPayload entirely and store the raw `IPayload`
|
|
227
|
+
* directly). Results are memoized indefinitely because deployed wrapper bytecode is immutable.
|
|
228
|
+
*/ async getOriginalPayload(wrapper) {
|
|
229
|
+
const key = wrapper.toString();
|
|
230
|
+
if (this.originalPayloadCache.has(key)) {
|
|
231
|
+
return this.originalPayloadCache.get(key);
|
|
232
|
+
}
|
|
233
|
+
let original;
|
|
234
|
+
try {
|
|
235
|
+
original = await this.client.readContract({
|
|
236
|
+
address: key,
|
|
237
|
+
abi: ProposerPayloadAbi,
|
|
238
|
+
functionName: 'getOriginalPayload'
|
|
239
|
+
});
|
|
240
|
+
} catch {
|
|
241
|
+
original = undefined;
|
|
62
242
|
}
|
|
63
|
-
|
|
243
|
+
this.originalPayloadCache.set(key, original);
|
|
244
|
+
return original;
|
|
64
245
|
}
|
|
65
246
|
async awaitProposalActive({ proposalId, logger }) {
|
|
66
247
|
const state = await this.getProposalState(proposalId);
|
|
@@ -4,6 +4,7 @@ import { type Hex, type TransactionReceipt, type TypedDataDefinition } from 'vie
|
|
|
4
4
|
import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
|
|
5
5
|
import type { ViemClient } from '../types.js';
|
|
6
6
|
import { type IEmpireBase } from './empire_base.js';
|
|
7
|
+
import { ReadOnlyGovernanceContract } from './governance.js';
|
|
7
8
|
export declare class GovernanceProposerContract implements IEmpireBase {
|
|
8
9
|
readonly client: ViemClient;
|
|
9
10
|
private readonly proposer;
|
|
@@ -24,11 +25,23 @@ export declare class GovernanceProposerContract implements IEmpireBase {
|
|
|
24
25
|
getPayloadSignals(rollupAddress: Hex, round: bigint, payload: Hex): Promise<bigint>;
|
|
25
26
|
createSignalRequest(payload: Hex): L1TxRequest;
|
|
26
27
|
createSignalRequestWithSignature(payload: Hex, slot: SlotNumber, chainId: number, signerAddress: Hex, signer: (msg: TypedDataDefinition) => Promise<Hex>): Promise<L1TxRequest>;
|
|
27
|
-
/**
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Resolves the Governance contract this proposer submits winners to. Lazily reads
|
|
30
|
+
* `GovernanceProposer.getGovernance()` (which itself looks the address up via the registry) and
|
|
31
|
+
* memoizes the resulting wrapper.
|
|
32
|
+
*/
|
|
33
|
+
getGovernance(): Promise<ReadOnlyGovernanceContract>;
|
|
34
|
+
/**
|
|
35
|
+
* Returns true iff the given original payload is currently the subject of a live (non-terminal)
|
|
36
|
+
* Governance proposal. Delegates to `ReadOnlyGovernanceContract.hasActiveProposalWithPayload`, which
|
|
37
|
+
* implements the actual sweep against the Governance contract -- this method exists only as a
|
|
38
|
+
* convenience wrapper so callers that already hold a GovernanceProposer reference don't have to
|
|
39
|
+
* resolve the Governance address themselves.
|
|
40
|
+
*/
|
|
41
|
+
hasActiveProposalWithPayload(payload: Hex): Promise<boolean>;
|
|
29
42
|
submitRoundWinner(round: bigint, l1TxUtils: L1TxUtils): Promise<{
|
|
30
43
|
receipt: TransactionReceipt;
|
|
31
44
|
proposalId: bigint;
|
|
32
45
|
}>;
|
|
33
46
|
}
|
|
34
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
47
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ292ZXJuYW5jZV9wcm9wb3Nlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbnRyYWN0cy9nb3Zlcm5hbmNlX3Byb3Bvc2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUU3RCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFHM0QsT0FBTyxFQUVMLEtBQUssR0FBRyxFQUNSLEtBQUssa0JBQWtCLEVBQ3ZCLEtBQUssbUJBQW1CLEVBR3pCLE1BQU0sTUFBTSxDQUFDO0FBRWQsT0FBTyxLQUFLLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3RFLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEVBQUUsS0FBSyxXQUFXLEVBQThELE1BQU0sa0JBQWtCLENBQUM7QUFDaEgsT0FBTyxFQUFFLDBCQUEwQixFQUE2QixNQUFNLGlCQUFpQixDQUFDO0FBRXhGLHFCQUFhLDBCQUEyQixZQUFXLFdBQVc7YUFJMUMsTUFBTSxFQUFFLFVBQVU7SUFIcEMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQWtFO0lBRTNGLFlBQ2tCLE1BQU0sRUFBRSxVQUFVLEVBQ2xDLE9BQU8sRUFBRSxHQUFHLEdBQUcsVUFBVSxFQU0xQjtJQUVELElBQVcsT0FBTyxlQUVqQjtJQUVZLGdCQUFnQix3QkFFNUI7SUFHWSxrQkFBa0Isd0JBRTlCO0lBRU0sYUFBYSxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FFdEM7SUFFTSxZQUFZLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUVyQztJQUVNLFdBQVcsMkJBRWpCO0lBRU0sWUFBWSxDQUFDLElBQUksRUFBRSxVQUFVLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUVyRDtJQUVZLFlBQVksQ0FDdkIsYUFBYSxFQUFFLEdBQUcsRUFDbEIsS0FBSyxFQUFFLE1BQU0sR0FDWixPQUFPLENBQUM7UUFBRSxjQUFjLEVBQUUsVUFBVSxDQUFDO1FBQUMsc0JBQXNCLEVBQUUsR0FBRyxDQUFDO1FBQUMsYUFBYSxFQUFFLE9BQU8sQ0FBQztRQUFDLFFBQVEsRUFBRSxPQUFPLENBQUE7S0FBRSxDQUFDLENBWWpIO0lBRU0saUJBQWlCLENBQUMsYUFBYSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUV6RjtJQUVNLG1CQUFtQixDQUFDLE9BQU8sRUFBRSxHQUFHLEdBQUcsV0FBVyxDQU1wRDtJQUVZLGdDQUFnQyxDQUMzQyxPQUFPLEVBQUUsR0FBRyxFQUNaLElBQUksRUFBRSxVQUFVLEVBQ2hCLE9BQU8sRUFBRSxNQUFNLEVBQ2YsYUFBYSxFQUFFLEdBQUcsRUFDbEIsTUFBTSxFQUFFLENBQUMsR0FBRyxFQUFFLG1CQUFtQixLQUFLLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FDakQsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQWN0QjtJQUVEOzs7O09BSUc7SUFFVSxhQUFhLElBQUksT0FBTyxDQUFDLDBCQUEwQixDQUFDLENBR2hFO0lBRUQ7Ozs7OztPQU1HO0lBQ1UsNEJBQTRCLENBQUMsT0FBTyxFQUFFLEdBQUcsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBR3hFO0lBRVksaUJBQWlCLENBQzVCLEtBQUssRUFBRSxNQUFNLEVBQ2IsU0FBUyxFQUFFLFNBQVMsR0FDbkIsT0FBTyxDQUFDO1FBQ1QsT0FBTyxFQUFFLGtCQUFrQixDQUFDO1FBQzVCLFVBQVUsRUFBRSxNQUFNLENBQUM7S0FDcEIsQ0FBQyxDQVlEO0NBQ0YifQ==
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"governance_proposer.d.ts","sourceRoot":"","sources":["../../src/contracts/governance_proposer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAE7D,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAG3D,OAAO,EAEL,KAAK,GAAG,EACR,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAGzB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,KAAK,WAAW,EAA8D,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"governance_proposer.d.ts","sourceRoot":"","sources":["../../src/contracts/governance_proposer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAE7D,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAG3D,OAAO,EAEL,KAAK,GAAG,EACR,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAGzB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,KAAK,WAAW,EAA8D,MAAM,kBAAkB,CAAC;AAChH,OAAO,EAAE,0BAA0B,EAA6B,MAAM,iBAAiB,CAAC;AAExF,qBAAa,0BAA2B,YAAW,WAAW;aAI1C,MAAM,EAAE,UAAU;IAHpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkE;IAE3F,YACkB,MAAM,EAAE,UAAU,EAClC,OAAO,EAAE,GAAG,GAAG,UAAU,EAM1B;IAED,IAAW,OAAO,eAEjB;IAEY,gBAAgB,wBAE5B;IAGY,kBAAkB,wBAE9B;IAEM,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAEtC;IAEM,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAErC;IAEM,WAAW,2BAEjB;IAEM,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAErD;IAEY,YAAY,CACvB,aAAa,EAAE,GAAG,EAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,cAAc,EAAE,UAAU,CAAC;QAAC,sBAAsB,EAAE,GAAG,CAAC;QAAC,aAAa,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAYjH;IAEM,iBAAiB,CAAC,aAAa,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzF;IAEM,mBAAmB,CAAC,OAAO,EAAE,GAAG,GAAG,WAAW,CAMpD;IAEY,gCAAgC,CAC3C,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,GAAG,EAClB,MAAM,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,OAAO,CAAC,GAAG,CAAC,GACjD,OAAO,CAAC,WAAW,CAAC,CActB;IAED;;;;OAIG;IAEU,aAAa,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAGhE;IAED;;;;;;OAMG;IACU,4BAA4B,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAGxE;IAEY,iBAAiB,CAC5B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC;QACT,OAAO,EAAE,kBAAkB,CAAC;QAC5B,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAYD;CACF"}
|
|
@@ -377,7 +377,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
377
377
|
import { GovernanceProposerAbi } from '@aztec/l1-artifacts/GovernanceProposerAbi';
|
|
378
378
|
import { encodeFunctionData, getContract } from 'viem';
|
|
379
379
|
import { encodeSignal, encodeSignalWithSignature, signSignalWithSig } from './empire_base.js';
|
|
380
|
-
import { extractProposalIdFromLogs } from './governance.js';
|
|
380
|
+
import { ReadOnlyGovernanceContract, extractProposalIdFromLogs } from './governance.js';
|
|
381
381
|
export class GovernanceProposerContract {
|
|
382
382
|
client;
|
|
383
383
|
static{
|
|
@@ -386,6 +386,11 @@ export class GovernanceProposerContract {
|
|
|
386
386
|
memoize,
|
|
387
387
|
2,
|
|
388
388
|
"getRegistryAddress"
|
|
389
|
+
],
|
|
390
|
+
[
|
|
391
|
+
memoize,
|
|
392
|
+
2,
|
|
393
|
+
"getGovernance"
|
|
389
394
|
]
|
|
390
395
|
], []));
|
|
391
396
|
}
|
|
@@ -467,14 +472,23 @@ export class GovernanceProposerContract {
|
|
|
467
472
|
data: encodeSignalWithSignature(payload, signature)
|
|
468
473
|
};
|
|
469
474
|
}
|
|
470
|
-
/**
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
475
|
+
/**
|
|
476
|
+
* Resolves the Governance contract this proposer submits winners to. Lazily reads
|
|
477
|
+
* `GovernanceProposer.getGovernance()` (which itself looks the address up via the registry) and
|
|
478
|
+
* memoizes the resulting wrapper.
|
|
479
|
+
*/ async getGovernance() {
|
|
480
|
+
const address = await this.proposer.read.getGovernance();
|
|
481
|
+
return new ReadOnlyGovernanceContract(address, this.client);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Returns true iff the given original payload is currently the subject of a live (non-terminal)
|
|
485
|
+
* Governance proposal. Delegates to `ReadOnlyGovernanceContract.hasActiveProposalWithPayload`, which
|
|
486
|
+
* implements the actual sweep against the Governance contract -- this method exists only as a
|
|
487
|
+
* convenience wrapper so callers that already hold a GovernanceProposer reference don't have to
|
|
488
|
+
* resolve the Governance address themselves.
|
|
489
|
+
*/ async hasActiveProposalWithPayload(payload) {
|
|
490
|
+
const governance = await this.getGovernance();
|
|
491
|
+
return governance.hasActiveProposalWithPayload(payload);
|
|
478
492
|
}
|
|
479
493
|
async submitRoundWinner(round, l1TxUtils) {
|
|
480
494
|
const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/ethereum",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./account": "./dest/account.js",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"../package.common.json"
|
|
51
51
|
],
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@aztec/blob-lib": "4.2.
|
|
54
|
-
"@aztec/constants": "4.2.
|
|
55
|
-
"@aztec/foundation": "4.2.
|
|
56
|
-
"@aztec/l1-artifacts": "4.2.
|
|
53
|
+
"@aztec/blob-lib": "4.2.1",
|
|
54
|
+
"@aztec/constants": "4.2.1",
|
|
55
|
+
"@aztec/foundation": "4.2.1",
|
|
56
|
+
"@aztec/l1-artifacts": "4.2.1",
|
|
57
57
|
"@viem/anvil": "^0.0.10",
|
|
58
58
|
"dotenv": "^16.0.3",
|
|
59
59
|
"lodash.chunk": "^4.2.0",
|
|
@@ -22,8 +22,6 @@ export interface IEmpireBase {
|
|
|
22
22
|
signerAddress: Hex,
|
|
23
23
|
signer: (msg: TypedDataDefinition) => Promise<Hex>,
|
|
24
24
|
): Promise<L1TxRequest>;
|
|
25
|
-
/** Checks if a payload was ever submitted to governance via submitRoundWinner. */
|
|
26
|
-
hasPayloadBeenProposed(payload: Hex, fromBlock: bigint): Promise<boolean>;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
export function encodeSignal(payload: Hex): Hex {
|
|
@@ -17,6 +17,21 @@ import type { L1ContractAddresses } from '../l1_contract_addresses.js';
|
|
|
17
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'
|
|
@@ -34,6 +49,98 @@ export enum ProposalState {
|
|
|
34
49
|
Expired,
|
|
35
50
|
}
|
|
36
51
|
|
|
52
|
+
/** Vote tallies on a single proposal. Both fields are mutated by `Governance.vote`. */
|
|
53
|
+
export interface Ballot {
|
|
54
|
+
yea: bigint;
|
|
55
|
+
nay: bigint;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Snapshot of the timing/quorum parameters that govern a single proposal's lifecycle. Each proposal
|
|
60
|
+
* stores its own copy at creation time (see `_propose` in Governance.sol), so this snapshot is
|
|
61
|
+
* immutable for the lifetime of the proposal even if the global `Configuration` changes later.
|
|
62
|
+
*/
|
|
63
|
+
export interface ProposalConfiguration {
|
|
64
|
+
votingDelay: bigint;
|
|
65
|
+
votingDuration: bigint;
|
|
66
|
+
executionDelay: bigint;
|
|
67
|
+
gracePeriod: bigint;
|
|
68
|
+
quorum: bigint;
|
|
69
|
+
requiredYeaMargin: bigint;
|
|
70
|
+
minimumVotes: bigint;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Parameters for `Governance.proposeWithLock`. Stored only in the global Configuration, never on a proposal. */
|
|
74
|
+
export interface ProposeWithLockConfiguration {
|
|
75
|
+
lockDelay: bigint;
|
|
76
|
+
lockAmount: bigint;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Live, mutable governance configuration. `proposeConfig` is the lock configuration used by
|
|
81
|
+
* `proposeWithLock`; the remaining fields are the same shape as `ProposalConfiguration` and are
|
|
82
|
+
* snapshotted onto each new proposal at creation time.
|
|
83
|
+
*/
|
|
84
|
+
export interface GovernanceConfiguration extends ProposalConfiguration {
|
|
85
|
+
proposeConfig: ProposeWithLockConfiguration;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A governance proposal augmented with its live (computed) state.
|
|
90
|
+
*
|
|
91
|
+
* Mutability:
|
|
92
|
+
* - `config`, `payload`, `proposer`, `creation` are immutable for the lifetime of the proposal.
|
|
93
|
+
* - `cachedState` is the raw value stored on-chain. It is only written when a proposal is explicitly
|
|
94
|
+
* executed or dropped, so for time-derived terminal states (Rejected, Expired) it remains at the
|
|
95
|
+
* value that was current when the proposal was created.
|
|
96
|
+
* - `state` is the computed state returned by `Governance.getProposalState`. This is the value
|
|
97
|
+
* callers almost always want -- it reflects time-derived transitions that `cachedState` does not.
|
|
98
|
+
* - `summedBallot` is mutated by every `Governance.vote` call while the proposal is Active.
|
|
99
|
+
*
|
|
100
|
+
* Once `state` is in a terminal phase (`Executed`/`Rejected`/`Dropped`/`Expired`) the entire struct
|
|
101
|
+
* is provably immutable on-chain (no further votes can be cast and no state-mutating call to
|
|
102
|
+
* `execute`/`dropProposal` can succeed), and the wrapper memoizes it.
|
|
103
|
+
*/
|
|
104
|
+
export interface Proposal {
|
|
105
|
+
config: ProposalConfiguration;
|
|
106
|
+
cachedState: ProposalState;
|
|
107
|
+
state: ProposalState;
|
|
108
|
+
payload: EthAddress;
|
|
109
|
+
proposer: EthAddress;
|
|
110
|
+
creation: bigint;
|
|
111
|
+
summedBallot: Ballot;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Set of `ProposalState` values for which a proposal is fully immutable on-chain. */
|
|
115
|
+
const TERMINAL_PROPOSAL_STATES: ReadonlySet<ProposalState> = new Set([
|
|
116
|
+
ProposalState.Executed,
|
|
117
|
+
ProposalState.Rejected,
|
|
118
|
+
ProposalState.Dropped,
|
|
119
|
+
ProposalState.Expired,
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
// Hard upper bound on the wall-clock lifetime of any Governance proposal, in seconds.
|
|
123
|
+
// Each proposal stores its own snapshot of `ProposalConfiguration` at creation time and progresses
|
|
124
|
+
// through Pending -> Active -> Queued -> Executable using those frozen durations
|
|
125
|
+
// (see `ProposalLib.{pendingThrough,activeThrough,queuedThrough,executableThrough}`). Each of those
|
|
126
|
+
// four durations is bounded by `ConfigurationLib.TIME_UPPER = 90 days` (validated in
|
|
127
|
+
// `ConfigurationLib.assertValid`), so no proposal can be live for more than 4 * 90 days regardless
|
|
128
|
+
// of what config it was created under. Once past this point, the proposal is guaranteed to be in a
|
|
129
|
+
// terminal state (Executed / Rejected / Dropped / Expired).
|
|
130
|
+
export const MAX_PROPOSAL_LIFETIME_SECONDS = 4n * 90n * 24n * 3600n;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Validates a number returned by an on-chain `ProposalState` enum field and narrows it to the
|
|
134
|
+
* `ProposalState` enum. Throws if the value is out of range.
|
|
135
|
+
*/
|
|
136
|
+
function asProposalState(raw: number): ProposalState {
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
138
|
+
if (raw < 0 || raw > ProposalState.Expired) {
|
|
139
|
+
throw new Error(`Invalid proposal state: ${raw}`);
|
|
140
|
+
}
|
|
141
|
+
return raw as ProposalState;
|
|
142
|
+
}
|
|
143
|
+
|
|
37
144
|
export function extractProposalIdFromLogs(logs: Log[]): bigint {
|
|
38
145
|
const parsedLogs = parseEventLogs({
|
|
39
146
|
abi: GovernanceAbi,
|
|
@@ -50,6 +157,22 @@ export function extractProposalIdFromLogs(logs: Log[]): bigint {
|
|
|
50
157
|
export class ReadOnlyGovernanceContract {
|
|
51
158
|
protected readonly governanceContract: GetContractReturnType<typeof GovernanceAbi, ViemClient>;
|
|
52
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Cache of fully-resolved proposals keyed by id. We populate this lazily and only retain entries
|
|
162
|
+
* whose state is provably terminal -- once `cachedState` is `Executed` or `Dropped` the on-chain
|
|
163
|
+
* proposal struct is frozen and safe to memoize indefinitely. Other state transitions (e.g.
|
|
164
|
+
* Pending -> Active, or accumulating votes) leave the cache untouched and force a fresh fetch.
|
|
165
|
+
*/
|
|
166
|
+
private readonly proposalCache: Map<bigint, Proposal> = new Map();
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Cache of `IProposerPayload.getOriginalPayload()` results keyed by wrapper address. The wrapper
|
|
170
|
+
* contract's bytecode is immutable, so this mapping never changes -- a value of `undefined`
|
|
171
|
+
* encodes a proposal whose payload doesn't implement `getOriginalPayload` (e.g. proposeWithLock
|
|
172
|
+
* proposals) and should be treated as "no original".
|
|
173
|
+
*/
|
|
174
|
+
private readonly originalPayloadCache: Map<Hex, Hex | undefined> = new Map();
|
|
175
|
+
|
|
53
176
|
constructor(
|
|
54
177
|
address: Hex,
|
|
55
178
|
public readonly client: ViemClient,
|
|
@@ -65,21 +188,155 @@ export class ReadOnlyGovernanceContract {
|
|
|
65
188
|
return EthAddress.fromString(await this.governanceContract.read.governanceProposer());
|
|
66
189
|
}
|
|
67
190
|
|
|
68
|
-
public getConfiguration() {
|
|
69
|
-
|
|
191
|
+
public async getConfiguration(): Promise<GovernanceConfiguration> {
|
|
192
|
+
const raw = await this.governanceContract.read.getConfiguration();
|
|
193
|
+
return {
|
|
194
|
+
proposeConfig: {
|
|
195
|
+
lockDelay: raw.proposeConfig.lockDelay,
|
|
196
|
+
lockAmount: raw.proposeConfig.lockAmount,
|
|
197
|
+
},
|
|
198
|
+
votingDelay: raw.votingDelay,
|
|
199
|
+
votingDuration: raw.votingDuration,
|
|
200
|
+
executionDelay: raw.executionDelay,
|
|
201
|
+
gracePeriod: raw.gracePeriod,
|
|
202
|
+
quorum: raw.quorum,
|
|
203
|
+
requiredYeaMargin: raw.requiredYeaMargin,
|
|
204
|
+
minimumVotes: raw.minimumVotes,
|
|
205
|
+
};
|
|
70
206
|
}
|
|
71
207
|
|
|
72
|
-
|
|
73
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Fetches a proposal by id together with its live state, returning the mapped {@link Proposal}
|
|
210
|
+
* type. Issues `getProposal` and `getProposalState` in parallel so the result carries both the
|
|
211
|
+
* raw stored `cachedState` and the time-derived `state` -- callers can use whichever they need
|
|
212
|
+
* without an extra round-trip.
|
|
213
|
+
*
|
|
214
|
+
* Backed by an in-memory cache that retains entries only when `state` is in one of the four
|
|
215
|
+
* terminal phases (`Executed` / `Rejected` / `Dropped` / `Expired`). At that point the entire
|
|
216
|
+
* proposal struct is provably immutable on-chain (no further votes can be cast and no
|
|
217
|
+
* state-mutating call can succeed), so caching is safe forever. Non-terminal states force a
|
|
218
|
+
* fresh fetch on every call so callers always see fresh `state` and `summedBallot` values.
|
|
219
|
+
*/
|
|
220
|
+
public async getProposal(proposalId: bigint): Promise<Proposal> {
|
|
221
|
+
const cached = this.proposalCache.get(proposalId);
|
|
222
|
+
if (cached !== undefined) {
|
|
223
|
+
return cached;
|
|
224
|
+
}
|
|
225
|
+
const [raw, rawState] = await Promise.all([
|
|
226
|
+
this.governanceContract.read.getProposal([proposalId]),
|
|
227
|
+
this.governanceContract.read.getProposalState([proposalId]),
|
|
228
|
+
]);
|
|
229
|
+
const proposal: Proposal = {
|
|
230
|
+
config: {
|
|
231
|
+
votingDelay: raw.config.votingDelay,
|
|
232
|
+
votingDuration: raw.config.votingDuration,
|
|
233
|
+
executionDelay: raw.config.executionDelay,
|
|
234
|
+
gracePeriod: raw.config.gracePeriod,
|
|
235
|
+
quorum: raw.config.quorum,
|
|
236
|
+
requiredYeaMargin: raw.config.requiredYeaMargin,
|
|
237
|
+
minimumVotes: raw.config.minimumVotes,
|
|
238
|
+
},
|
|
239
|
+
cachedState: asProposalState(raw.cachedState),
|
|
240
|
+
state: asProposalState(rawState),
|
|
241
|
+
payload: EthAddress.fromString(raw.payload),
|
|
242
|
+
proposer: EthAddress.fromString(raw.proposer),
|
|
243
|
+
creation: raw.creation,
|
|
244
|
+
summedBallot: { yea: raw.summedBallot.yea, nay: raw.summedBallot.nay },
|
|
245
|
+
};
|
|
246
|
+
if (TERMINAL_PROPOSAL_STATES.has(proposal.state)) {
|
|
247
|
+
this.proposalCache.set(proposalId, proposal);
|
|
248
|
+
}
|
|
249
|
+
return proposal;
|
|
74
250
|
}
|
|
75
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Returns the live state of a proposal as computed by `Governance.getProposalState`. Prefer
|
|
254
|
+
* {@link getProposal} when you also need any other proposal data -- it returns this same value
|
|
255
|
+
* via {@link Proposal.state} alongside the rest of the struct in a single round-trip.
|
|
256
|
+
*/
|
|
76
257
|
public async getProposalState(proposalId: bigint): Promise<ProposalState> {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
258
|
+
return asProposalState(await this.governanceContract.read.getProposalState([proposalId]));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public getProposalCount(): Promise<bigint> {
|
|
262
|
+
return this.governanceContract.read.proposalCount();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Checks whether the given original payload is currently the subject of a live (non-terminal)
|
|
267
|
+
* Governance proposal. Returns true only if a proposal references this payload and is still in
|
|
268
|
+
* Pending, Active, Queued, or Executable state. Terminal proposals (Executed, Rejected, Dropped,
|
|
269
|
+
* Expired) are ignored, because once a proposal reaches a terminal state the same original
|
|
270
|
+
* payload may legitimately be re-signaled and re-submitted via the GovernanceProposer (each round
|
|
271
|
+
* is independent and there is no payload-level uniqueness check on-chain).
|
|
272
|
+
*
|
|
273
|
+
* Implemented as a bounded view-call sweep over `Governance.proposals` rather than an event scan,
|
|
274
|
+
* because `eth_getLogs` over the full deployment history of a long-lived rollup exceeds typical
|
|
275
|
+
* RPC block-range caps. The number of proposals (`proposalCount`) is small in practice, and we
|
|
276
|
+
* walk newest -> oldest with a hard early-stop on the protocol-wide proposal lifetime cap.
|
|
277
|
+
*/
|
|
278
|
+
public async hasActiveProposalWithPayload(payload: Hex): Promise<boolean> {
|
|
279
|
+
const proposalCount = await this.getProposalCount();
|
|
280
|
+
if (proposalCount === 0n) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Anything created before this cutoff is guaranteed terminal regardless of its frozen config.
|
|
285
|
+
const block = await this.client.getBlock();
|
|
286
|
+
const hardCutoff = block.timestamp - MAX_PROPOSAL_LIFETIME_SECONDS;
|
|
287
|
+
|
|
288
|
+
const target = payload.toLowerCase() as Hex;
|
|
289
|
+
|
|
290
|
+
// Proposals are append-only with monotonically non-decreasing creation timestamps, so iterating
|
|
291
|
+
// from newest -> oldest lets us early-stop as soon as we cross the lifetime cutoff.
|
|
292
|
+
for (let id = proposalCount - 1n; id >= 0n; id--) {
|
|
293
|
+
const proposal = await this.getProposal(id);
|
|
294
|
+
|
|
295
|
+
// Hard early-stop: every older proposal is also older than the cutoff and therefore terminal.
|
|
296
|
+
if (proposal.creation < hardCutoff) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const original = await this.getOriginalPayload(proposal.payload);
|
|
301
|
+
if (original === undefined || original.toLowerCase() !== target) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// The wrapper unwraps to our payload. Only treat this as "already proposed" if the proposal
|
|
306
|
+
// is still live -- terminal states allow re-proposing the same payload in a later round.
|
|
307
|
+
if (TERMINAL_PROPOSAL_STATES.has(proposal.state)) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Resolves the original payload behind a `GSEPayload` wrapper. Returns `undefined` if the
|
|
319
|
+
* wrapper does not implement `IProposerPayload.getOriginalPayload` (e.g. proposals created via
|
|
320
|
+
* `Governance.proposeWithLock`, which bypass GSEPayload entirely and store the raw `IPayload`
|
|
321
|
+
* directly). Results are memoized indefinitely because deployed wrapper bytecode is immutable.
|
|
322
|
+
*/
|
|
323
|
+
private async getOriginalPayload(wrapper: EthAddress): Promise<Hex | undefined> {
|
|
324
|
+
const key = wrapper.toString();
|
|
325
|
+
if (this.originalPayloadCache.has(key)) {
|
|
326
|
+
return this.originalPayloadCache.get(key);
|
|
327
|
+
}
|
|
328
|
+
let original: Hex | undefined;
|
|
329
|
+
try {
|
|
330
|
+
original = await this.client.readContract({
|
|
331
|
+
address: key,
|
|
332
|
+
abi: ProposerPayloadAbi,
|
|
333
|
+
functionName: 'getOriginalPayload',
|
|
334
|
+
});
|
|
335
|
+
} catch {
|
|
336
|
+
original = undefined;
|
|
81
337
|
}
|
|
82
|
-
|
|
338
|
+
this.originalPayloadCache.set(key, original);
|
|
339
|
+
return original;
|
|
83
340
|
}
|
|
84
341
|
|
|
85
342
|
public async awaitProposalActive({ proposalId, logger }: { proposalId: bigint; logger: Logger }) {
|
|
@@ -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 { extractProposalIdFromLogs } from './governance.js';
|
|
18
|
+
import { ReadOnlyGovernanceContract, extractProposalIdFromLogs } from './governance.js';
|
|
19
19
|
|
|
20
20
|
export class GovernanceProposerContract implements IEmpireBase {
|
|
21
21
|
private readonly proposer: GetContractReturnType<typeof GovernanceProposerAbi, ViemClient>;
|
|
@@ -110,10 +110,27 @@ export class GovernanceProposerContract implements IEmpireBase {
|
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Resolves the Governance contract this proposer submits winners to. Lazily reads
|
|
115
|
+
* `GovernanceProposer.getGovernance()` (which itself looks the address up via the registry) and
|
|
116
|
+
* memoizes the resulting wrapper.
|
|
117
|
+
*/
|
|
118
|
+
@memoize
|
|
119
|
+
public async getGovernance(): Promise<ReadOnlyGovernanceContract> {
|
|
120
|
+
const address = await this.proposer.read.getGovernance();
|
|
121
|
+
return new ReadOnlyGovernanceContract(address, this.client);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Returns true iff the given original payload is currently the subject of a live (non-terminal)
|
|
126
|
+
* Governance proposal. Delegates to `ReadOnlyGovernanceContract.hasActiveProposalWithPayload`, which
|
|
127
|
+
* implements the actual sweep against the Governance contract -- this method exists only as a
|
|
128
|
+
* convenience wrapper so callers that already hold a GovernanceProposer reference don't have to
|
|
129
|
+
* resolve the Governance address themselves.
|
|
130
|
+
*/
|
|
131
|
+
public async hasActiveProposalWithPayload(payload: Hex): Promise<boolean> {
|
|
132
|
+
const governance = await this.getGovernance();
|
|
133
|
+
return governance.hasActiveProposalWithPayload(payload);
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
public async submitRoundWinner(
|