@aztec/ethereum 0.0.0-test.1 → 0.0.1-commit.017a351

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/dest/account.d.ts +2 -0
  2. package/dest/account.d.ts.map +1 -0
  3. package/dest/account.js +4 -0
  4. package/dest/chain.d.ts +1 -1
  5. package/dest/client.d.ts +28 -4
  6. package/dest/client.d.ts.map +1 -1
  7. package/dest/client.js +73 -3
  8. package/dest/config.d.ts +70 -23
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +151 -34
  11. package/dest/constants.d.ts +1 -1
  12. package/dest/contracts/chain_state_override.d.ts +78 -0
  13. package/dest/contracts/chain_state_override.d.ts.map +1 -0
  14. package/dest/contracts/chain_state_override.js +137 -0
  15. package/dest/contracts/empire_base.d.ts +25 -8
  16. package/dest/contracts/empire_base.d.ts.map +1 -1
  17. package/dest/contracts/empire_base.js +75 -2
  18. package/dest/contracts/errors.d.ts +7 -0
  19. package/dest/contracts/errors.d.ts.map +1 -0
  20. package/dest/contracts/errors.js +12 -0
  21. package/dest/contracts/fee_asset_handler.d.ts +20 -0
  22. package/dest/contracts/fee_asset_handler.d.ts.map +1 -0
  23. package/dest/contracts/fee_asset_handler.js +59 -0
  24. package/dest/contracts/fee_asset_price_oracle.d.ts +105 -0
  25. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  26. package/dest/contracts/fee_asset_price_oracle.js +656 -0
  27. package/dest/contracts/fee_juice.d.ts +6 -7
  28. package/dest/contracts/fee_juice.d.ts.map +1 -1
  29. package/dest/contracts/fee_juice.js +27 -20
  30. package/dest/contracts/governance.d.ts +131 -43
  31. package/dest/contracts/governance.d.ts.map +1 -1
  32. package/dest/contracts/governance.js +281 -87
  33. package/dest/contracts/governance_proposer.d.ts +49 -13
  34. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  35. package/dest/contracts/governance_proposer.js +483 -26
  36. package/dest/contracts/gse.d.ts +32 -0
  37. package/dest/contracts/gse.d.ts.map +1 -0
  38. package/dest/contracts/gse.js +72 -0
  39. package/dest/contracts/inbox.d.ts +47 -0
  40. package/dest/contracts/inbox.d.ts.map +1 -0
  41. package/dest/contracts/inbox.js +85 -0
  42. package/dest/contracts/index.d.ts +11 -2
  43. package/dest/contracts/index.d.ts.map +1 -1
  44. package/dest/contracts/index.js +10 -1
  45. package/dest/contracts/log.d.ts +13 -0
  46. package/dest/contracts/log.d.ts.map +1 -0
  47. package/dest/contracts/log.js +1 -0
  48. package/dest/contracts/multicall.d.ts +136 -0
  49. package/dest/contracts/multicall.d.ts.map +1 -0
  50. package/dest/contracts/multicall.js +264 -0
  51. package/dest/contracts/outbox.d.ts +64 -0
  52. package/dest/contracts/outbox.d.ts.map +1 -0
  53. package/dest/contracts/outbox.js +113 -0
  54. package/dest/contracts/registry.d.ts +12 -5
  55. package/dest/contracts/registry.d.ts.map +1 -1
  56. package/dest/contracts/registry.js +74 -17
  57. package/dest/contracts/rollup.d.ts +5526 -52
  58. package/dest/contracts/rollup.d.ts.map +1 -1
  59. package/dest/contracts/rollup.js +1446 -118
  60. package/dest/contracts/slasher_contract.d.ts +44 -0
  61. package/dest/contracts/slasher_contract.d.ts.map +1 -0
  62. package/dest/contracts/slasher_contract.js +75 -0
  63. package/dest/contracts/slashing_proposer.d.ts +133 -15
  64. package/dest/contracts/slashing_proposer.d.ts.map +1 -1
  65. package/dest/contracts/slashing_proposer.js +293 -22
  66. package/dest/contracts/utils.d.ts +3 -0
  67. package/dest/contracts/utils.d.ts.map +1 -0
  68. package/dest/contracts/utils.js +11 -0
  69. package/dest/deploy_aztec_l1_contracts.d.ts +256 -0
  70. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -0
  71. package/dest/deploy_aztec_l1_contracts.js +411 -0
  72. package/dest/deploy_l1_contract.d.ts +68 -0
  73. package/dest/deploy_l1_contract.d.ts.map +1 -0
  74. package/dest/deploy_l1_contract.js +312 -0
  75. package/dest/eth-signer/eth-signer.d.ts +21 -0
  76. package/dest/eth-signer/eth-signer.d.ts.map +1 -0
  77. package/dest/eth-signer/eth-signer.js +5 -0
  78. package/dest/eth-signer/index.d.ts +2 -0
  79. package/dest/eth-signer/index.d.ts.map +1 -0
  80. package/dest/eth-signer/index.js +1 -0
  81. package/dest/forwarder_proxy.d.ts +32 -0
  82. package/dest/forwarder_proxy.d.ts.map +1 -0
  83. package/dest/forwarder_proxy.js +93 -0
  84. package/dest/generated/l1-contracts-defaults.d.ts +30 -0
  85. package/dest/generated/l1-contracts-defaults.d.ts.map +1 -0
  86. package/dest/generated/l1-contracts-defaults.js +30 -0
  87. package/dest/l1_artifacts.d.ts +79270 -0
  88. package/dest/l1_artifacts.d.ts.map +1 -0
  89. package/dest/l1_artifacts.js +151 -0
  90. package/dest/l1_contract_addresses.d.ts +61 -45
  91. package/dest/l1_contract_addresses.d.ts.map +1 -1
  92. package/dest/l1_contract_addresses.js +99 -76
  93. package/dest/l1_reader.d.ts +8 -6
  94. package/dest/l1_reader.d.ts.map +1 -1
  95. package/dest/l1_reader.js +20 -12
  96. package/dest/l1_tx_utils/config.d.ts +65 -0
  97. package/dest/l1_tx_utils/config.d.ts.map +1 -0
  98. package/dest/l1_tx_utils/config.js +109 -0
  99. package/dest/l1_tx_utils/constants.d.ts +12 -0
  100. package/dest/l1_tx_utils/constants.d.ts.map +1 -0
  101. package/dest/l1_tx_utils/constants.js +39 -0
  102. package/dest/l1_tx_utils/factory.d.ts +32 -0
  103. package/dest/l1_tx_utils/factory.d.ts.map +1 -0
  104. package/dest/l1_tx_utils/factory.js +22 -0
  105. package/dest/l1_tx_utils/fee-strategies/index.d.ts +10 -0
  106. package/dest/l1_tx_utils/fee-strategies/index.d.ts.map +1 -0
  107. package/dest/l1_tx_utils/fee-strategies/index.js +12 -0
  108. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts +8 -0
  109. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts.map +1 -0
  110. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +129 -0
  111. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts +23 -0
  112. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts.map +1 -0
  113. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +191 -0
  114. package/dest/l1_tx_utils/fee-strategies/types.d.ts +51 -0
  115. package/dest/l1_tx_utils/fee-strategies/types.d.ts.map +1 -0
  116. package/dest/l1_tx_utils/fee-strategies/types.js +3 -0
  117. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +41 -0
  118. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -0
  119. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +42 -0
  120. package/dest/l1_tx_utils/index-blobs.d.ts +3 -0
  121. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -0
  122. package/dest/l1_tx_utils/index-blobs.js +2 -0
  123. package/dest/l1_tx_utils/index.d.ts +13 -0
  124. package/dest/l1_tx_utils/index.d.ts.map +1 -0
  125. package/dest/l1_tx_utils/index.js +13 -0
  126. package/dest/l1_tx_utils/interfaces.d.ts +76 -0
  127. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
  128. package/dest/l1_tx_utils/interfaces.js +4 -0
  129. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +233 -0
  130. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -0
  131. package/dest/l1_tx_utils/l1_fee_analyzer.js +506 -0
  132. package/dest/l1_tx_utils/l1_tx_utils.d.ts +113 -0
  133. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
  134. package/dest/l1_tx_utils/l1_tx_utils.js +658 -0
  135. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +83 -0
  136. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
  137. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +328 -0
  138. package/dest/l1_tx_utils/signer.d.ts +4 -0
  139. package/dest/l1_tx_utils/signer.d.ts.map +1 -0
  140. package/dest/l1_tx_utils/signer.js +16 -0
  141. package/dest/l1_tx_utils/tx_delayer.d.ts +58 -0
  142. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  143. package/dest/l1_tx_utils/tx_delayer.js +223 -0
  144. package/dest/l1_tx_utils/types.d.ts +67 -0
  145. package/dest/l1_tx_utils/types.d.ts.map +1 -0
  146. package/dest/l1_tx_utils/types.js +26 -0
  147. package/dest/l1_tx_utils/utils.d.ts +4 -0
  148. package/dest/l1_tx_utils/utils.d.ts.map +1 -0
  149. package/dest/l1_tx_utils/utils.js +14 -0
  150. package/dest/l1_types.d.ts +6 -0
  151. package/dest/l1_types.d.ts.map +1 -0
  152. package/dest/l1_types.js +1 -0
  153. package/dest/publisher_manager.d.ts +30 -0
  154. package/dest/publisher_manager.d.ts.map +1 -0
  155. package/dest/publisher_manager.js +162 -0
  156. package/dest/queries.d.ts +17 -4
  157. package/dest/queries.d.ts.map +1 -1
  158. package/dest/queries.js +120 -12
  159. package/dest/test/chain_monitor.d.ts +95 -0
  160. package/dest/test/chain_monitor.d.ts.map +1 -0
  161. package/dest/test/chain_monitor.js +249 -0
  162. package/dest/test/eth_cheat_codes.d.ts +237 -0
  163. package/dest/test/eth_cheat_codes.d.ts.map +1 -0
  164. package/dest/test/eth_cheat_codes.js +573 -0
  165. package/dest/test/eth_cheat_codes_with_state.d.ts +2 -2
  166. package/dest/test/eth_cheat_codes_with_state.d.ts.map +1 -1
  167. package/dest/test/eth_cheat_codes_with_state.js +1 -1
  168. package/dest/test/index.d.ts +4 -3
  169. package/dest/test/index.d.ts.map +1 -1
  170. package/dest/test/index.js +3 -2
  171. package/dest/test/rollup_cheat_codes.d.ts +118 -0
  172. package/dest/test/rollup_cheat_codes.d.ts.map +1 -0
  173. package/dest/test/rollup_cheat_codes.js +344 -0
  174. package/dest/test/start_anvil.d.ts +30 -2
  175. package/dest/test/start_anvil.d.ts.map +1 -1
  176. package/dest/test/start_anvil.js +147 -24
  177. package/dest/test/upgrade_utils.d.ts +6 -5
  178. package/dest/test/upgrade_utils.d.ts.map +1 -1
  179. package/dest/test/upgrade_utils.js +23 -16
  180. package/dest/types.d.ts +62 -8
  181. package/dest/types.d.ts.map +1 -1
  182. package/dest/types.js +3 -1
  183. package/dest/utils.d.ts +25 -4
  184. package/dest/utils.d.ts.map +1 -1
  185. package/dest/utils.js +142 -91
  186. package/dest/zkPassportVerifierAddress.d.ts +15 -0
  187. package/dest/zkPassportVerifierAddress.d.ts.map +1 -0
  188. package/dest/zkPassportVerifierAddress.js +11 -0
  189. package/package.json +52 -26
  190. package/src/account.ts +5 -0
  191. package/src/client.ts +107 -5
  192. package/src/config.ts +218 -43
  193. package/src/contracts/README.md +157 -0
  194. package/src/contracts/chain_state_override.ts +200 -0
  195. package/src/contracts/empire_base.ts +77 -7
  196. package/src/contracts/errors.ts +13 -0
  197. package/src/contracts/fee_asset_handler.ts +66 -0
  198. package/src/contracts/fee_asset_price_oracle.ts +285 -0
  199. package/src/contracts/fee_juice.ts +29 -15
  200. package/src/contracts/governance.ts +343 -74
  201. package/src/contracts/governance_proposer.ts +128 -25
  202. package/src/contracts/gse.ts +88 -0
  203. package/src/contracts/inbox.ts +123 -0
  204. package/src/contracts/index.ts +10 -1
  205. package/src/contracts/log.ts +13 -0
  206. package/src/contracts/multicall.ts +296 -0
  207. package/src/contracts/outbox.ts +132 -0
  208. package/src/contracts/registry.ts +81 -26
  209. package/src/contracts/rollup.ts +1246 -90
  210. package/src/contracts/slasher_contract.ts +89 -0
  211. package/src/contracts/slashing_proposer.ts +296 -27
  212. package/src/contracts/utils.ts +14 -0
  213. package/src/deploy_aztec_l1_contracts.ts +646 -0
  214. package/src/deploy_l1_contract.ts +362 -0
  215. package/src/eth-signer/eth-signer.ts +25 -0
  216. package/src/eth-signer/index.ts +1 -0
  217. package/src/forwarder_proxy.ts +108 -0
  218. package/src/generated/l1-contracts-defaults.ts +32 -0
  219. package/src/l1_artifacts.ts +231 -0
  220. package/src/l1_contract_addresses.ts +121 -80
  221. package/src/l1_reader.ts +31 -16
  222. package/src/l1_tx_utils/README.md +177 -0
  223. package/src/l1_tx_utils/config.ts +181 -0
  224. package/src/l1_tx_utils/constants.ts +29 -0
  225. package/src/l1_tx_utils/factory.ts +64 -0
  226. package/src/l1_tx_utils/fee-strategies/index.ts +22 -0
  227. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +163 -0
  228. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +245 -0
  229. package/src/l1_tx_utils/fee-strategies/types.ts +56 -0
  230. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +108 -0
  231. package/src/l1_tx_utils/index-blobs.ts +2 -0
  232. package/src/l1_tx_utils/index.ts +15 -0
  233. package/src/l1_tx_utils/interfaces.ts +86 -0
  234. package/src/l1_tx_utils/l1_fee_analyzer.ts +803 -0
  235. package/src/l1_tx_utils/l1_tx_utils.ts +789 -0
  236. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +424 -0
  237. package/src/l1_tx_utils/signer.ts +28 -0
  238. package/src/l1_tx_utils/tx_delayer.ts +303 -0
  239. package/src/l1_tx_utils/types.ts +85 -0
  240. package/src/l1_tx_utils/utils.ts +16 -0
  241. package/src/l1_types.ts +6 -0
  242. package/src/publisher_manager.ts +203 -0
  243. package/src/queries.ts +149 -17
  244. package/src/test/chain_monitor.ts +307 -0
  245. package/src/test/eth_cheat_codes.ts +600 -0
  246. package/src/test/eth_cheat_codes_with_state.ts +1 -1
  247. package/src/test/index.ts +3 -2
  248. package/src/test/rollup_cheat_codes.ts +388 -0
  249. package/src/test/start_anvil.ts +189 -22
  250. package/src/test/upgrade_utils.ts +30 -21
  251. package/src/types.ts +71 -7
  252. package/src/utils.ts +164 -96
  253. package/src/zkPassportVerifierAddress.ts +15 -0
  254. package/dest/contracts/forwarder.d.ts +0 -24
  255. package/dest/contracts/forwarder.d.ts.map +0 -1
  256. package/dest/contracts/forwarder.js +0 -101
  257. package/dest/deploy_l1_contracts.d.ts +0 -21210
  258. package/dest/deploy_l1_contracts.d.ts.map +0 -1
  259. package/dest/deploy_l1_contracts.js +0 -687
  260. package/dest/eth_cheat_codes.d.ts +0 -147
  261. package/dest/eth_cheat_codes.d.ts.map +0 -1
  262. package/dest/eth_cheat_codes.js +0 -303
  263. package/dest/index.d.ts +0 -14
  264. package/dest/index.d.ts.map +0 -1
  265. package/dest/index.js +0 -13
  266. package/dest/l1_tx_utils.d.ts +0 -192
  267. package/dest/l1_tx_utils.d.ts.map +0 -1
  268. package/dest/l1_tx_utils.js +0 -641
  269. package/dest/l1_tx_utils_with_blobs.d.ts +0 -12
  270. package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
  271. package/dest/l1_tx_utils_with_blobs.js +0 -64
  272. package/dest/test/delayed_tx_utils.d.ts +0 -8
  273. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  274. package/dest/test/delayed_tx_utils.js +0 -21
  275. package/dest/test/tx_delayer.d.ts +0 -25
  276. package/dest/test/tx_delayer.d.ts.map +0 -1
  277. package/dest/test/tx_delayer.js +0 -116
  278. package/src/contracts/forwarder.ts +0 -132
  279. package/src/deploy_l1_contracts.ts +0 -948
  280. package/src/eth_cheat_codes.ts +0 -314
  281. package/src/index.ts +0 -13
  282. package/src/l1_tx_utils.ts +0 -847
  283. package/src/l1_tx_utils_with_blobs.ts +0 -86
  284. package/src/test/delayed_tx_utils.ts +0 -24
  285. package/src/test/tx_delayer.ts +0 -163
@@ -1,20 +1,36 @@
1
1
  import { EthAddress } from '@aztec/foundation/eth-address';
2
2
  import type { Logger } from '@aztec/foundation/log';
3
3
  import { sleep } from '@aztec/foundation/sleep';
4
- import { GovernanceAbi } from '@aztec/l1-artifacts';
4
+ import { GovernanceAbi } from '@aztec/l1-artifacts/GovernanceAbi';
5
5
 
6
6
  import {
7
7
  type EncodeFunctionDataParameters,
8
8
  type GetContractReturnType,
9
9
  type Hex,
10
+ type Log,
10
11
  encodeFunctionData,
11
12
  getContract,
13
+ parseEventLogs,
12
14
  } from 'viem';
13
15
 
14
16
  import type { L1ContractAddresses } from '../l1_contract_addresses.js';
15
- import { L1TxUtils } from '../l1_tx_utils.js';
16
- import type { ViemPublicClient, ViemWalletClient } from '../types.js';
17
- import { GovernanceProposerContract } from './governance_proposer.js';
17
+ import { createL1TxUtils } from '../l1_tx_utils/index.js';
18
+ import { type ExtendedViemWalletClient, type ViemClient, isExtendedClient } from '../types.js';
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;
18
34
 
19
35
  export type L1GovernanceContractAddresses = Pick<
20
36
  L1ContractAddresses,
@@ -33,85 +49,294 @@ export enum ProposalState {
33
49
  Expired,
34
50
  }
35
51
 
36
- export class GovernanceContract {
37
- private readonly publicGovernance: GetContractReturnType<typeof GovernanceAbi, ViemPublicClient>;
38
- private readonly walletGovernance: GetContractReturnType<typeof GovernanceAbi, ViemWalletClient> | undefined;
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
+
144
+ export function extractProposalIdFromLogs(logs: Log[]): bigint {
145
+ const parsedLogs = parseEventLogs({
146
+ abi: GovernanceAbi,
147
+ logs: logs,
148
+ eventName: 'Proposed',
149
+ });
150
+
151
+ if (parsedLogs.length === 0) {
152
+ throw new Error('Proposal log not found');
153
+ }
154
+ return parsedLogs[0].args.proposalId;
155
+ }
156
+
157
+ export class ReadOnlyGovernanceContract {
158
+ protected readonly governanceContract: GetContractReturnType<typeof GovernanceAbi, ViemClient>;
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();
39
175
 
40
176
  constructor(
41
177
  address: Hex,
42
- public readonly publicClient: ViemPublicClient,
43
- public readonly walletClient: ViemWalletClient | undefined,
178
+ public readonly client: ViemClient,
44
179
  ) {
45
- this.publicGovernance = getContract({ address, abi: GovernanceAbi, client: publicClient });
46
- this.walletGovernance = walletClient
47
- ? getContract({ address, abi: GovernanceAbi, client: walletClient })
48
- : undefined;
180
+ this.governanceContract = getContract({ address, abi: GovernanceAbi, client: client });
49
181
  }
50
182
 
51
183
  public get address() {
52
- return EthAddress.fromString(this.publicGovernance.address);
184
+ return EthAddress.fromString(this.governanceContract.address);
53
185
  }
54
186
 
55
- public async getProposer() {
56
- const governanceProposerAddress = EthAddress.fromString(await this.publicGovernance.read.governanceProposer());
57
- return new GovernanceProposerContract(this.publicClient, governanceProposerAddress.toString());
187
+ public async getGovernanceProposerAddress() {
188
+ return EthAddress.fromString(await this.governanceContract.read.governanceProposer());
58
189
  }
59
190
 
60
- public async getGovernanceAddresses(): Promise<L1GovernanceContractAddresses> {
61
- const governanceProposer = await this.getProposer();
62
- const [rollupAddress, registryAddress] = await Promise.all([
63
- governanceProposer.getRollupAddress(),
64
- governanceProposer.getRegistryAddress(),
65
- ]);
191
+ public async getConfiguration(): Promise<GovernanceConfiguration> {
192
+ const raw = await this.governanceContract.read.getConfiguration();
66
193
  return {
67
- governanceAddress: this.address,
68
- rollupAddress,
69
- registryAddress,
70
- governanceProposerAddress: governanceProposer.address,
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,
71
205
  };
72
206
  }
73
207
 
74
- public getProposal(proposalId: bigint) {
75
- return this.publicGovernance.read.getProposal([proposalId]);
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;
76
250
  }
77
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
+ */
78
257
  public async getProposalState(proposalId: bigint): Promise<ProposalState> {
79
- const state = await this.publicGovernance.read.getProposalState([proposalId]);
80
- if (state < 0 || state > ProposalState.Expired) {
81
- throw new Error(`Invalid proposal state: ${state}`);
82
- }
83
- return state as ProposalState;
258
+ return asProposalState(await this.governanceContract.read.getProposalState([proposalId]));
84
259
  }
85
260
 
86
- private assertWalletGovernance(): NonNullable<typeof this.walletGovernance> {
87
- if (!this.walletGovernance) {
88
- throw new Error('Wallet client is required for this operation');
89
- }
90
- return this.walletGovernance;
261
+ public getProposalCount(): Promise<bigint> {
262
+ return this.governanceContract.read.proposalCount();
91
263
  }
92
264
 
93
- public async deposit(onBehalfOf: Hex, amount: bigint) {
94
- const walletGovernance = this.assertWalletGovernance();
95
- const depositTx = await walletGovernance.write.deposit([onBehalfOf, amount]);
96
- await this.publicClient.waitForTransactionReceipt({ hash: depositTx });
97
- }
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
+ }
98
283
 
99
- public async proposeWithLock({
100
- payloadAddress,
101
- withdrawAddress,
102
- }: {
103
- payloadAddress: Hex;
104
- withdrawAddress: Hex;
105
- }): Promise<number> {
106
- const walletGovernance = this.assertWalletGovernance();
107
- const proposeTx = await walletGovernance.write.proposeWithLock([payloadAddress, withdrawAddress]);
108
- const receipt = await this.publicClient.waitForTransactionReceipt({ hash: proposeTx });
109
- if (receipt.status !== 'success') {
110
- throw new Error(`Proposal failed: ${receipt.status}`);
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;
111
312
  }
112
313
 
113
- const proposalId = Number(receipt.logs[1].topics[1]);
114
- return proposalId;
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;
337
+ }
338
+ this.originalPayloadCache.set(key, original);
339
+ return original;
115
340
  }
116
341
 
117
342
  public async awaitProposalActive({ proposalId, logger }: { proposalId: bigint; logger: Logger }) {
@@ -123,7 +348,7 @@ export class GovernanceContract {
123
348
  } else {
124
349
  const proposal = await this.getProposal(proposalId);
125
350
  const startOfActive = proposal.creation + proposal.config.votingDelay;
126
- const block = await this.publicClient.getBlock();
351
+ const block = await this.client.getBlock();
127
352
  // Add 12 seconds to the time to make sure we don't vote too early
128
353
  const secondsToActive = Number(startOfActive - block.timestamp) + 12;
129
354
  const now = new Date();
@@ -151,7 +376,7 @@ export class GovernanceContract {
151
376
  proposal.config.votingDelay +
152
377
  proposal.config.votingDuration +
153
378
  proposal.config.executionDelay;
154
- const block = await this.publicClient.getBlock();
379
+ const block = await this.client.getBlock();
155
380
  const secondsToExecutable = Number(startOfExecutable - block.timestamp) + 12;
156
381
  const now = new Date();
157
382
  logger.info(`
@@ -162,11 +387,55 @@ export class GovernanceContract {
162
387
  await sleep(secondsToExecutable * 1000);
163
388
  }
164
389
  }
390
+ }
391
+
392
+ export class GovernanceContract extends ReadOnlyGovernanceContract {
393
+ protected override readonly governanceContract: GetContractReturnType<typeof GovernanceAbi, ExtendedViemWalletClient>;
394
+
395
+ constructor(
396
+ address: Hex | EthAddress,
397
+ public override readonly client: ExtendedViemWalletClient,
398
+ ) {
399
+ if (address instanceof EthAddress) {
400
+ address = address.toString();
401
+ }
402
+ super(address, client);
403
+ if (!isExtendedClient(client)) {
404
+ throw new Error('GovernanceContract has to be instantiated with a wallet client.');
405
+ }
406
+ this.governanceContract = getContract({ address, abi: GovernanceAbi, client });
407
+ }
408
+
409
+ public async deposit(onBehalfOf: Hex, amount: bigint) {
410
+ const depositTx = await this.governanceContract.write.deposit([onBehalfOf, amount]);
411
+ await this.client.waitForTransactionReceipt({ hash: depositTx });
412
+ }
413
+
414
+ public async proposeWithLock({
415
+ payloadAddress,
416
+ withdrawAddress,
417
+ }: {
418
+ payloadAddress: Hex;
419
+ withdrawAddress: Hex;
420
+ }): Promise<bigint> {
421
+ const proposeTx = await this.governanceContract.write.proposeWithLock([payloadAddress, withdrawAddress]);
422
+ const receipt = await this.client.waitForTransactionReceipt({ hash: proposeTx });
423
+ if (receipt.status !== 'success') {
424
+ throw new Error(`Proposal failed: ${receipt.status}`);
425
+ }
426
+ return extractProposalIdFromLogs(receipt.logs);
427
+ }
165
428
 
166
429
  public async getPower(): Promise<bigint> {
167
- const walletGovernance = this.assertWalletGovernance();
168
- const now = await this.publicClient.getBlock();
169
- return walletGovernance.read.powerAt([this.walletClient!.account.address, now.timestamp]);
430
+ const now = await this.client.getBlock();
431
+ return this.governanceContract.read.powerAt([this.client.account.address, now.timestamp]);
432
+ }
433
+
434
+ /** Returns the user's voting power for a specific proposal, checked at pendingThrough timestamp. */
435
+ public async getPowerForProposal(proposalId: bigint): Promise<bigint> {
436
+ const proposal = await this.getProposal(proposalId);
437
+ const pendingThrough = proposal.creation + proposal.config.votingDelay;
438
+ return this.governanceContract.read.powerAt([this.client.account.address, pendingThrough]);
170
439
  }
171
440
 
172
441
  public async vote({
@@ -182,11 +451,10 @@ export class GovernanceContract {
182
451
  retries: number;
183
452
  logger: Logger;
184
453
  }) {
185
- const walletGovernance = this.assertWalletGovernance();
186
- const l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient!, logger);
454
+ const l1TxUtils = createL1TxUtils(this.client, { logger });
187
455
  const retryDelaySeconds = 12;
188
456
 
189
- voteAmount = voteAmount ?? (await this.getPower());
457
+ voteAmount = voteAmount ?? (await this.getPowerForProposal(proposalId));
190
458
 
191
459
  let success = false;
192
460
  for (let i = 0; i < retries; i++) {
@@ -199,7 +467,8 @@ export class GovernanceContract {
199
467
  const encodedVoteData = encodeFunctionData(voteFunctionData);
200
468
 
201
469
  const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
202
- to: walletGovernance.address,
470
+ to: this.governanceContract.address,
471
+ abi: GovernanceAbi,
203
472
  data: encodedVoteData,
204
473
  });
205
474
 
@@ -209,7 +478,7 @@ export class GovernanceContract {
209
478
  } else {
210
479
  const args = {
211
480
  ...voteFunctionData,
212
- address: walletGovernance.address,
481
+ address: this.governanceContract.address,
213
482
  };
214
483
  const errorMsg = await l1TxUtils.tryGetErrorFromRevertedTx(encodedVoteData, args, undefined, []);
215
484
  logger.error(`Error voting on proposal ${proposalId}: ${errorMsg}`);
@@ -226,9 +495,9 @@ export class GovernanceContract {
226
495
  }
227
496
  logger.info(`Voted [${inFavor ? 'yea' : 'nay'}] on proposal [${proposalId}]`);
228
497
  const proposal = await this.getProposal(proposalId);
229
- logger.info(`Proposal [${proposalId}] has state [${proposal.state}]`);
498
+ logger.info(`Proposal [${proposalId}] has cached state [${proposal.cachedState}]`);
230
499
  logger.info(`Proposal [${proposalId}] has summedBallot yea [${proposal.summedBallot.yea}]`);
231
- logger.info(`Proposal [${proposalId}] has summedBallot nea [${proposal.summedBallot.nea}]`);
500
+ logger.info(`Proposal [${proposalId}] has summedBallot nay [${proposal.summedBallot.nay}]`);
232
501
  }
233
502
 
234
503
  public async executeProposal({
@@ -240,8 +509,7 @@ export class GovernanceContract {
240
509
  retries: number;
241
510
  logger: Logger;
242
511
  }) {
243
- const walletGovernance = this.assertWalletGovernance();
244
- const l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient!, logger);
512
+ const l1TxUtils = createL1TxUtils(this.client, { logger });
245
513
  const retryDelaySeconds = 12;
246
514
  let success = false;
247
515
  for (let i = 0; i < retries; i++) {
@@ -254,7 +522,8 @@ export class GovernanceContract {
254
522
  const encodedExecuteData = encodeFunctionData(executeFunctionData);
255
523
 
256
524
  const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
257
- to: walletGovernance.address,
525
+ to: this.governanceContract.address,
526
+ abi: GovernanceAbi,
258
527
  data: encodedExecuteData,
259
528
  });
260
529
 
@@ -264,7 +533,7 @@ export class GovernanceContract {
264
533
  } else {
265
534
  const args = {
266
535
  ...executeFunctionData,
267
- address: walletGovernance.address,
536
+ address: this.governanceContract.address,
268
537
  };
269
538
  const errorMsg = await l1TxUtils.tryGetErrorFromRevertedTx(encodedExecuteData, args, undefined, []);
270
539
  logger.error(`Error executing proposal ${proposalId}: ${errorMsg}`);