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

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 (229) 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/client.d.ts +5 -3
  5. package/dest/client.d.ts.map +1 -1
  6. package/dest/client.js +16 -2
  7. package/dest/config.d.ts +107 -16
  8. package/dest/config.d.ts.map +1 -1
  9. package/dest/config.js +456 -22
  10. package/dest/contracts/empire_base.d.ts +21 -6
  11. package/dest/contracts/empire_base.d.ts.map +1 -1
  12. package/dest/contracts/empire_base.js +75 -2
  13. package/dest/contracts/empire_slashing_proposer.d.ts +65 -0
  14. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -0
  15. package/dest/contracts/empire_slashing_proposer.js +194 -0
  16. package/dest/contracts/errors.d.ts +7 -0
  17. package/dest/contracts/errors.d.ts.map +1 -0
  18. package/dest/contracts/errors.js +12 -0
  19. package/dest/contracts/fee_asset_handler.d.ts +19 -0
  20. package/dest/contracts/fee_asset_handler.d.ts.map +1 -0
  21. package/dest/contracts/fee_asset_handler.js +57 -0
  22. package/dest/contracts/fee_juice.d.ts +5 -6
  23. package/dest/contracts/fee_juice.d.ts.map +1 -1
  24. package/dest/contracts/fee_juice.js +27 -20
  25. package/dest/contracts/governance.d.ts +36 -25
  26. package/dest/contracts/governance.d.ts.map +1 -1
  27. package/dest/contracts/governance.js +87 -84
  28. package/dest/contracts/governance_proposer.d.ts +13 -11
  29. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  30. package/dest/contracts/governance_proposer.js +32 -18
  31. package/dest/contracts/gse.d.ts +32 -0
  32. package/dest/contracts/gse.d.ts.map +1 -0
  33. package/dest/contracts/gse.js +72 -0
  34. package/dest/contracts/inbox.d.ts +26 -0
  35. package/dest/contracts/inbox.d.ts.map +1 -0
  36. package/dest/contracts/inbox.js +45 -0
  37. package/dest/contracts/index.d.ts +8 -2
  38. package/dest/contracts/index.d.ts.map +1 -1
  39. package/dest/contracts/index.js +8 -2
  40. package/dest/contracts/multicall.d.ts +21 -0
  41. package/dest/contracts/multicall.d.ts.map +1 -0
  42. package/dest/contracts/multicall.js +156 -0
  43. package/dest/contracts/registry.d.ts +9 -4
  44. package/dest/contracts/registry.d.ts.map +1 -1
  45. package/dest/contracts/registry.js +44 -16
  46. package/dest/contracts/rollup.d.ts +202 -29
  47. package/dest/contracts/rollup.d.ts.map +1 -1
  48. package/dest/contracts/rollup.js +500 -55
  49. package/dest/contracts/slasher_contract.d.ts +44 -0
  50. package/dest/contracts/slasher_contract.d.ts.map +1 -0
  51. package/dest/contracts/slasher_contract.js +75 -0
  52. package/dest/contracts/tally_slashing_proposer.d.ts +138 -0
  53. package/dest/contracts/tally_slashing_proposer.d.ts.map +1 -0
  54. package/dest/contracts/tally_slashing_proposer.js +313 -0
  55. package/dest/contracts/utils.d.ts +3 -0
  56. package/dest/contracts/utils.d.ts.map +1 -0
  57. package/dest/contracts/utils.js +11 -0
  58. package/dest/deploy_l1_contracts.d.ts +128 -21112
  59. package/dest/deploy_l1_contracts.d.ts.map +1 -1
  60. package/dest/deploy_l1_contracts.js +1204 -418
  61. package/dest/eth-signer/eth-signer.d.ts +21 -0
  62. package/dest/eth-signer/eth-signer.d.ts.map +1 -0
  63. package/dest/eth-signer/eth-signer.js +5 -0
  64. package/dest/eth-signer/index.d.ts +2 -0
  65. package/dest/eth-signer/index.d.ts.map +1 -0
  66. package/dest/eth-signer/index.js +1 -0
  67. package/dest/index.d.ts +6 -2
  68. package/dest/index.d.ts.map +1 -1
  69. package/dest/index.js +6 -2
  70. package/dest/l1_artifacts.d.ts +76184 -0
  71. package/dest/l1_artifacts.d.ts.map +1 -0
  72. package/dest/l1_artifacts.js +166 -0
  73. package/dest/l1_contract_addresses.d.ts +21 -1
  74. package/dest/l1_contract_addresses.d.ts.map +1 -1
  75. package/dest/l1_contract_addresses.js +22 -18
  76. package/dest/l1_reader.d.ts +1 -1
  77. package/dest/l1_reader.d.ts.map +1 -1
  78. package/dest/l1_reader.js +8 -8
  79. package/dest/l1_tx_utils/config.d.ts +59 -0
  80. package/dest/l1_tx_utils/config.d.ts.map +1 -0
  81. package/dest/l1_tx_utils/config.js +73 -0
  82. package/dest/l1_tx_utils/constants.d.ts +6 -0
  83. package/dest/l1_tx_utils/constants.d.ts.map +1 -0
  84. package/dest/l1_tx_utils/constants.js +14 -0
  85. package/dest/l1_tx_utils/factory.d.ts +24 -0
  86. package/dest/l1_tx_utils/factory.d.ts.map +1 -0
  87. package/dest/l1_tx_utils/factory.js +12 -0
  88. package/dest/l1_tx_utils/index.d.ts +10 -0
  89. package/dest/l1_tx_utils/index.d.ts.map +1 -0
  90. package/dest/l1_tx_utils/index.js +10 -0
  91. package/dest/l1_tx_utils/interfaces.d.ts +76 -0
  92. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
  93. package/dest/l1_tx_utils/interfaces.js +4 -0
  94. package/dest/l1_tx_utils/l1_tx_utils.d.ts +95 -0
  95. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
  96. package/dest/l1_tx_utils/l1_tx_utils.js +610 -0
  97. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +26 -0
  98. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -0
  99. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +26 -0
  100. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +94 -0
  101. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
  102. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +431 -0
  103. package/dest/l1_tx_utils/signer.d.ts +4 -0
  104. package/dest/l1_tx_utils/signer.d.ts.map +1 -0
  105. package/dest/l1_tx_utils/signer.js +16 -0
  106. package/dest/l1_tx_utils/types.d.ts +67 -0
  107. package/dest/l1_tx_utils/types.d.ts.map +1 -0
  108. package/dest/l1_tx_utils/types.js +26 -0
  109. package/dest/l1_tx_utils/utils.d.ts +4 -0
  110. package/dest/l1_tx_utils/utils.d.ts.map +1 -0
  111. package/dest/l1_tx_utils/utils.js +14 -0
  112. package/dest/l1_types.d.ts +6 -0
  113. package/dest/l1_types.d.ts.map +1 -0
  114. package/dest/l1_types.js +1 -0
  115. package/dest/publisher_manager.d.ts +15 -0
  116. package/dest/publisher_manager.d.ts.map +1 -0
  117. package/dest/publisher_manager.js +88 -0
  118. package/dest/queries.d.ts +3 -1
  119. package/dest/queries.d.ts.map +1 -1
  120. package/dest/queries.js +51 -12
  121. package/dest/test/chain_monitor.d.ts +72 -0
  122. package/dest/test/chain_monitor.d.ts.map +1 -0
  123. package/dest/test/chain_monitor.js +216 -0
  124. package/dest/test/delayed_tx_utils.d.ts +7 -2
  125. package/dest/test/delayed_tx_utils.d.ts.map +1 -1
  126. package/dest/test/delayed_tx_utils.js +13 -6
  127. package/dest/{eth_cheat_codes.d.ts → test/eth_cheat_codes.d.ts} +87 -13
  128. package/dest/test/eth_cheat_codes.d.ts.map +1 -0
  129. package/dest/test/eth_cheat_codes.js +552 -0
  130. package/dest/test/eth_cheat_codes_with_state.d.ts +1 -1
  131. package/dest/test/eth_cheat_codes_with_state.d.ts.map +1 -1
  132. package/dest/test/eth_cheat_codes_with_state.js +1 -1
  133. package/dest/test/index.d.ts +3 -0
  134. package/dest/test/index.d.ts.map +1 -1
  135. package/dest/test/index.js +3 -0
  136. package/dest/test/rollup_cheat_codes.d.ts +86 -0
  137. package/dest/test/rollup_cheat_codes.d.ts.map +1 -0
  138. package/dest/test/rollup_cheat_codes.js +268 -0
  139. package/dest/test/start_anvil.d.ts +5 -0
  140. package/dest/test/start_anvil.d.ts.map +1 -1
  141. package/dest/test/start_anvil.js +15 -7
  142. package/dest/test/tx_delayer.d.ts +17 -6
  143. package/dest/test/tx_delayer.d.ts.map +1 -1
  144. package/dest/test/tx_delayer.js +95 -19
  145. package/dest/test/upgrade_utils.d.ts +5 -4
  146. package/dest/test/upgrade_utils.d.ts.map +1 -1
  147. package/dest/test/upgrade_utils.js +23 -16
  148. package/dest/types.d.ts +6 -7
  149. package/dest/types.d.ts.map +1 -1
  150. package/dest/types.js +3 -1
  151. package/dest/utils.d.ts +1 -0
  152. package/dest/utils.d.ts.map +1 -1
  153. package/dest/utils.js +43 -88
  154. package/dest/zkPassportVerifierAddress.d.ts +15 -0
  155. package/dest/zkPassportVerifierAddress.d.ts.map +1 -0
  156. package/dest/zkPassportVerifierAddress.js +11 -0
  157. package/package.json +24 -16
  158. package/src/account.ts +5 -0
  159. package/src/client.ts +42 -4
  160. package/src/config.ts +584 -31
  161. package/src/contracts/empire_base.ts +75 -6
  162. package/src/contracts/empire_slashing_proposer.ts +259 -0
  163. package/src/contracts/errors.ts +13 -0
  164. package/src/contracts/fee_asset_handler.ts +63 -0
  165. package/src/contracts/fee_juice.ts +29 -15
  166. package/src/contracts/governance.ts +80 -77
  167. package/src/contracts/governance_proposer.ts +60 -24
  168. package/src/contracts/gse.ts +88 -0
  169. package/src/contracts/inbox.ts +63 -0
  170. package/src/contracts/index.ts +8 -2
  171. package/src/contracts/multicall.ts +155 -0
  172. package/src/contracts/registry.ts +51 -26
  173. package/src/contracts/rollup.ts +585 -56
  174. package/src/contracts/slasher_contract.ts +89 -0
  175. package/src/contracts/tally_slashing_proposer.ts +315 -0
  176. package/src/contracts/utils.ts +14 -0
  177. package/src/deploy_l1_contracts.ts +1467 -566
  178. package/src/eth-signer/eth-signer.ts +25 -0
  179. package/src/eth-signer/index.ts +1 -0
  180. package/src/index.ts +6 -2
  181. package/src/l1_artifacts.ts +254 -0
  182. package/src/l1_contract_addresses.ts +32 -19
  183. package/src/l1_reader.ts +9 -9
  184. package/src/l1_tx_utils/README.md +177 -0
  185. package/src/l1_tx_utils/config.ts +140 -0
  186. package/src/l1_tx_utils/constants.ts +18 -0
  187. package/src/l1_tx_utils/factory.ts +64 -0
  188. package/src/l1_tx_utils/index.ts +12 -0
  189. package/src/l1_tx_utils/interfaces.ts +86 -0
  190. package/src/l1_tx_utils/l1_tx_utils.ts +718 -0
  191. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +77 -0
  192. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +559 -0
  193. package/src/l1_tx_utils/signer.ts +28 -0
  194. package/src/l1_tx_utils/types.ts +85 -0
  195. package/src/l1_tx_utils/utils.ts +16 -0
  196. package/src/l1_types.ts +6 -0
  197. package/src/publisher_manager.ts +106 -0
  198. package/src/queries.ts +70 -15
  199. package/src/test/chain_monitor.ts +243 -0
  200. package/src/test/delayed_tx_utils.ts +34 -6
  201. package/src/test/eth_cheat_codes.ts +582 -0
  202. package/src/test/eth_cheat_codes_with_state.ts +1 -1
  203. package/src/test/index.ts +3 -0
  204. package/src/test/rollup_cheat_codes.ts +310 -0
  205. package/src/test/start_anvil.ts +20 -5
  206. package/src/test/tx_delayer.ts +127 -26
  207. package/src/test/upgrade_utils.ts +30 -21
  208. package/src/types.ts +10 -8
  209. package/src/utils.ts +49 -90
  210. package/src/zkPassportVerifierAddress.ts +15 -0
  211. package/dest/contracts/forwarder.d.ts +0 -24
  212. package/dest/contracts/forwarder.d.ts.map +0 -1
  213. package/dest/contracts/forwarder.js +0 -101
  214. package/dest/contracts/slashing_proposer.d.ts +0 -21
  215. package/dest/contracts/slashing_proposer.d.ts.map +0 -1
  216. package/dest/contracts/slashing_proposer.js +0 -47
  217. package/dest/eth_cheat_codes.d.ts.map +0 -1
  218. package/dest/eth_cheat_codes.js +0 -303
  219. package/dest/l1_tx_utils.d.ts +0 -192
  220. package/dest/l1_tx_utils.d.ts.map +0 -1
  221. package/dest/l1_tx_utils.js +0 -641
  222. package/dest/l1_tx_utils_with_blobs.d.ts +0 -12
  223. package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
  224. package/dest/l1_tx_utils_with_blobs.js +0 -64
  225. package/src/contracts/forwarder.ts +0 -132
  226. package/src/contracts/slashing_proposer.ts +0 -51
  227. package/src/eth_cheat_codes.ts +0 -314
  228. package/src/l1_tx_utils.ts +0 -847
  229. package/src/l1_tx_utils_with_blobs.ts +0 -86
@@ -1,93 +1,98 @@
1
+ import { L1_TO_L2_MSG_SUBTREE_HEIGHT } from '@aztec/constants';
2
+ import { SecretValue, getActiveNetworkName } from '@aztec/foundation/config';
3
+ import { keccak256String } from '@aztec/foundation/crypto';
1
4
  import { EthAddress } from '@aztec/foundation/eth-address';
2
5
  import type { Fr } from '@aztec/foundation/fields';
6
+ import { jsonStringify } from '@aztec/foundation/json-rpc';
3
7
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
- import {
5
- CoinIssuerAbi,
6
- CoinIssuerBytecode,
7
- ExtRollupLibAbi,
8
- ExtRollupLibBytecode,
9
- FeeJuicePortalAbi,
10
- FeeJuicePortalBytecode,
11
- ForwarderAbi,
12
- ForwarderBytecode,
13
- GovernanceAbi,
14
- GovernanceBytecode,
15
- GovernanceProposerAbi,
16
- GovernanceProposerBytecode,
17
- InboxAbi,
18
- InboxBytecode,
19
- OutboxAbi,
20
- OutboxBytecode,
21
- RegisterNewRollupVersionPayloadAbi,
22
- RegisterNewRollupVersionPayloadBytecode,
23
- RegistryAbi,
24
- RegistryBytecode,
25
- RewardDistributorAbi,
26
- RewardDistributorBytecode,
27
- RollupAbi,
28
- RollupBytecode,
29
- RollupLinkReferences,
30
- SlashFactoryAbi,
31
- SlashFactoryBytecode,
32
- TestERC20Abi,
33
- TestERC20Bytecode,
34
- ValidatorSelectionLibAbi,
35
- ValidatorSelectionLibBytecode,
36
- } from '@aztec/l1-artifacts';
8
+ import { DateProvider } from '@aztec/foundation/timer';
9
+ import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
37
10
 
38
11
  import type { Abi, Narrow } from 'abitype';
12
+ import { mkdir, writeFile } from 'fs/promises';
13
+ import chunk from 'lodash.chunk';
39
14
  import {
40
15
  type Chain,
16
+ type ContractConstructorArgs,
17
+ type HDAccount,
41
18
  type Hex,
19
+ type PrivateKeyAccount,
42
20
  concatHex,
43
- createPublicClient,
44
- createWalletClient,
21
+ encodeAbiParameters,
45
22
  encodeDeployData,
46
23
  encodeFunctionData,
47
- fallback,
48
24
  getAddress,
49
25
  getContract,
50
26
  getContractAddress,
51
- http,
52
27
  numberToHex,
53
28
  padHex,
54
- publicActions,
55
29
  } from 'viem';
56
- import { type HDAccount, type PrivateKeyAccount, mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
57
30
  import { foundry } from 'viem/chains';
58
31
 
59
32
  import { isAnvilTestChain } from './chain.js';
60
- import type { L1ContractsConfig } from './config.js';
33
+ import { createExtendedL1Client } from './client.js';
34
+ import {
35
+ type L1ContractsConfig,
36
+ getEntryQueueConfig,
37
+ getGovernanceConfiguration,
38
+ getRewardBoostConfig,
39
+ getRewardConfig,
40
+ validateConfig,
41
+ } from './config.js';
42
+ import { GSEContract } from './contracts/gse.js';
43
+ import { deployMulticall3 } from './contracts/multicall.js';
61
44
  import { RegistryContract } from './contracts/registry.js';
62
- import { RollupContract } from './contracts/rollup.js';
45
+ import { RollupContract, SlashingProposerType } from './contracts/rollup.js';
46
+ import {
47
+ CoinIssuerArtifact,
48
+ DateGatedRelayerArtifact,
49
+ FeeAssetArtifact,
50
+ FeeAssetHandlerArtifact,
51
+ GSEArtifact,
52
+ GovernanceArtifact,
53
+ GovernanceProposerArtifact,
54
+ MultiAdderArtifact,
55
+ RegisterNewRollupVersionPayloadArtifact,
56
+ RegistryArtifact,
57
+ RollupArtifact,
58
+ SlashFactoryArtifact,
59
+ StakingAssetArtifact,
60
+ StakingAssetHandlerArtifact,
61
+ l1ArtifactsVerifiers,
62
+ mockVerifiers,
63
+ } from './l1_artifacts.js';
63
64
  import type { L1ContractAddresses } from './l1_contract_addresses.js';
64
65
  import {
65
66
  type GasPrice,
67
+ type L1TxConfig,
66
68
  type L1TxRequest,
67
69
  L1TxUtils,
68
70
  type L1TxUtilsConfig,
69
- defaultL1TxUtilsConfig,
70
- } from './l1_tx_utils.js';
71
- import type { L1Clients, ViemPublicClient, ViemWalletClient } from './types.js';
71
+ createL1TxUtilsFromViemWallet,
72
+ getL1TxUtilsConfigEnvVars,
73
+ } from './l1_tx_utils/index.js';
74
+ import type { ExtendedViemWalletClient } from './types.js';
75
+ import { formatViemError } from './utils.js';
76
+ import { ZK_PASSPORT_DOMAIN, ZK_PASSPORT_SCOPE, ZK_PASSPORT_VERIFIER_ADDRESS } from './zkPassportVerifierAddress.js';
72
77
 
73
78
  export const DEPLOYER_ADDRESS: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
74
79
 
80
+ export type Operator = {
81
+ attester: EthAddress;
82
+ withdrawer: EthAddress;
83
+ bn254SecretKey: SecretValue<bigint>;
84
+ };
85
+
75
86
  /**
76
87
  * Return type of the deployL1Contract function.
77
88
  */
78
89
  export type DeployL1ContractsReturnType = {
79
- /**
80
- * Wallet Client Type.
81
- */
82
- walletClient: ViemWalletClient;
83
- /**
84
- * Public Client Type.
85
- */
86
- publicClient: ViemPublicClient;
87
- /**
88
- * The currently deployed l1 contract addresses
89
- */
90
+ /** Extended Wallet Client Type. */
91
+ l1Client: ExtendedViemWalletClient;
92
+ /** The currently deployed l1 contract addresses */
90
93
  l1ContractAddresses: L1ContractAddresses;
94
+ /** Version of the current rollup contract. */
95
+ rollupVersion: number;
91
96
  };
92
97
 
93
98
  export interface LinkReferences {
@@ -107,11 +112,15 @@ export interface Libraries {
107
112
  /**
108
113
  * Contract artifacts
109
114
  */
110
- export interface ContractArtifacts {
115
+ export interface ContractArtifacts<TAbi extends Abi | readonly unknown[] = Abi> {
116
+ /**
117
+ * The contract name.
118
+ */
119
+ name: string;
111
120
  /**
112
121
  * The contract abi.
113
122
  */
114
- contractAbi: Narrow<Abi | readonly unknown[]>;
123
+ contractAbi: Narrow<TAbi>;
115
124
  /**
116
125
  * The contract bytecode
117
126
  */
@@ -122,305 +131,1247 @@ export interface ContractArtifacts {
122
131
  libraries?: Libraries;
123
132
  }
124
133
 
125
- export const l1Artifacts = {
126
- registry: {
127
- contractAbi: RegistryAbi,
128
- contractBytecode: RegistryBytecode as Hex,
129
- },
130
- inbox: {
131
- contractAbi: InboxAbi,
132
- contractBytecode: InboxBytecode as Hex,
133
- },
134
- outbox: {
135
- contractAbi: OutboxAbi,
136
- contractBytecode: OutboxBytecode as Hex,
137
- },
138
- rollup: {
139
- contractAbi: RollupAbi,
140
- contractBytecode: RollupBytecode as Hex,
141
- libraries: {
142
- linkReferences: RollupLinkReferences,
143
- libraryCode: {
144
- ValidatorSelectionLib: {
145
- contractAbi: ValidatorSelectionLibAbi,
146
- contractBytecode: ValidatorSelectionLibBytecode as Hex,
147
- },
148
- ExtRollupLib: {
149
- contractAbi: ExtRollupLibAbi,
150
- contractBytecode: ExtRollupLibBytecode as Hex,
151
- },
152
- },
153
- },
154
- },
155
- stakingAsset: {
156
- contractAbi: TestERC20Abi,
157
- contractBytecode: TestERC20Bytecode as Hex,
158
- },
159
- feeAsset: {
160
- contractAbi: TestERC20Abi,
161
- contractBytecode: TestERC20Bytecode as Hex,
162
- },
163
- feeJuicePortal: {
164
- contractAbi: FeeJuicePortalAbi,
165
- contractBytecode: FeeJuicePortalBytecode as Hex,
166
- },
167
- rewardDistributor: {
168
- contractAbi: RewardDistributorAbi,
169
- contractBytecode: RewardDistributorBytecode as Hex,
170
- },
171
- coinIssuer: {
172
- contractAbi: CoinIssuerAbi,
173
- contractBytecode: CoinIssuerBytecode as Hex,
174
- },
175
- governanceProposer: {
176
- contractAbi: GovernanceProposerAbi,
177
- contractBytecode: GovernanceProposerBytecode as Hex,
178
- },
179
- governance: {
180
- contractAbi: GovernanceAbi,
181
- contractBytecode: GovernanceBytecode as Hex,
182
- },
183
- slashFactory: {
184
- contractAbi: SlashFactoryAbi,
185
- contractBytecode: SlashFactoryBytecode as Hex,
186
- },
187
- registerNewRollupVersionPayload: {
188
- contractAbi: RegisterNewRollupVersionPayloadAbi,
189
- contractBytecode: RegisterNewRollupVersionPayloadBytecode as Hex,
190
- },
134
+ export type VerificationLibraryEntry = {
135
+ file: string;
136
+ contract: string;
137
+ address: string;
191
138
  };
192
139
 
193
- export interface DeployL1ContractsArgs extends L1ContractsConfig {
194
- /**
195
- * The address of the L2 Fee Juice contract.
196
- * It should be an AztecAddress, but the type is defined in stdlib,
197
- * which would create a circular import
198
- * */
199
- l2FeeJuiceAddress: Fr;
140
+ export type VerificationRecord = {
141
+ name: string;
142
+ address: string;
143
+ constructorArgsHex: Hex;
144
+ libraries: VerificationLibraryEntry[];
145
+ };
146
+
147
+ export interface DeployL1ContractsArgs extends Omit<L1ContractsConfig, keyof L1TxUtilsConfig> {
200
148
  /** The vk tree root. */
201
149
  vkTreeRoot: Fr;
202
- /** The protocol contract tree root. */
203
- protocolContractTreeRoot: Fr;
150
+ /** The hash of the protocol contracts. */
151
+ protocolContractsHash: Fr;
204
152
  /** The genesis root of the archive tree. */
205
153
  genesisArchiveRoot: Fr;
206
- /** The hash of the genesis block header. */
207
- genesisBlockHash: Fr;
208
154
  /** The salt for CREATE2 deployment. */
209
155
  salt: number | undefined;
210
156
  /** The initial validators for the rollup contract. */
211
- initialValidators?: EthAddress[];
157
+ initialValidators?: Operator[];
212
158
  /** Configuration for the L1 tx utils module. */
213
159
  l1TxConfig?: Partial<L1TxUtilsConfig>;
214
160
  /** Enable fast mode for deployments (fire and forget transactions) */
215
161
  acceleratedTestDeployments?: boolean;
162
+ /** The initial balance of the fee juice portal. This is the amount of fee juice that is prefunded to accounts */
163
+ feeJuicePortalInitialBalance?: bigint;
164
+ /** Whether to deploy the real verifier or the mock verifier */
165
+ realVerifier: boolean;
166
+ /** The zk passport args */
167
+ zkPassportArgs?: ZKPassportArgs;
168
+ /** If provided, use this token for BOTH fee and staking assets (skip deployments) */
169
+ existingTokenAddress?: EthAddress;
170
+ }
171
+
172
+ export interface ZKPassportArgs {
173
+ /** Whether to use the mock zk passport verifier */
174
+ mockZkPassportVerifier?: boolean;
175
+ /** The domain of the zk passport (url) */
176
+ zkPassportDomain?: string;
177
+ /** The scope of the zk passport (personhood, etc) */
178
+ zkPassportScope?: string;
216
179
  }
217
180
 
181
+ // Minimal ERC20 ABI for validation purposes. We only read view methods.
182
+ const ERC20_VALIDATION_ABI = [
183
+ {
184
+ type: 'function',
185
+ name: 'totalSupply',
186
+ stateMutability: 'view',
187
+ inputs: [],
188
+ outputs: [{ name: '', type: 'uint256' }],
189
+ },
190
+ {
191
+ type: 'function',
192
+ name: 'name',
193
+ stateMutability: 'view',
194
+ inputs: [],
195
+ outputs: [{ name: '', type: 'string' }],
196
+ },
197
+ {
198
+ type: 'function',
199
+ name: 'symbol',
200
+ stateMutability: 'view',
201
+ inputs: [],
202
+ outputs: [{ name: '', type: 'string' }],
203
+ },
204
+ {
205
+ type: 'function',
206
+ name: 'decimals',
207
+ stateMutability: 'view',
208
+ inputs: [],
209
+ outputs: [{ name: '', type: 'uint8' }],
210
+ },
211
+ ] as const;
212
+
218
213
  /**
219
- * Creates a wallet and a public viem client for interacting with L1.
220
- * @param rpcUrls - List of RPC URLs to connect to L1.
221
- * @param mnemonicOrPrivateKeyOrHdAccount - Mnemonic or account for the wallet client.
222
- * @param chain - Optional chain spec (defaults to local foundry).
223
- * @param addressIndex - Optional index of the address to use from the mnemonic.
224
- * @returns - A wallet and a public client.
214
+ * Validates that the provided address points to a contract that resembles an ERC20 token.
215
+ * Checks for contract code and attempts common ERC20 view calls.
216
+ * Throws an error if validation fails.
225
217
  */
226
- export function createL1Clients(
227
- rpcUrls: string[],
228
- mnemonicOrPrivateKeyOrHdAccount: string | `0x${string}` | HDAccount | PrivateKeyAccount,
229
- chain: Chain = foundry,
230
- addressIndex?: number,
231
- ): L1Clients {
232
- const hdAccount =
233
- typeof mnemonicOrPrivateKeyOrHdAccount === 'string'
234
- ? mnemonicOrPrivateKeyOrHdAccount.startsWith('0x')
235
- ? privateKeyToAccount(mnemonicOrPrivateKeyOrHdAccount as `0x${string}`)
236
- : mnemonicToAccount(mnemonicOrPrivateKeyOrHdAccount, { addressIndex })
237
- : mnemonicOrPrivateKeyOrHdAccount;
238
-
239
- // From what I can see, this is the difference between the HDAccount and the PrivateKeyAccount
240
- // and we don't need it for anything. This lets us use the same type for both.
241
- // eslint-disable-next-line camelcase
242
- hdAccount.experimental_signAuthorization ??= () => {
243
- throw new Error('experimental_signAuthorization not implemented for HDAccount');
244
- };
218
+ export async function validateExistingErc20TokenAddress(
219
+ l1Client: ExtendedViemWalletClient,
220
+ tokenAddress: EthAddress,
221
+ logger: Logger,
222
+ ): Promise<void> {
223
+ const addressString = tokenAddress.toString();
224
+
225
+ // Ensure there is contract code at the address
226
+ const code = await l1Client.getCode({ address: addressString });
227
+ if (!code || code === '0x') {
228
+ throw new Error(`No contract code found at provided token address ${addressString}`);
229
+ }
245
230
 
246
- const walletClient = createWalletClient({
247
- account: hdAccount,
248
- chain,
249
- transport: fallback(rpcUrls.map(url => http(url))),
250
- }).extend(publicActions);
251
- const publicClient = createPublicClient({
252
- chain,
253
- transport: fallback(rpcUrls.map(url => http(url))),
254
- pollingInterval: 100,
231
+ const contract = getContract({
232
+ address: getAddress(addressString),
233
+ abi: ERC20_VALIDATION_ABI,
234
+ client: l1Client,
255
235
  });
256
236
 
257
- return { walletClient, publicClient } as L1Clients;
237
+ // Validate all required ERC20 methods in parallel
238
+ const checks = [
239
+ contract.read.totalSupply().then(total => typeof total === 'bigint'),
240
+ contract.read.name().then(() => true),
241
+ contract.read.symbol().then(() => true),
242
+ contract.read.decimals().then(dec => typeof dec === 'number' || typeof dec === 'bigint'),
243
+ ];
244
+
245
+ const results = await Promise.allSettled(checks);
246
+ const failedChecks = results.filter(result => result.status === 'rejected' || result.value !== true);
247
+
248
+ if (failedChecks.length > 0) {
249
+ throw new Error(`Address ${addressString} does not appear to implement ERC20 view methods`);
250
+ }
251
+
252
+ logger.verbose(`Validated existing token at ${addressString} appears to be ERC20-compatible`);
258
253
  }
259
254
 
260
- export const deployRollupAndPeriphery = async (
261
- clients: L1Clients,
255
+ export const deploySharedContracts = async (
256
+ l1Client: ExtendedViemWalletClient,
257
+ deployer: L1Deployer,
262
258
  args: DeployL1ContractsArgs,
259
+ logger: Logger,
260
+ ) => {
261
+ const networkName = getActiveNetworkName();
262
+
263
+ logger.info(`Deploying shared contracts for network configuration: ${networkName}`);
264
+
265
+ const txHashes: Hex[] = [];
266
+
267
+ let feeAssetAddress: EthAddress;
268
+ let stakingAssetAddress: EthAddress;
269
+ if (args.existingTokenAddress) {
270
+ await validateExistingErc20TokenAddress(l1Client, args.existingTokenAddress, logger);
271
+ feeAssetAddress = args.existingTokenAddress;
272
+ stakingAssetAddress = args.existingTokenAddress;
273
+ logger.verbose(`Using existing token for fee and staking assets at ${args.existingTokenAddress}`);
274
+ } else {
275
+ const deployedFee = await deployer.deploy(FeeAssetArtifact, ['FeeJuice', 'FEE', l1Client.account.address]);
276
+ feeAssetAddress = deployedFee.address;
277
+ logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
278
+
279
+ // Mint a tiny bit of tokens to satisfy coin-issuer constraints
280
+ const { txHash } = await deployer.sendTransaction({
281
+ to: feeAssetAddress.toString(),
282
+ data: encodeFunctionData({
283
+ abi: FeeAssetArtifact.contractAbi,
284
+ functionName: 'mint',
285
+ args: [l1Client.account.address, 1n * 10n ** 18n],
286
+ }),
287
+ });
288
+ logger.verbose(`Minted tiny bit of tokens to satisfy coin-issuer constraints in ${txHash}`);
289
+
290
+ const deployedStaking = await deployer.deploy(StakingAssetArtifact, ['Staking', 'STK', l1Client.account.address]);
291
+ stakingAssetAddress = deployedStaking.address;
292
+ logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
293
+
294
+ await deployer.waitForDeployments();
295
+ }
296
+
297
+ const gseAddress = (
298
+ await deployer.deploy(GSEArtifact, [
299
+ l1Client.account.address,
300
+ stakingAssetAddress.toString(),
301
+ args.activationThreshold,
302
+ args.ejectionThreshold,
303
+ ])
304
+ ).address;
305
+ logger.verbose(`Deployed GSE at ${gseAddress}`);
306
+
307
+ const { address: registryAddress } = await deployer.deploy(RegistryArtifact, [
308
+ l1Client.account.address,
309
+ feeAssetAddress.toString(),
310
+ ]);
311
+ logger.verbose(`Deployed Registry at ${registryAddress}`);
312
+
313
+ const { address: governanceProposerAddress } = await deployer.deploy(GovernanceProposerArtifact, [
314
+ registryAddress.toString(),
315
+ gseAddress.toString(),
316
+ BigInt(args.governanceProposerQuorum ?? args.governanceProposerRoundSize / 2 + 1),
317
+ BigInt(args.governanceProposerRoundSize),
318
+ ]);
319
+ logger.verbose(`Deployed GovernanceProposer at ${governanceProposerAddress}`);
320
+
321
+ // @note @LHerskind the assets are expected to be the same at some point, but for better
322
+ // configurability they are different for now.
323
+ const { address: governanceAddress } = await deployer.deploy(GovernanceArtifact, [
324
+ stakingAssetAddress.toString(),
325
+ governanceProposerAddress.toString(),
326
+ gseAddress.toString(),
327
+ getGovernanceConfiguration(networkName),
328
+ ]);
329
+ logger.verbose(`Deployed Governance at ${governanceAddress}`);
330
+
331
+ let needToSetGovernance = false;
332
+
333
+ const existingCode = await l1Client.getCode({ address: gseAddress.toString() });
334
+ if (!existingCode || existingCode === '0x') {
335
+ needToSetGovernance = true;
336
+ } else {
337
+ const gseContract = getContract({
338
+ address: getAddress(gseAddress.toString()),
339
+ abi: GSEArtifact.contractAbi,
340
+ client: l1Client,
341
+ });
342
+ const existingGovernance = await gseContract.read.getGovernance();
343
+ if (EthAddress.fromString(existingGovernance).equals(EthAddress.ZERO)) {
344
+ needToSetGovernance = true;
345
+ }
346
+ }
347
+
348
+ if (needToSetGovernance) {
349
+ const { txHash } = await deployer.sendTransaction(
350
+ {
351
+ to: gseAddress.toString(),
352
+ data: encodeFunctionData({
353
+ abi: GSEArtifact.contractAbi,
354
+ functionName: 'setGovernance',
355
+ args: [governanceAddress.toString()],
356
+ }),
357
+ },
358
+ { gasLimit: 100_000n },
359
+ );
360
+
361
+ logger.verbose(`Set governance on GSE in ${txHash}`);
362
+ txHashes.push(txHash);
363
+ }
364
+
365
+ const coinIssuerAddress = (
366
+ await deployer.deploy(CoinIssuerArtifact, [
367
+ feeAssetAddress.toString(),
368
+ 2n * 10n ** 17n, // hard cap of 20% per year
369
+ l1Client.account.address,
370
+ ])
371
+ ).address;
372
+ logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
373
+
374
+ logger.verbose(`Waiting for deployments to complete`);
375
+ await deployer.waitForDeployments();
376
+
377
+ // Registry ownership will be transferred to governance later, after rollup is added
378
+
379
+ let feeAssetHandlerAddress: EthAddress | undefined = undefined;
380
+ let stakingAssetHandlerAddress: EthAddress | undefined = undefined;
381
+ let zkPassportVerifierAddress: EthAddress | undefined = undefined;
382
+
383
+ // Only if not on mainnet will we deploy the handlers, and only when we control the token
384
+ if (l1Client.chain.id !== 1 && !args.existingTokenAddress) {
385
+ /* -------------------------------------------------------------------------- */
386
+ /* CHEAT CODES START HERE */
387
+ /* -------------------------------------------------------------------------- */
388
+
389
+ feeAssetHandlerAddress = (
390
+ await deployer.deploy(FeeAssetHandlerArtifact, [
391
+ l1Client.account.address,
392
+ feeAssetAddress.toString(),
393
+ BigInt(1000n * 10n ** 18n),
394
+ ])
395
+ ).address;
396
+ logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
397
+
398
+ // Only if we are "fresh" will we be adding as a minter, otherwise above will simply get same address
399
+ if (needToSetGovernance) {
400
+ const { txHash } = await deployer.sendTransaction({
401
+ to: feeAssetAddress.toString(),
402
+ data: encodeFunctionData({
403
+ abi: FeeAssetArtifact.contractAbi,
404
+ functionName: 'addMinter',
405
+ args: [feeAssetHandlerAddress.toString()],
406
+ }),
407
+ });
408
+ logger.verbose(`Added fee asset handler ${feeAssetHandlerAddress} as minter on fee asset in ${txHash}`);
409
+ txHashes.push(txHash);
410
+ }
411
+
412
+ // Only if on sepolia will we deploy the staking asset handler
413
+ // Should not be deployed to devnet since it would cause caos with sequencers there etc.
414
+ if ([11155111, foundry.id].includes(l1Client.chain.id)) {
415
+ const AMIN = EthAddress.fromString('0x3b218d0F26d15B36C715cB06c949210a0d630637');
416
+ zkPassportVerifierAddress = await getZkPassportVerifierAddress(deployer, args);
417
+ const [domain, scope] = getZkPassportScopes(args);
418
+
419
+ const stakingAssetHandlerDeployArgs = {
420
+ owner: l1Client.account.address,
421
+ stakingAsset: stakingAssetAddress.toString(),
422
+ registry: registryAddress.toString(),
423
+ withdrawer: AMIN.toString(),
424
+ validatorsToFlush: 16n,
425
+ mintInterval: BigInt(60 * 60 * 24),
426
+ depositsPerMint: BigInt(10),
427
+ depositMerkleRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
428
+ zkPassportVerifier: zkPassportVerifierAddress.toString(),
429
+ unhinged: [AMIN.toString()], // isUnhinged,
430
+ // Scopes
431
+ domain: domain,
432
+ scope: scope,
433
+ // Skip checks
434
+ skipBindCheck: args.zkPassportArgs?.mockZkPassportVerifier ?? false,
435
+ skipMerkleCheck: true, // skip merkle check - needed for testing without generating proofs
436
+ } as const;
437
+
438
+ stakingAssetHandlerAddress = (await deployer.deploy(StakingAssetHandlerArtifact, [stakingAssetHandlerDeployArgs]))
439
+ .address;
440
+ logger.verbose(`Deployed StakingAssetHandler at ${stakingAssetHandlerAddress}`);
441
+
442
+ const { txHash: stakingMinterTxHash } = await deployer.sendTransaction({
443
+ to: stakingAssetAddress.toString(),
444
+ data: encodeFunctionData({
445
+ abi: StakingAssetArtifact.contractAbi,
446
+ functionName: 'addMinter',
447
+ args: [stakingAssetHandlerAddress.toString()],
448
+ }),
449
+ });
450
+ logger.verbose(
451
+ `Added staking asset handler ${stakingAssetHandlerAddress} as minter on staking asset in ${stakingMinterTxHash}`,
452
+ );
453
+ txHashes.push(stakingMinterTxHash);
454
+ }
455
+ }
456
+
457
+ /* -------------------------------------------------------------------------- */
458
+ /* CHEAT CODES END HERE */
459
+ /* -------------------------------------------------------------------------- */
460
+
461
+ logger.verbose(`Waiting for deployments to complete`);
462
+ await deployer.waitForDeployments();
463
+ await Promise.all(txHashes.map(txHash => l1Client.waitForTransactionReceipt({ hash: txHash })));
464
+
465
+ logger.verbose(`Deployed shared contracts`);
466
+
467
+ const registry = new RegistryContract(l1Client, registryAddress);
468
+
469
+ /* -------------------------------------------------------------------------- */
470
+ /* FUND REWARD DISTRIBUTOR START */
471
+ /* -------------------------------------------------------------------------- */
472
+
473
+ const rewardDistributorAddress = await registry.getRewardDistributor();
474
+
475
+ if (!args.existingTokenAddress) {
476
+ const blockReward = getRewardConfig(networkName).blockReward;
477
+
478
+ const funding = blockReward * 200000n;
479
+ const { txHash: fundRewardDistributorTxHash } = await deployer.sendTransaction({
480
+ to: feeAssetAddress.toString(),
481
+ data: encodeFunctionData({
482
+ abi: FeeAssetArtifact.contractAbi,
483
+ functionName: 'mint',
484
+ args: [rewardDistributorAddress.toString(), funding],
485
+ }),
486
+ });
487
+
488
+ logger.verbose(`Funded reward distributor with ${funding} fee asset in ${fundRewardDistributorTxHash}`);
489
+ } else {
490
+ logger.verbose(`Skipping reward distributor funding as existing token is provided`);
491
+ }
492
+
493
+ /* -------------------------------------------------------------------------- */
494
+ /* FUND REWARD DISTRIBUTOR STOP */
495
+ /* -------------------------------------------------------------------------- */
496
+
497
+ return {
498
+ feeAssetAddress,
499
+ feeAssetHandlerAddress,
500
+ stakingAssetAddress,
501
+ stakingAssetHandlerAddress,
502
+ zkPassportVerifierAddress,
503
+ registryAddress,
504
+ gseAddress,
505
+ governanceAddress,
506
+ governanceProposerAddress,
507
+ coinIssuerAddress,
508
+ rewardDistributorAddress: await registry.getRewardDistributor(),
509
+ };
510
+ };
511
+
512
+ const getZkPassportVerifierAddress = async (deployer: L1Deployer, args: DeployL1ContractsArgs): Promise<EthAddress> => {
513
+ if (args.zkPassportArgs?.mockZkPassportVerifier) {
514
+ return (await deployer.deploy(mockVerifiers.mockZkPassportVerifier)).address;
515
+ }
516
+ return ZK_PASSPORT_VERIFIER_ADDRESS;
517
+ };
518
+
519
+ /**
520
+ * Get the zk passport scopes - default to testnet values if not provided
521
+ * @param args - The deployment arguments
522
+ * @returns The zk passport scopes
523
+ */
524
+ const getZkPassportScopes = (args: DeployL1ContractsArgs): [string, string] => {
525
+ const domain = args.zkPassportArgs?.zkPassportDomain ?? ZK_PASSPORT_DOMAIN;
526
+ const scope = args.zkPassportArgs?.zkPassportScope ?? ZK_PASSPORT_SCOPE;
527
+ return [domain, scope];
528
+ };
529
+
530
+ /**
531
+ * Generates verification records for a deployed rollup and its associated contracts (Inbox, Outbox, Slasher, etc).
532
+ * @param rollup - The deployed rollup contract.
533
+ * @param deployer - The L1 deployer instance.
534
+ * @param args - The deployment arguments used for the rollup.
535
+ * @param addresses - The L1 contract addresses.
536
+ * @param extendedClient - The extended viem wallet client.
537
+ * @param logger - The logger.
538
+ */
539
+ async function generateRollupVerificationRecords(
540
+ rollup: RollupContract,
541
+ deployer: L1Deployer,
542
+ args: {
543
+ slashingVetoer: EthAddress;
544
+ slashingRoundSizeInEpochs: number;
545
+ aztecEpochDuration: number;
546
+ slashingQuorum?: number;
547
+ slashingLifetimeInRounds: number;
548
+ slashingExecutionDelayInRounds: number;
549
+ slasherFlavor: 'none' | 'tally' | 'empire';
550
+ slashAmountSmall: bigint;
551
+ slashAmountMedium: bigint;
552
+ slashAmountLarge: bigint;
553
+ aztecTargetCommitteeSize: number;
554
+ slashingOffsetInRounds: number;
555
+ },
556
+ addresses: Pick<L1ContractAddresses, 'feeJuiceAddress'>,
557
+ extendedClient: ExtendedViemWalletClient,
558
+ logger: Logger,
559
+ ): Promise<void> {
560
+ try {
561
+ // Add Inbox / Outbox verification records (constructor args are created inside RollupCore)
562
+ const rollupAddr = rollup.address;
563
+ const rollupAddresses = await rollup.getRollupAddresses();
564
+ const inboxAddr = rollupAddresses.inboxAddress.toString();
565
+ const outboxAddr = rollupAddresses.outboxAddress.toString();
566
+ const feeAsset = rollupAddresses.feeJuiceAddress.toString();
567
+ const version = await rollup.getVersion();
568
+
569
+ const inboxCtor = encodeAbiParameters(
570
+ [{ type: 'address' }, { type: 'address' }, { type: 'uint256' }, { type: 'uint256' }],
571
+ [rollupAddr, feeAsset, version, BigInt(L1_TO_L2_MSG_SUBTREE_HEIGHT)],
572
+ );
573
+
574
+ const outboxCtor = encodeAbiParameters([{ type: 'address' }, { type: 'uint256' }], [rollupAddr, version]);
575
+
576
+ deployer.verificationRecords.push(
577
+ { name: 'Inbox', address: inboxAddr, constructorArgsHex: inboxCtor, libraries: [] },
578
+ { name: 'Outbox', address: outboxAddr, constructorArgsHex: outboxCtor, libraries: [] },
579
+ );
580
+
581
+ // Include Slasher and SlashingProposer (if deployed) in verification data
582
+ try {
583
+ const slasherAddrHex = await rollup.getSlasherAddress();
584
+ const slasherAddr = EthAddress.fromString(slasherAddrHex);
585
+ if (!slasherAddr.isZero()) {
586
+ // Slasher constructor: (address _vetoer, address _governance)
587
+ const slasherCtor = encodeAbiParameters(
588
+ [{ type: 'address' }, { type: 'address' }],
589
+ [args.slashingVetoer.toString(), extendedClient.account.address],
590
+ );
591
+ deployer.verificationRecords.push({
592
+ name: 'Slasher',
593
+ address: slasherAddr.toString(),
594
+ constructorArgsHex: slasherCtor,
595
+ libraries: [],
596
+ });
597
+
598
+ // Proposer address is stored in Slasher.PROPOSER()
599
+ const proposerAddr = (await rollup.getSlashingProposerAddress()).toString();
600
+
601
+ // Compute constructor args matching deployment path in RollupCore
602
+ const computedRoundSize = BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration);
603
+ const computedQuorum = BigInt(
604
+ args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1,
605
+ );
606
+ const lifetimeInRounds = BigInt(args.slashingLifetimeInRounds);
607
+ const executionDelayInRounds = BigInt(args.slashingExecutionDelayInRounds);
608
+
609
+ if (args.slasherFlavor === 'tally') {
610
+ const slashAmounts: readonly [bigint, bigint, bigint] = [
611
+ args.slashAmountSmall,
612
+ args.slashAmountMedium,
613
+ args.slashAmountLarge,
614
+ ];
615
+ const committeeSize = BigInt(args.aztecTargetCommitteeSize);
616
+ const epochDuration = BigInt(args.aztecEpochDuration);
617
+ const slashOffsetInRounds = BigInt(args.slashingOffsetInRounds);
618
+
619
+ const proposerCtor = encodeAbiParameters(
620
+ [
621
+ { type: 'address' },
622
+ { type: 'address' },
623
+ { type: 'uint256' },
624
+ { type: 'uint256' },
625
+ { type: 'uint256' },
626
+ { type: 'uint256' },
627
+ { type: 'uint256[3]' },
628
+ { type: 'uint256' },
629
+ { type: 'uint256' },
630
+ { type: 'uint256' },
631
+ ],
632
+ [
633
+ rollup.address,
634
+ slasherAddr.toString(),
635
+ computedQuorum,
636
+ computedRoundSize,
637
+ lifetimeInRounds,
638
+ executionDelayInRounds,
639
+ slashAmounts,
640
+ committeeSize,
641
+ epochDuration,
642
+ slashOffsetInRounds,
643
+ ],
644
+ );
645
+
646
+ deployer.verificationRecords.push({
647
+ name: 'TallySlashingProposer',
648
+ address: proposerAddr,
649
+ constructorArgsHex: proposerCtor,
650
+ libraries: [],
651
+ });
652
+ } else if (args.slasherFlavor === 'empire') {
653
+ const proposerCtor = encodeAbiParameters(
654
+ [
655
+ { type: 'address' },
656
+ { type: 'address' },
657
+ { type: 'uint256' },
658
+ { type: 'uint256' },
659
+ { type: 'uint256' },
660
+ { type: 'uint256' },
661
+ ],
662
+ [
663
+ rollup.address,
664
+ slasherAddr.toString(),
665
+ computedQuorum,
666
+ computedRoundSize,
667
+ lifetimeInRounds,
668
+ executionDelayInRounds,
669
+ ],
670
+ );
671
+
672
+ deployer.verificationRecords.push({
673
+ name: 'EmpireSlashingProposer',
674
+ address: proposerAddr,
675
+ constructorArgsHex: proposerCtor,
676
+ libraries: [],
677
+ });
678
+ }
679
+ }
680
+ } catch (e) {
681
+ logger.warn(`Failed to add Slasher/Proposer verification records: ${String(e)}`);
682
+ }
683
+ } catch (e) {
684
+ throw new Error(`Failed to generate rollup verification records: ${String(e)}`);
685
+ }
686
+ }
687
+
688
+ /**
689
+ * Writes verification records to a JSON file for later forge verify.
690
+ * @param deployer - The L1 deployer containing verification records.
691
+ * @param outputDirectory - The directory to write the verification file to.
692
+ * @param chainId - The chain ID.
693
+ * @param filenameSuffix - Optional suffix for the filename (e.g., 'upgrade').
694
+ * @param logger - The logger.
695
+ */
696
+ async function writeVerificationJson(
697
+ deployer: L1Deployer,
698
+ outputDirectory: string,
699
+ chainId: number,
700
+ filenameSuffix: string = '',
701
+ logger: Logger,
702
+ ): Promise<void> {
703
+ try {
704
+ const date = new Date();
705
+ const formattedDate = date.toISOString().slice(2, 19).replace(/[-T:]/g, '');
706
+ // Ensure the verification output directory exists
707
+ await mkdir(outputDirectory, { recursive: true });
708
+ const suffix = filenameSuffix ? `-${filenameSuffix}` : '';
709
+ const verificationOutputPath = `${outputDirectory}/l1-verify${suffix}-${chainId}-${formattedDate.slice(0, 6)}-${formattedDate.slice(6)}.json`;
710
+ const networkName = getActiveNetworkName();
711
+ const verificationData = {
712
+ chainId: chainId,
713
+ network: networkName,
714
+ records: deployer.verificationRecords,
715
+ };
716
+ await writeFile(verificationOutputPath, JSON.stringify(verificationData, null, 2));
717
+ logger.info(`Wrote L1 verification data to ${verificationOutputPath}`);
718
+ } catch (e) {
719
+ logger.warn(`Failed to write L1 verification data file: ${String(e)}`);
720
+ }
721
+ }
722
+
723
+ /**
724
+ * Deploys a new rollup, using the existing canonical version to derive certain values (addresses of assets etc).
725
+ * @param clients - The L1 clients.
726
+ * @param args - The deployment arguments.
727
+ * @param registryAddress - The address of the registry.
728
+ * @param logger - The logger.
729
+ * @param txUtilsConfig - The L1 tx utils config.
730
+ * @param createVerificationJson - Optional path to write verification data for forge verify.
731
+ */
732
+ export const deployRollupForUpgrade = async (
733
+ extendedClient: ExtendedViemWalletClient,
734
+ args: Omit<
735
+ DeployL1ContractsArgs,
736
+ 'governanceProposerQuorum' | 'governanceProposerRoundSize' | 'ejectionThreshold' | 'activationThreshold'
737
+ >,
263
738
  registryAddress: EthAddress,
264
739
  logger: Logger,
265
740
  txUtilsConfig: L1TxUtilsConfig,
741
+ createVerificationJson: string | false = false,
266
742
  ) => {
267
743
  const deployer = new L1Deployer(
268
- clients.walletClient,
269
- clients.publicClient,
744
+ extendedClient,
270
745
  args.salt,
746
+ undefined,
271
747
  args.acceleratedTestDeployments,
272
748
  logger,
273
749
  txUtilsConfig,
750
+ !!createVerificationJson,
274
751
  );
275
752
 
276
- const addresses = await RegistryContract.collectAddresses(clients.publicClient, registryAddress, 'canonical');
753
+ const addresses = await RegistryContract.collectAddresses(extendedClient, registryAddress, 'canonical');
754
+
755
+ const { rollup, slashFactoryAddress } = await deployRollup(extendedClient, deployer, args, addresses, logger);
756
+
757
+ await deployer.waitForDeployments();
758
+
759
+ // Write verification data (constructor args + linked libraries) to file for later forge verify
760
+ if (createVerificationJson) {
761
+ await generateRollupVerificationRecords(rollup, deployer, args, addresses, extendedClient, logger);
762
+ await writeVerificationJson(deployer, createVerificationJson, extendedClient.chain.id, 'upgrade', logger);
763
+ }
764
+
765
+ return { rollup, slashFactoryAddress };
766
+ };
767
+
768
+ export const deploySlashFactory = async (deployer: L1Deployer, rollupAddress: Hex, logger: Logger) => {
769
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [rollupAddress])).address;
770
+ logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
771
+ return slashFactoryAddress;
772
+ };
773
+
774
+ export const deployUpgradePayload = async (
775
+ deployer: L1Deployer,
776
+ addresses: Pick<L1ContractAddresses, 'registryAddress' | 'rollupAddress'>,
777
+ ) => {
778
+ const payloadAddress = (
779
+ await deployer.deploy(RegisterNewRollupVersionPayloadArtifact, [
780
+ addresses.registryAddress.toString(),
781
+ addresses.rollupAddress.toString(),
782
+ ])
783
+ ).address;
784
+
785
+ return payloadAddress;
786
+ };
787
+
788
+ function slasherFlavorToSolidityEnum(flavor: DeployL1ContractsArgs['slasherFlavor']): number {
789
+ switch (flavor) {
790
+ case 'none':
791
+ return SlashingProposerType.None.valueOf();
792
+ case 'tally':
793
+ return SlashingProposerType.Tally.valueOf();
794
+ case 'empire':
795
+ return SlashingProposerType.Empire.valueOf();
796
+ default: {
797
+ const _: never = flavor;
798
+ throw new Error(`Unexpected slasher flavor ${flavor}`);
799
+ }
800
+ }
801
+ }
802
+
803
+ /**
804
+ * Deploys a new rollup contract, funds and initializes the fee juice portal, and initializes the validator set.
805
+ */
806
+ export const deployRollup = async (
807
+ extendedClient: ExtendedViemWalletClient,
808
+ deployer: L1Deployer,
809
+ args: Omit<
810
+ DeployL1ContractsArgs,
811
+ 'governanceProposerQuorum' | 'governanceProposerRoundSize' | 'ejectionThreshold' | 'activationThreshold'
812
+ >,
813
+ addresses: Pick<
814
+ L1ContractAddresses,
815
+ | 'feeJuiceAddress'
816
+ | 'registryAddress'
817
+ | 'rewardDistributorAddress'
818
+ | 'stakingAssetAddress'
819
+ | 'gseAddress'
820
+ | 'governanceAddress'
821
+ >,
822
+ logger: Logger,
823
+ ) => {
824
+ if (!addresses.gseAddress) {
825
+ throw new Error('GSE address is required when deploying');
826
+ }
827
+ const networkName = getActiveNetworkName();
828
+
829
+ logger.info(`Deploying rollup using network configuration: ${networkName}`);
830
+
831
+ const txHashes: Hex[] = [];
832
+
833
+ let epochProofVerifier = EthAddress.ZERO;
834
+
835
+ if (args.realVerifier) {
836
+ epochProofVerifier = (await deployer.deploy(l1ArtifactsVerifiers.honkVerifier)).address;
837
+ logger.verbose(`Rollup will use the real verifier at ${epochProofVerifier}`);
838
+ } else {
839
+ epochProofVerifier = (await deployer.deploy(mockVerifiers.mockVerifier)).address;
840
+ logger.verbose(`Rollup will use the mock verifier at ${epochProofVerifier}`);
841
+ }
842
+
843
+ const rewardConfig = {
844
+ ...getRewardConfig(networkName),
845
+ rewardDistributor: addresses.rewardDistributorAddress.toString(),
846
+ };
847
+
848
+ const rollupConfigArgs: ContractConstructorArgs<typeof RollupAbi>[6] = {
849
+ aztecSlotDuration: BigInt(args.aztecSlotDuration),
850
+ aztecEpochDuration: BigInt(args.aztecEpochDuration),
851
+ targetCommitteeSize: BigInt(args.aztecTargetCommitteeSize),
852
+ lagInEpochs: BigInt(args.lagInEpochs),
853
+ aztecProofSubmissionEpochs: BigInt(args.aztecProofSubmissionEpochs),
854
+ slashingQuorum: BigInt(args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1),
855
+ slashingRoundSize: BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration),
856
+ slashingLifetimeInRounds: BigInt(args.slashingLifetimeInRounds),
857
+ slashingExecutionDelayInRounds: BigInt(args.slashingExecutionDelayInRounds),
858
+ slashingVetoer: args.slashingVetoer.toString(),
859
+ manaTarget: args.manaTarget,
860
+ provingCostPerMana: args.provingCostPerMana,
861
+ rewardConfig: rewardConfig,
862
+ version: 0,
863
+ rewardBoostConfig: getRewardBoostConfig(),
864
+ stakingQueueConfig: getEntryQueueConfig(networkName),
865
+ exitDelaySeconds: BigInt(args.exitDelaySeconds),
866
+ slasherFlavor: slasherFlavorToSolidityEnum(args.slasherFlavor),
867
+ slashingOffsetInRounds: BigInt(args.slashingOffsetInRounds),
868
+ slashAmounts: [args.slashAmountSmall, args.slashAmountMedium, args.slashAmountLarge],
869
+ localEjectionThreshold: args.localEjectionThreshold,
870
+ slashingDisableDuration: BigInt(args.slashingDisableDuration ?? 0n),
871
+ earliestRewardsClaimableTimestamp: 0n,
872
+ };
873
+
874
+ const genesisStateArgs = {
875
+ vkTreeRoot: args.vkTreeRoot.toString(),
876
+ protocolContractsHash: args.protocolContractsHash.toString(),
877
+ genesisArchiveRoot: args.genesisArchiveRoot.toString(),
878
+ };
879
+
880
+ // Until there is an actual chain-id for the version, we will just draw a random value.
881
+ // TODO(https://linear.app/aztec-labs/issue/TMNT-139/version-at-deployment)
882
+ rollupConfigArgs.version = Buffer.from(
883
+ keccak256String(
884
+ jsonStringify({
885
+ rollupConfigArgs,
886
+ genesisStateArgs,
887
+ }),
888
+ ),
889
+ ).readUint32BE(0);
890
+ logger.verbose(`Rollup config args`, rollupConfigArgs);
891
+
892
+ const rollupArgs = [
893
+ addresses.feeJuiceAddress.toString(),
894
+ addresses.stakingAssetAddress.toString(),
895
+ addresses.gseAddress.toString(),
896
+ epochProofVerifier.toString(),
897
+ extendedClient.account.address,
898
+ genesisStateArgs,
899
+ rollupConfigArgs,
900
+ ] as const;
901
+
902
+ const { address: rollupAddress, existed: rollupExisted } = await deployer.deploy(RollupArtifact, rollupArgs, {
903
+ gasLimit: 15_000_000n,
904
+ });
905
+ logger.verbose(`Deployed Rollup at ${rollupAddress}, already existed: ${rollupExisted}`, rollupConfigArgs);
906
+
907
+ const rollupContract = new RollupContract(extendedClient, rollupAddress);
908
+
909
+ await deployer.waitForDeployments();
910
+ logger.verbose(`All core contracts have been deployed`);
911
+
912
+ if (args.feeJuicePortalInitialBalance && args.feeJuicePortalInitialBalance > 0n) {
913
+ // Skip funding when using an external token, as we likely don't have mint permissions
914
+ if (!('existingTokenAddress' in args) || !args.existingTokenAddress) {
915
+ const feeJuicePortalAddress = await rollupContract.getFeeJuicePortal();
916
+
917
+ // In fast mode, use the L1TxUtils to send transactions with nonce management
918
+ const { txHash: mintTxHash } = await deployer.sendTransaction({
919
+ to: addresses.feeJuiceAddress.toString(),
920
+ data: encodeFunctionData({
921
+ abi: FeeAssetArtifact.contractAbi,
922
+ functionName: 'mint',
923
+ args: [feeJuicePortalAddress.toString(), args.feeJuicePortalInitialBalance],
924
+ }),
925
+ });
926
+ logger.verbose(
927
+ `Funding fee juice portal with ${args.feeJuicePortalInitialBalance} fee juice in ${mintTxHash} (accelerated test deployments)`,
928
+ );
929
+ txHashes.push(mintTxHash);
930
+ } else {
931
+ logger.verbose('Skipping fee juice portal funding due to external token usage');
932
+ }
933
+ }
934
+
935
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [rollupAddress.toString()])).address;
936
+ logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
937
+
938
+ // We need to call a function on the registry to set the various contract addresses.
939
+ const registryContract = getContract({
940
+ address: getAddress(addresses.registryAddress.toString()),
941
+ abi: RegistryArtifact.contractAbi,
942
+ client: extendedClient,
943
+ });
944
+
945
+ // Only if we are the owner will we be sending these transactions
946
+ if ((await registryContract.read.owner()) === getAddress(extendedClient.account.address)) {
947
+ const version = await rollupContract.getVersion();
948
+ try {
949
+ const retrievedRollupAddress = await registryContract.read.getRollup([version]);
950
+ logger.verbose(`Rollup ${retrievedRollupAddress} already exists in registry`);
951
+ } catch {
952
+ const { txHash: addRollupTxHash } = await deployer.sendTransaction({
953
+ to: addresses.registryAddress.toString(),
954
+ data: encodeFunctionData({
955
+ abi: RegistryArtifact.contractAbi,
956
+ functionName: 'addRollup',
957
+ args: [getAddress(rollupContract.address)],
958
+ }),
959
+ });
960
+ logger.verbose(
961
+ `Adding rollup ${rollupContract.address} to registry ${addresses.registryAddress} in tx ${addRollupTxHash}`,
962
+ );
963
+
964
+ txHashes.push(addRollupTxHash);
965
+ }
966
+ } else {
967
+ logger.verbose(`Not the owner of the registry, skipping rollup addition`);
968
+ }
969
+
970
+ // We need to call a function on the registry to set the various contract addresses.
971
+ const gseContract = getContract({
972
+ address: getAddress(addresses.gseAddress.toString()),
973
+ abi: GSEArtifact.contractAbi,
974
+ client: extendedClient,
975
+ });
976
+ if ((await gseContract.read.owner()) === getAddress(extendedClient.account.address)) {
977
+ if (!(await gseContract.read.isRollupRegistered([rollupContract.address]))) {
978
+ const { txHash: addRollupTxHash } = await deployer.sendTransaction({
979
+ to: addresses.gseAddress.toString(),
980
+ data: encodeFunctionData({
981
+ abi: GSEArtifact.contractAbi,
982
+ functionName: 'addRollup',
983
+ args: [getAddress(rollupContract.address)],
984
+ }),
985
+ });
986
+ logger.verbose(`Adding rollup ${rollupContract.address} to GSE ${addresses.gseAddress} in tx ${addRollupTxHash}`);
987
+
988
+ // wait for this tx to land in case we have to register initialValidators
989
+ await extendedClient.waitForTransactionReceipt({ hash: addRollupTxHash });
990
+ } else {
991
+ logger.verbose(`Rollup ${rollupContract.address} is already registered in GSE ${addresses.gseAddress}`);
992
+ }
993
+ } else {
994
+ logger.verbose(`Not the owner of the gse, skipping rollup addition`);
995
+ }
996
+
997
+ const activeAttestorCount = await rollupContract.getActiveAttesterCount();
998
+ const queuedAttestorCount = await rollupContract.getEntryQueueLength();
999
+ logger.info(`Rollup has ${activeAttestorCount} active attestors and ${queuedAttestorCount} queued attestors`);
1000
+
1001
+ const shouldAddValidators = activeAttestorCount === 0n && queuedAttestorCount === 0n;
1002
+
1003
+ if (
1004
+ args.initialValidators &&
1005
+ shouldAddValidators &&
1006
+ (await gseContract.read.isRollupRegistered([rollupContract.address]))
1007
+ ) {
1008
+ await addMultipleValidators(
1009
+ extendedClient,
1010
+ deployer,
1011
+ addresses.gseAddress.toString(),
1012
+ rollupAddress.toString(),
1013
+ addresses.stakingAssetAddress.toString(),
1014
+ args.initialValidators,
1015
+ args.acceleratedTestDeployments,
1016
+ logger,
1017
+ );
1018
+ }
1019
+
1020
+ // If the owner is not the Governance contract, transfer ownership to the Governance contract
1021
+ logger.verbose(addresses.governanceAddress.toString());
1022
+ if (getAddress(await rollupContract.getOwner()) !== getAddress(addresses.governanceAddress.toString())) {
1023
+ // TODO(md): add send transaction to the deployer such that we do not need to manage tx hashes here
1024
+ const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
1025
+ to: rollupContract.address,
1026
+ data: encodeFunctionData({
1027
+ abi: RegistryArtifact.contractAbi,
1028
+ functionName: 'transferOwnership',
1029
+ args: [getAddress(addresses.governanceAddress.toString())],
1030
+ }),
1031
+ });
1032
+ logger.verbose(
1033
+ `Transferring the ownership of the rollup contract at ${rollupContract.address} to the Governance ${addresses.governanceAddress} in tx ${transferOwnershipTxHash}`,
1034
+ );
1035
+ txHashes.push(transferOwnershipTxHash);
1036
+ }
1037
+
1038
+ await deployer.waitForDeployments();
1039
+ await Promise.all(txHashes.map(txHash => extendedClient.waitForTransactionReceipt({ hash: txHash })));
1040
+ logger.verbose(`Rollup deployed`);
1041
+
1042
+ return { rollup: rollupContract, slashFactoryAddress };
1043
+ };
1044
+
1045
+ export const handoverToGovernance = async (
1046
+ extendedClient: ExtendedViemWalletClient,
1047
+ deployer: L1Deployer,
1048
+ registryAddress: EthAddress,
1049
+ gseAddress: EthAddress,
1050
+ coinIssuerAddress: EthAddress,
1051
+ feeAssetAddress: EthAddress,
1052
+ governanceAddress: EthAddress,
1053
+ logger: Logger,
1054
+ acceleratedTestDeployments: boolean | undefined,
1055
+ useExternalToken: boolean = false,
1056
+ ) => {
1057
+ // We need to call a function on the registry to set the various contract addresses.
1058
+ const registryContract = getContract({
1059
+ address: getAddress(registryAddress.toString()),
1060
+ abi: RegistryArtifact.contractAbi,
1061
+ client: extendedClient,
1062
+ });
1063
+
1064
+ const gseContract = getContract({
1065
+ address: getAddress(gseAddress.toString()),
1066
+ abi: GSEArtifact.contractAbi,
1067
+ client: extendedClient,
1068
+ });
1069
+
1070
+ const coinIssuerContract = getContract({
1071
+ address: getAddress(coinIssuerAddress.toString()),
1072
+ abi: CoinIssuerArtifact.contractAbi,
1073
+ client: extendedClient,
1074
+ });
1075
+
1076
+ const feeAsset = getContract({
1077
+ address: getAddress(feeAssetAddress.toString()),
1078
+ abi: FeeAssetArtifact.contractAbi,
1079
+ client: extendedClient,
1080
+ });
1081
+
1082
+ const txHashes: Hex[] = [];
1083
+
1084
+ // If the owner is not the Governance contract, transfer ownership to the Governance contract
1085
+ if (
1086
+ acceleratedTestDeployments ||
1087
+ (await registryContract.read.owner()) !== getAddress(governanceAddress.toString())
1088
+ ) {
1089
+ // TODO(md): add send transaction to the deployer such that we do not need to manage tx hashes here
1090
+ const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
1091
+ to: registryAddress.toString(),
1092
+ data: encodeFunctionData({
1093
+ abi: RegistryArtifact.contractAbi,
1094
+ functionName: 'transferOwnership',
1095
+ args: [getAddress(governanceAddress.toString())],
1096
+ }),
1097
+ });
1098
+ logger.verbose(
1099
+ `Transferring the ownership of the registry contract at ${registryAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
1100
+ );
1101
+ txHashes.push(transferOwnershipTxHash);
1102
+ }
1103
+
1104
+ // If the owner is not the Governance contract, transfer ownership to the Governance contract
1105
+ if (acceleratedTestDeployments || (await gseContract.read.owner()) !== getAddress(governanceAddress.toString())) {
1106
+ // TODO(md): add send transaction to the deployer such that we do not need to manage tx hashes here
1107
+ const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
1108
+ to: gseContract.address,
1109
+ data: encodeFunctionData({
1110
+ abi: GSEArtifact.contractAbi,
1111
+ functionName: 'transferOwnership',
1112
+ args: [getAddress(governanceAddress.toString())],
1113
+ }),
1114
+ });
1115
+ logger.verbose(
1116
+ `Transferring the ownership of the gse contract at ${gseAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
1117
+ );
1118
+ txHashes.push(transferOwnershipTxHash);
1119
+ }
277
1120
 
278
- const rollup = await deployRollup(clients, deployer, args, addresses, logger);
279
- const payloadAddress = await deployUpgradePayload(deployer, {
280
- registryAddress: addresses.registryAddress,
281
- rollupAddress: EthAddress.fromString(rollup.address),
282
- });
283
- const slashFactoryAddress = await deploySlashFactory(deployer, rollup.address, logger);
1121
+ if (
1122
+ !useExternalToken &&
1123
+ (acceleratedTestDeployments || (await feeAsset.read.owner()) !== coinIssuerAddress.toString())
1124
+ ) {
1125
+ const { txHash } = await deployer.sendTransaction(
1126
+ {
1127
+ to: feeAssetAddress.toString(),
1128
+ data: encodeFunctionData({
1129
+ abi: FeeAssetArtifact.contractAbi,
1130
+ functionName: 'transferOwnership',
1131
+ args: [coinIssuerAddress.toString()],
1132
+ }),
1133
+ },
1134
+ { gasLimit: 500_000n },
1135
+ );
1136
+ logger.verbose(`Transfer ownership of fee asset to coin issuer ${coinIssuerAddress} in ${txHash}`);
1137
+ txHashes.push(txHash);
284
1138
 
285
- await deployer.waitForDeployments();
1139
+ const { txHash: acceptTokenOwnershipTxHash } = await deployer.sendTransaction(
1140
+ {
1141
+ to: coinIssuerAddress.toString(),
1142
+ data: encodeFunctionData({
1143
+ abi: CoinIssuerArtifact.contractAbi,
1144
+ functionName: 'acceptTokenOwnership',
1145
+ }),
1146
+ },
1147
+ { gasLimit: 500_000n },
1148
+ );
1149
+ logger.verbose(`Accept ownership of fee asset in ${acceptTokenOwnershipTxHash}`);
1150
+ txHashes.push(acceptTokenOwnershipTxHash);
1151
+ } else if (useExternalToken) {
1152
+ logger.verbose('Skipping fee asset ownership transfer due to external token usage');
1153
+ }
286
1154
 
287
- return { rollup, payloadAddress, slashFactoryAddress };
288
- };
1155
+ // Either deploy or at least predict the address of the date gated relayer
1156
+ const dateGatedRelayer = await deployer.deploy(DateGatedRelayerArtifact, [
1157
+ governanceAddress.toString(),
1158
+ 1798761600n, // 2027-01-01 00:00:00 UTC
1159
+ ]);
289
1160
 
290
- export const deploySlashFactory = async (deployer: L1Deployer, rollupAddress: Hex, logger: Logger) => {
291
- const slashFactoryAddress = await deployer.deploy(l1Artifacts.slashFactory, [rollupAddress]);
292
- logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
293
- return slashFactoryAddress;
294
- };
1161
+ // If the owner is not the Governance contract, transfer ownership to the Governance contract
1162
+ if (acceleratedTestDeployments || (await coinIssuerContract.read.owner()) === deployer.client.account.address) {
1163
+ const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
1164
+ to: coinIssuerContract.address,
1165
+ data: encodeFunctionData({
1166
+ abi: CoinIssuerArtifact.contractAbi,
1167
+ functionName: 'transferOwnership',
1168
+ args: [getAddress(dateGatedRelayer.address.toString())],
1169
+ }),
1170
+ });
1171
+ logger.verbose(
1172
+ `Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the DateGatedRelayer ${dateGatedRelayer.address} in tx ${transferOwnershipTxHash}`,
1173
+ );
1174
+ txHashes.push(transferOwnershipTxHash);
1175
+ }
295
1176
 
296
- export const deployUpgradePayload = async (
297
- deployer: L1Deployer,
298
- addresses: Pick<L1ContractAddresses, 'registryAddress' | 'rollupAddress'>,
299
- ) => {
300
- const payloadAddress = await deployer.deploy(l1Artifacts.registerNewRollupVersionPayload, [
301
- addresses.registryAddress.toString(),
302
- addresses.rollupAddress.toString(),
303
- ]);
1177
+ // Wait for all actions to be mined
1178
+ await deployer.waitForDeployments();
1179
+ await Promise.all(txHashes.map(txHash => extendedClient.waitForTransactionReceipt({ hash: txHash })));
304
1180
 
305
- return payloadAddress;
1181
+ return { dateGatedRelayerAddress: dateGatedRelayer.address };
306
1182
  };
307
1183
 
308
- export const deployRollup = async (
309
- clients: L1Clients,
1184
+ /*
1185
+ * Adds multiple validators to the rollup
1186
+ *
1187
+ * @param extendedClient - The L1 clients.
1188
+ * @param deployer - The L1 deployer.
1189
+ * @param rollupAddress - The address of the rollup.
1190
+ * @param stakingAssetAddress - The address of the staking asset.
1191
+ * @param validators - The validators to initialize.
1192
+ * @param acceleratedTestDeployments - Whether to use accelerated test deployments.
1193
+ * @param logger - The logger.
1194
+ */
1195
+ export const addMultipleValidators = async (
1196
+ extendedClient: ExtendedViemWalletClient,
310
1197
  deployer: L1Deployer,
311
- args: DeployL1ContractsArgs,
312
- addresses: Pick<L1ContractAddresses, 'feeJuicePortalAddress' | 'rewardDistributorAddress' | 'stakingAssetAddress'>,
1198
+ gseAddress: Hex,
1199
+ rollupAddress: Hex,
1200
+ stakingAssetAddress: Hex,
1201
+ validators: Operator[],
1202
+ acceleratedTestDeployments: boolean | undefined,
313
1203
  logger: Logger,
314
- ): Promise<RollupContract> => {
315
- const rollupConfigArgs = {
316
- aztecSlotDuration: args.aztecSlotDuration,
317
- aztecEpochDuration: args.aztecEpochDuration,
318
- targetCommitteeSize: args.aztecTargetCommitteeSize,
319
- aztecProofSubmissionWindow: args.aztecProofSubmissionWindow,
320
- minimumStake: args.minimumStake,
321
- slashingQuorum: args.slashingQuorum,
322
- slashingRoundSize: args.slashingRoundSize,
323
- };
324
- const genesisStateArgs = {
325
- vkTreeRoot: args.vkTreeRoot.toString(),
326
- protocolContractTreeRoot: args.protocolContractTreeRoot.toString(),
327
- genesisArchiveRoot: args.genesisArchiveRoot.toString(),
328
- genesisBlockHash: args.genesisBlockHash.toString(),
329
- };
330
- logger.verbose(`Rollup config args`, rollupConfigArgs);
331
- const rollupArgs = [
332
- addresses.feeJuicePortalAddress.toString(),
333
- addresses.rewardDistributorAddress.toString(),
334
- addresses.stakingAssetAddress.toString(),
335
- clients.walletClient.account.address.toString(),
336
- genesisStateArgs,
337
- rollupConfigArgs,
338
- ];
339
-
340
- const rollupAddress = await deployer.deploy(l1Artifacts.rollup, rollupArgs);
341
- logger.verbose(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs);
342
-
343
- await deployer.waitForDeployments();
344
- logger.verbose(`All core contracts have been deployed`);
345
-
346
- const rollup = getContract({
347
- address: getAddress(rollupAddress.toString()),
348
- abi: l1Artifacts.rollup.contractAbi,
349
- client: clients.walletClient,
350
- });
351
-
352
- const txHashes: Hex[] = [];
353
-
354
- if (args.initialValidators && args.initialValidators.length > 0) {
1204
+ ) => {
1205
+ const rollup = new RollupContract(extendedClient, rollupAddress);
1206
+ const activationThreshold = await rollup.getActivationThreshold();
1207
+ if (validators && validators.length > 0) {
355
1208
  // Check if some of the initial validators are already registered, so we support idempotent deployments
356
- let newValidatorsAddresses = args.initialValidators.map(v => v.toString());
357
- if (!args.acceleratedTestDeployments) {
358
- const validatorsInfo = await Promise.all(
359
- args.initialValidators.map(async address => ({
360
- address,
361
- ...(await rollup.read.getInfo([address.toString()])),
1209
+ if (!acceleratedTestDeployments) {
1210
+ const enrichedValidators = await Promise.all(
1211
+ validators.map(async operator => ({
1212
+ operator,
1213
+ status: await rollup.getStatus(operator.attester),
362
1214
  })),
363
1215
  );
364
- const existingValidators = validatorsInfo.filter(v => v.status !== 0);
1216
+ const existingValidators = enrichedValidators.filter(v => v.status !== 0);
365
1217
  if (existingValidators.length > 0) {
366
1218
  logger.warn(
367
1219
  `Validators ${existingValidators
368
- .map(v => v.address)
1220
+ .map(v => v.operator.attester)
369
1221
  .join(', ')} already exist. Skipping from initialization.`,
370
1222
  );
371
1223
  }
372
1224
 
373
- newValidatorsAddresses = validatorsInfo.filter(v => v.status === 0).map(v => v.address.toString());
1225
+ validators = enrichedValidators.filter(v => v.status === 0).map(v => v.operator);
1226
+ }
1227
+
1228
+ if (validators.length === 0) {
1229
+ logger.warn('No validators to add. Skipping.');
1230
+ return;
374
1231
  }
375
1232
 
376
- if (newValidatorsAddresses.length > 0) {
377
- // Mint tokens, approve them, use cheat code to initialise validator set without setting up the epoch.
378
- const stakeNeeded = args.minimumStake * BigInt(newValidatorsAddresses.length);
379
- await Promise.all(
380
- [
381
- await deployer.sendTransaction({
382
- to: addresses.stakingAssetAddress.toString(),
383
- data: encodeFunctionData({
384
- abi: l1Artifacts.stakingAsset.contractAbi,
385
- functionName: 'mint',
386
- args: [clients.walletClient.account.address, stakeNeeded],
387
- }),
1233
+ const gseContract = new GSEContract(extendedClient, gseAddress);
1234
+ const multiAdder = (await deployer.deploy(MultiAdderArtifact, [rollupAddress, deployer.client.account.address]))
1235
+ .address;
1236
+
1237
+ const makeValidatorTuples = async (validator: Operator) => {
1238
+ const registrationTuple = await gseContract.makeRegistrationTuple(validator.bn254SecretKey.getValue());
1239
+ return {
1240
+ attester: getAddress(validator.attester.toString()),
1241
+ withdrawer: getAddress(validator.withdrawer.toString()),
1242
+ ...registrationTuple,
1243
+ };
1244
+ };
1245
+
1246
+ const validatorsTuples = await Promise.all(validators.map(makeValidatorTuples));
1247
+
1248
+ // Mint tokens, approve them, use cheat code to initialize validator set without setting up the epoch.
1249
+ const stakeNeeded = activationThreshold * BigInt(validators.length);
1250
+
1251
+ await deployer.l1TxUtils.sendAndMonitorTransaction({
1252
+ to: stakingAssetAddress,
1253
+ data: encodeFunctionData({
1254
+ abi: StakingAssetArtifact.contractAbi,
1255
+ functionName: 'mint',
1256
+ args: [multiAdder.toString(), stakeNeeded],
1257
+ }),
1258
+ });
1259
+
1260
+ const entryQueueLengthBefore = await rollup.getEntryQueueLength();
1261
+ const validatorCountBefore = await rollup.getActiveAttesterCount();
1262
+
1263
+ logger.info(`Adding ${validators.length} validators to the rollup`);
1264
+
1265
+ const chunkSize = 16;
1266
+
1267
+ // We will add `chunkSize` validators to the queue until we have covered all of our validators.
1268
+ // The `chunkSize` needs to be small enough to fit inside a single tx, therefore 16.
1269
+ for (const c of chunk(validatorsTuples, chunkSize)) {
1270
+ await deployer.l1TxUtils.sendAndMonitorTransaction(
1271
+ {
1272
+ to: multiAdder.toString(),
1273
+ data: encodeFunctionData({
1274
+ abi: MultiAdderArtifact.contractAbi,
1275
+ functionName: 'addValidators',
1276
+ args: [c, BigInt(0)],
388
1277
  }),
389
- await deployer.sendTransaction({
390
- to: addresses.stakingAssetAddress.toString(),
391
- data: encodeFunctionData({
392
- abi: l1Artifacts.stakingAsset.contractAbi,
393
- functionName: 'approve',
394
- args: [rollupAddress.toString(), stakeNeeded],
395
- }),
1278
+ },
1279
+ {
1280
+ gasLimit: 16_000_000n,
1281
+ },
1282
+ );
1283
+ }
1284
+
1285
+ // After adding to the queue, we will now try to flush from it.
1286
+ // We are explicitly doing this as a second step instead of as part of adding to benefit
1287
+ // from the accounting used to speed the process up.
1288
+ // As the queue computes the amount of possible flushes in an epoch when told to flush,
1289
+ // waiting until we have added all we want allows us to benefit in the case were we added
1290
+ // enough to pass the bootstrap set size without needing to wait another epoch.
1291
+ // This is useful when we are testing as it speeds up the tests slightly.
1292
+ while (true) {
1293
+ // If the queue is empty, we can break
1294
+ if ((await rollup.getEntryQueueLength()) == 0n) {
1295
+ break;
1296
+ }
1297
+
1298
+ // If there are no available validator flushes, no need to even try
1299
+ if ((await rollup.getAvailableValidatorFlushes()) == 0n) {
1300
+ break;
1301
+ }
1302
+
1303
+ // Note that we are flushing at most `chunkSize` at each call
1304
+ await deployer.l1TxUtils.sendAndMonitorTransaction(
1305
+ {
1306
+ to: rollup.address,
1307
+ data: encodeFunctionData({
1308
+ abi: RollupArtifact.contractAbi,
1309
+ functionName: 'flushEntryQueue',
1310
+ args: [BigInt(chunkSize)],
396
1311
  }),
397
- ].map(tx => clients.publicClient.waitForTransactionReceipt({ hash: tx.txHash })),
1312
+ },
1313
+ {
1314
+ gasLimit: 16_000_000n,
1315
+ },
398
1316
  );
1317
+ }
399
1318
 
400
- const validators = newValidatorsAddresses.map(v => ({
401
- attester: v,
402
- proposer: getExpectedAddress(ForwarderAbi, ForwarderBytecode, [v], v).address,
403
- withdrawer: v,
404
- amount: args.minimumStake,
405
- }));
406
- // const initiateValidatorSetTxHash = await rollup.write.cheat__InitialiseValidatorSet([validators]);
407
- const initiateValidatorSetTxHash = await deployer.walletClient.writeContract({
408
- address: rollupAddress.toString(),
409
- abi: l1Artifacts.rollup.contractAbi,
410
- functionName: 'cheat__InitialiseValidatorSet',
411
- args: [validators],
412
- });
413
- txHashes.push(initiateValidatorSetTxHash);
414
- logger.info(`Initialized validator set`, {
415
- validators,
416
- txHash: initiateValidatorSetTxHash,
417
- });
1319
+ const entryQueueLengthAfter = await rollup.getEntryQueueLength();
1320
+ const validatorCountAfter = await rollup.getActiveAttesterCount();
1321
+
1322
+ if (
1323
+ entryQueueLengthAfter + validatorCountAfter <
1324
+ entryQueueLengthBefore + validatorCountBefore + BigInt(validators.length)
1325
+ ) {
1326
+ throw new Error(
1327
+ `Failed to add ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}. A likely issue is the bootstrap size.`,
1328
+ );
418
1329
  }
419
- }
420
1330
 
421
- await Promise.all(txHashes.map(txHash => clients.publicClient.waitForTransactionReceipt({ hash: txHash })));
1331
+ logger.info(
1332
+ `Added ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`,
1333
+ );
1334
+ }
1335
+ };
422
1336
 
423
- return new RollupContract(clients.publicClient, rollupAddress);
1337
+ /**
1338
+ * Initialize the fee asset handler and make it a minter on the fee asset.
1339
+ * @note This function will only be used for testing purposes.
1340
+ *
1341
+ * @param extendedClient - The L1 clients.
1342
+ * @param deployer - The L1 deployer.
1343
+ * @param feeAssetAddress - The address of the fee asset.
1344
+ * @param logger - The logger.
1345
+ */
1346
+ // eslint-disable-next-line camelcase
1347
+ export const cheat_initializeFeeAssetHandler = async (
1348
+ extendedClient: ExtendedViemWalletClient,
1349
+ deployer: L1Deployer,
1350
+ feeAssetAddress: EthAddress,
1351
+ logger: Logger,
1352
+ ): Promise<{
1353
+ feeAssetHandlerAddress: EthAddress;
1354
+ txHash: Hex;
1355
+ }> => {
1356
+ const feeAssetHandlerAddress = (
1357
+ await deployer.deploy(FeeAssetHandlerArtifact, [
1358
+ extendedClient.account.address,
1359
+ feeAssetAddress.toString(),
1360
+ BigInt(1e18),
1361
+ ])
1362
+ ).address;
1363
+ logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
1364
+
1365
+ const { txHash } = await deployer.sendTransaction({
1366
+ to: feeAssetAddress.toString(),
1367
+ data: encodeFunctionData({
1368
+ abi: FeeAssetArtifact.contractAbi,
1369
+ functionName: 'addMinter',
1370
+ args: [feeAssetHandlerAddress.toString()],
1371
+ }),
1372
+ });
1373
+ logger.verbose(`Added fee asset handler ${feeAssetHandlerAddress} as minter on fee asset in ${txHash}`);
1374
+ return { feeAssetHandlerAddress, txHash };
424
1375
  };
425
1376
 
426
1377
  /**
@@ -438,17 +1389,30 @@ export const deployL1Contracts = async (
438
1389
  chain: Chain,
439
1390
  logger: Logger,
440
1391
  args: DeployL1ContractsArgs,
441
- txUtilsConfig: L1TxUtilsConfig = defaultL1TxUtilsConfig,
1392
+ txUtilsConfig: L1TxUtilsConfig = getL1TxUtilsConfigEnvVars(),
1393
+ createVerificationJson: string | false = false,
442
1394
  ): Promise<DeployL1ContractsReturnType> => {
443
- const clients = createL1Clients(rpcUrls, account, chain);
444
- const { walletClient, publicClient } = clients;
1395
+ logger.info(`Deploying L1 contracts with config: ${jsonStringify(args)}`);
1396
+ validateConfig(args);
1397
+
1398
+ if (args.initialValidators && args.initialValidators.length > 0 && args.existingTokenAddress) {
1399
+ throw new Error(
1400
+ 'Cannot deploy with both initialValidators and existingTokenAddress. ' +
1401
+ 'Initial validator funding requires minting tokens, which is not possible with an external token.',
1402
+ );
1403
+ }
1404
+
1405
+ const l1Client = createExtendedL1Client(rpcUrls, account, chain);
1406
+
1407
+ // Deploy multicall3 if it does not exist in this network
1408
+ await deployMulticall3(l1Client, logger);
445
1409
 
446
1410
  // We are assuming that you are running this on a local anvil node which have 1s block times
447
1411
  // To align better with actual deployment, we update the block interval to 12s
448
1412
 
449
1413
  const rpcCall = async (method: string, params: any[]) => {
450
1414
  logger.info(`Calling ${method} with params: ${JSON.stringify(params)}`);
451
- return (await publicClient.transport.request({
1415
+ return (await l1Client.transport.request({
452
1416
  method,
453
1417
  params,
454
1418
  })) as any;
@@ -465,229 +1429,74 @@ export const deployL1Contracts = async (
465
1429
 
466
1430
  logger.verbose(`Deploying contracts from ${account.address.toString()}`);
467
1431
 
468
- // Governance stuff
1432
+ const dateProvider = new DateProvider();
469
1433
  const deployer = new L1Deployer(
470
- walletClient,
471
- publicClient,
1434
+ l1Client,
472
1435
  args.salt,
1436
+ dateProvider,
473
1437
  args.acceleratedTestDeployments,
474
1438
  logger,
475
1439
  txUtilsConfig,
1440
+ !!createVerificationJson,
476
1441
  );
477
1442
 
478
- const registryAddress = await deployer.deploy(l1Artifacts.registry, [account.address.toString()]);
479
- logger.verbose(`Deployed Registry at ${registryAddress}`);
480
-
481
- const feeAssetAddress = await deployer.deploy(l1Artifacts.feeAsset, ['FeeJuice', 'FEE', account.address.toString()]);
482
- logger.verbose(`Deployed Fee Juice at ${feeAssetAddress}`);
483
-
484
- const stakingAssetAddress = await deployer.deploy(l1Artifacts.stakingAsset, [
485
- 'Staking',
486
- 'STK',
487
- account.address.toString(),
488
- ]);
489
- logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
490
-
491
- const governanceProposerAddress = await deployer.deploy(l1Artifacts.governanceProposer, [
492
- registryAddress.toString(),
493
- args.governanceProposerQuorum,
494
- args.governanceProposerRoundSize,
495
- ]);
496
- logger.verbose(`Deployed GovernanceProposer at ${governanceProposerAddress}`);
497
-
498
- // @note @LHerskind the assets are expected to be the same at some point, but for better
499
- // configurability they are different for now.
500
- const governanceAddress = await deployer.deploy(l1Artifacts.governance, [
501
- feeAssetAddress.toString(),
502
- governanceProposerAddress.toString(),
503
- ]);
504
- logger.verbose(`Deployed Governance at ${governanceAddress}`);
505
-
506
- const coinIssuerAddress = await deployer.deploy(l1Artifacts.coinIssuer, [
507
- feeAssetAddress.toString(),
508
- 1n * 10n ** 18n, // @todo #8084
509
- governanceAddress.toString(),
510
- ]);
511
- logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
512
-
513
- const rewardDistributorAddress = await deployer.deploy(l1Artifacts.rewardDistributor, [
514
- feeAssetAddress.toString(),
515
- registryAddress.toString(),
516
- governanceAddress.toString(),
517
- ]);
518
- logger.verbose(`Deployed RewardDistributor at ${rewardDistributorAddress}`);
519
-
520
- const feeJuicePortalAddress = await deployer.deploy(l1Artifacts.feeJuicePortal, [
521
- registryAddress.toString(),
522
- feeAssetAddress.toString(),
523
- args.l2FeeJuiceAddress.toString(),
524
- ]);
525
- logger.verbose(`Deployed Fee Juice Portal at ${feeJuicePortalAddress}`);
526
-
527
- logger.verbose(`Waiting for governance contracts to be deployed`);
528
- await deployer.waitForDeployments();
529
- logger.verbose(`All governance contracts deployed`);
530
-
531
- const feeJuicePortal = getContract({
532
- address: feeJuicePortalAddress.toString(),
533
- abi: l1Artifacts.feeJuicePortal.contractAbi,
534
- client: walletClient,
535
- });
536
-
537
- const feeAsset = getContract({
538
- address: feeAssetAddress.toString(),
539
- abi: l1Artifacts.feeAsset.contractAbi,
540
- client: walletClient,
541
- });
542
- // Transaction hashes to await
543
- const txHashes: Hex[] = [];
544
-
545
- if (args.acceleratedTestDeployments || !(await feeAsset.read.freeForAll())) {
546
- const { txHash } = await deployer.sendTransaction({
547
- to: feeAssetAddress.toString(),
548
- data: encodeFunctionData({
549
- abi: l1Artifacts.feeAsset.contractAbi,
550
- functionName: 'setFreeForAll',
551
- args: [true],
552
- }),
553
- });
554
- logger.verbose(`Fee asset set to free for all in ${txHash}`);
555
- txHashes.push(txHash);
556
- }
557
-
558
- if (args.acceleratedTestDeployments || (await feeAsset.read.owner()) !== getAddress(coinIssuerAddress.toString())) {
559
- const { txHash } = await deployer.sendTransaction({
560
- to: feeAssetAddress.toString(),
561
- data: encodeFunctionData({
562
- abi: l1Artifacts.feeAsset.contractAbi,
563
- functionName: 'transferOwnership',
564
- args: [coinIssuerAddress.toString()],
565
- }),
566
- });
567
- logger.verbose(`Fee asset transferred ownership to coin issuer in ${txHash}`);
568
- txHashes.push(txHash);
569
- }
570
-
571
- // @note This value MUST match what is in `constants.nr`. It is currently specified here instead of just importing
572
- // because there is circular dependency hell. This is a temporary solution. #3342
573
- // @todo #8084
574
- // fund the portal contract with Fee Juice
575
- const FEE_JUICE_INITIAL_MINT = 200000000000000000000000n;
576
-
577
- // In fast mode, use the L1TxUtils to send transactions with nonce management
578
- const { txHash: mintTxHash } = await deployer.sendTransaction({
579
- to: feeAssetAddress.toString(),
580
- data: encodeFunctionData({
581
- abi: l1Artifacts.feeAsset.contractAbi,
582
- functionName: 'mint',
583
- args: [feeJuicePortalAddress.toString(), FEE_JUICE_INITIAL_MINT],
584
- }),
585
- });
586
- logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash} (accelerated test deployments)`);
587
- txHashes.push(mintTxHash);
588
-
589
- // @note This is used to ensure we fully wait for the transaction when running against a real chain
590
- // otherwise we execute subsequent transactions too soon
591
- if (!args.acceleratedTestDeployments) {
592
- await publicClient.waitForTransactionReceipt({ hash: mintTxHash });
593
- logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash}`);
594
- }
595
-
596
- // Check if portal needs initialization
597
- let needsInitialization = args.acceleratedTestDeployments;
598
- if (!args.acceleratedTestDeployments) {
599
- // Only check if not in fast mode and not already known to need initialization
600
- needsInitialization = !(await feeJuicePortal.read.initialized());
601
- }
602
- if (needsInitialization) {
603
- const { txHash: initPortalTxHash } = await deployer.sendTransaction({
604
- to: feeJuicePortalAddress.toString(),
605
- data: encodeFunctionData({
606
- abi: l1Artifacts.feeJuicePortal.contractAbi,
607
- functionName: 'initialize',
608
- args: [],
609
- }),
610
- });
611
-
612
- txHashes.push(initPortalTxHash);
613
- logger.verbose(`Fee juice portal initializing in tx ${initPortalTxHash}`);
614
- } else {
615
- logger.verbose(`Fee juice portal is already initialized`);
616
- }
617
- logger.verbose(
618
- `Initialized Fee Juice Portal at ${feeJuicePortalAddress} to bridge between L1 ${feeAssetAddress} to L2 ${args.l2FeeJuiceAddress}`,
619
- );
620
-
621
- const rollup = await deployRollup(
622
- {
623
- walletClient,
624
- publicClient,
625
- },
1443
+ const {
1444
+ feeAssetAddress,
1445
+ feeAssetHandlerAddress,
1446
+ stakingAssetAddress,
1447
+ stakingAssetHandlerAddress,
1448
+ registryAddress,
1449
+ gseAddress,
1450
+ governanceAddress,
1451
+ rewardDistributorAddress,
1452
+ zkPassportVerifierAddress,
1453
+ coinIssuerAddress,
1454
+ } = await deploySharedContracts(l1Client, deployer, args, logger);
1455
+ const { rollup, slashFactoryAddress } = await deployRollup(
1456
+ l1Client,
626
1457
  deployer,
627
1458
  args,
628
- { feeJuicePortalAddress, rewardDistributorAddress, stakingAssetAddress },
1459
+ {
1460
+ feeJuiceAddress: feeAssetAddress,
1461
+ registryAddress,
1462
+ gseAddress,
1463
+ rewardDistributorAddress,
1464
+ stakingAssetAddress,
1465
+ governanceAddress,
1466
+ },
629
1467
  logger,
630
1468
  );
631
- const slashFactoryAddress = await deploySlashFactory(deployer, rollup.address, logger);
632
1469
 
633
1470
  logger.verbose('Waiting for rollup and slash factory to be deployed');
634
1471
  await deployer.waitForDeployments();
635
- logger.verbose(`Rollup and slash factory deployed`);
636
-
637
- // We need to call a function on the registry to set the various contract addresses.
638
- const registryContract = getContract({
639
- address: getAddress(registryAddress.toString()),
640
- abi: l1Artifacts.registry.contractAbi,
641
- client: walletClient,
642
- });
643
1472
 
644
- if (
645
- args.acceleratedTestDeployments ||
646
- !(await registryContract.read.isRollupRegistered([getAddress(rollup.address.toString())]))
647
- ) {
648
- const { txHash: upgradeTxHash } = await deployer.sendTransaction({
649
- to: registryAddress.toString(),
650
- data: encodeFunctionData({
651
- abi: l1Artifacts.registry.contractAbi,
652
- functionName: 'upgrade',
653
- args: [getAddress(rollup.address.toString())],
654
- }),
655
- });
656
- logger.verbose(
657
- `Upgrading registry contract at ${registryAddress} to rollup ${rollup.address} in tx ${upgradeTxHash}`,
658
- );
659
- txHashes.push(upgradeTxHash);
660
- } else {
661
- logger.verbose(`Registry ${registryAddress} has already registered rollup ${rollup.address}`);
662
- }
1473
+ // Now that the rollup has been deployed and added to the registry, transfer ownership to governance
1474
+ const { dateGatedRelayerAddress } = await handoverToGovernance(
1475
+ l1Client,
1476
+ deployer,
1477
+ registryAddress,
1478
+ gseAddress,
1479
+ coinIssuerAddress,
1480
+ feeAssetAddress,
1481
+ governanceAddress,
1482
+ logger,
1483
+ args.acceleratedTestDeployments,
1484
+ !!args.existingTokenAddress,
1485
+ );
663
1486
 
664
- // If the owner is not the Governance contract, transfer ownership to the Governance contract
665
- if (
666
- args.acceleratedTestDeployments ||
667
- (await registryContract.read.owner()) !== getAddress(governanceAddress.toString())
668
- ) {
669
- // TODO(md): add send transaction to the deployer such that we do not need to manage tx hashes here
670
- const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
671
- to: registryAddress.toString(),
672
- data: encodeFunctionData({
673
- abi: l1Artifacts.registry.contractAbi,
674
- functionName: 'transferOwnership',
675
- args: [getAddress(governanceAddress.toString())],
676
- }),
677
- });
678
- logger.verbose(
679
- `Transferring the ownership of the registry contract at ${registryAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
680
- );
681
- txHashes.push(transferOwnershipTxHash);
682
- }
1487
+ logger.info(`Handing over to governance complete`);
683
1488
 
684
- // Wait for all actions to be mined
685
- await Promise.all(txHashes.map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })));
686
1489
  logger.verbose(`All transactions for L1 deployment have been mined`);
687
- const l1Contracts = await RegistryContract.collectAddresses(publicClient, registryAddress, 'canonical');
1490
+ const l1Contracts = await RegistryContract.collectAddresses(l1Client, registryAddress, 'canonical');
688
1491
 
689
1492
  logger.info(`Aztec L1 contracts initialized`, l1Contracts);
690
1493
 
1494
+ // Write verification data (constructor args + linked libraries) to file for later forge verify
1495
+ if (createVerificationJson) {
1496
+ await generateRollupVerificationRecords(rollup, deployer, args, l1Contracts, l1Client, logger);
1497
+ await writeVerificationJson(deployer, createVerificationJson, chain.id, '', logger);
1498
+ }
1499
+
691
1500
  if (isAnvilTestChain(chain.id)) {
692
1501
  // @note We make a time jump PAST the very first slot to not have to deal with the edge case of the first slot.
693
1502
  // The edge case being that the genesis block is already occupying slot 0, so we cannot have another block.
@@ -712,55 +1521,95 @@ export const deployL1Contracts = async (
712
1521
  }
713
1522
 
714
1523
  return {
715
- walletClient,
716
- publicClient,
1524
+ rollupVersion: Number(await rollup.getVersion()),
1525
+ l1Client: l1Client,
717
1526
  l1ContractAddresses: {
718
1527
  ...l1Contracts,
719
1528
  slashFactoryAddress,
1529
+ feeAssetHandlerAddress,
1530
+ stakingAssetHandlerAddress,
1531
+ zkPassportVerifierAddress,
1532
+ coinIssuerAddress,
1533
+ dateGatedRelayerAddress,
720
1534
  },
721
1535
  };
722
1536
  };
723
1537
 
724
- class L1Deployer {
1538
+ export class L1Deployer {
725
1539
  private salt: Hex | undefined;
726
1540
  private txHashes: Hex[] = [];
727
1541
  public readonly l1TxUtils: L1TxUtils;
1542
+ public readonly verificationRecords: VerificationRecord[] = [];
728
1543
 
729
1544
  constructor(
730
- public readonly walletClient: ViemWalletClient,
731
- private publicClient: ViemPublicClient,
1545
+ public readonly client: ExtendedViemWalletClient,
732
1546
  maybeSalt: number | undefined,
1547
+ dateProvider: DateProvider = new DateProvider(),
733
1548
  private acceleratedTestDeployments: boolean = false,
734
1549
  private logger: Logger = createLogger('L1Deployer'),
735
1550
  private txUtilsConfig?: L1TxUtilsConfig,
1551
+ private createVerificationJson: boolean = false,
736
1552
  ) {
737
1553
  this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), { size: 32 }) : undefined;
738
- this.l1TxUtils = new L1TxUtils(
739
- this.publicClient,
740
- this.walletClient,
741
- this.logger,
742
- this.txUtilsConfig,
743
- this.acceleratedTestDeployments,
1554
+ this.l1TxUtils = createL1TxUtilsFromViemWallet(
1555
+ this.client,
1556
+ { logger: this.logger, dateProvider },
1557
+ { ...this.txUtilsConfig, debugMaxGasLimit: acceleratedTestDeployments },
744
1558
  );
745
1559
  }
746
1560
 
747
- async deploy(params: ContractArtifacts, args: readonly unknown[] = []): Promise<EthAddress> {
748
- const { txHash, address } = await deployL1Contract(
749
- this.walletClient,
750
- this.publicClient,
751
- params.contractAbi,
752
- params.contractBytecode,
753
- args,
754
- this.salt,
755
- params.libraries,
756
- this.logger,
757
- this.l1TxUtils,
758
- this.acceleratedTestDeployments,
759
- );
760
- if (txHash) {
761
- this.txHashes.push(txHash);
1561
+ async deploy<const TAbi extends Abi>(
1562
+ params: ContractArtifacts<TAbi>,
1563
+ args?: ContractConstructorArgs<TAbi>,
1564
+ opts: { gasLimit?: bigint } = {},
1565
+ ): Promise<{ address: EthAddress; existed: boolean }> {
1566
+ this.logger.debug(`Deploying ${params.name} contract`, { args });
1567
+ try {
1568
+ const { txHash, address, deployedLibraries, existed } = await deployL1Contract(
1569
+ this.client,
1570
+ params.contractAbi,
1571
+ params.contractBytecode,
1572
+ (args ?? []) as readonly unknown[],
1573
+ {
1574
+ salt: this.salt,
1575
+ libraries: params.libraries,
1576
+ logger: this.logger,
1577
+ l1TxUtils: this.l1TxUtils,
1578
+ acceleratedTestDeployments: this.acceleratedTestDeployments,
1579
+ gasLimit: opts.gasLimit,
1580
+ },
1581
+ );
1582
+ if (txHash) {
1583
+ this.txHashes.push(txHash);
1584
+ }
1585
+ this.logger.debug(`Deployed ${params.name} at ${address}`, { args });
1586
+
1587
+ if (this.createVerificationJson) {
1588
+ // Encode constructor args for verification
1589
+ let constructorArgsHex: Hex = '0x';
1590
+ try {
1591
+ const abiItem: any = (params.contractAbi as any[]).find((x: any) => x && x.type === 'constructor');
1592
+ const inputDefs: any[] = abiItem && Array.isArray(abiItem.inputs) ? abiItem.inputs : [];
1593
+ constructorArgsHex =
1594
+ inputDefs.length > 0 ? (encodeAbiParameters(inputDefs as any, (args ?? []) as any) as Hex) : ('0x' as Hex);
1595
+ } catch {
1596
+ constructorArgsHex = '0x' as Hex;
1597
+ }
1598
+
1599
+ this.verificationRecords.push({
1600
+ name: params.name,
1601
+ address: address.toString(),
1602
+ constructorArgsHex,
1603
+ libraries: deployedLibraries ?? [],
1604
+ });
1605
+ }
1606
+ return {
1607
+ address,
1608
+ existed,
1609
+ };
1610
+ } catch (error) {
1611
+ throw new Error(`Failed to deploy ${params.name}`, { cause: formatViemError(error) });
762
1612
  }
763
- return address;
764
1613
  }
765
1614
 
766
1615
  async waitForDeployments(): Promise<void> {
@@ -772,17 +1621,29 @@ class L1Deployer {
772
1621
  return;
773
1622
  }
774
1623
 
775
- this.logger.info(`Waiting for ${this.txHashes.length} transactions to be mined...`);
776
- await Promise.all(this.txHashes.map(txHash => this.publicClient.waitForTransactionReceipt({ hash: txHash })));
777
- this.logger.info('All transactions mined successfully');
1624
+ this.logger.verbose(`Waiting for ${this.txHashes.length} transactions to be mined`, { txHashes: this.txHashes });
1625
+ const receipts = await Promise.all(
1626
+ this.txHashes.map(txHash => this.client.waitForTransactionReceipt({ hash: txHash })),
1627
+ );
1628
+ const failed = receipts.filter(r => r.status !== 'success');
1629
+ if (failed.length > 0) {
1630
+ throw new Error(`Some deployment txs have failed: ${failed.map(f => f.transactionHash).join(', ')}`);
1631
+ }
1632
+ this.logger.info('All transactions mined successfully', { txHashes: this.txHashes });
778
1633
  }
779
1634
 
780
- sendTransaction(tx: L1TxRequest): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> {
781
- return this.l1TxUtils.sendTransaction(tx);
1635
+ sendTransaction(
1636
+ tx: L1TxRequest,
1637
+ options?: L1TxConfig,
1638
+ ): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> {
1639
+ return this.l1TxUtils.sendTransaction(tx, options).then(({ txHash, state }) => ({
1640
+ txHash,
1641
+ gasLimit: state.gasLimit,
1642
+ gasPrice: state.gasPrice,
1643
+ }));
782
1644
  }
783
1645
  }
784
1646
 
785
- // docs:start:deployL1Contract
786
1647
  /**
787
1648
  * Helper function to deploy ETH contracts.
788
1649
  * @param walletClient - A viem WalletClient.
@@ -790,26 +1651,42 @@ class L1Deployer {
790
1651
  * @param abi - The ETH contract's ABI (as abitype's Abi).
791
1652
  * @param bytecode - The ETH contract's bytecode.
792
1653
  * @param args - Constructor arguments for the contract.
793
- * @param maybeSalt - Optional salt for CREATE2 deployment (does not wait for deployment tx to be mined if set, does not send tx if contract already exists).
1654
+ * @param salt - Optional salt for CREATE2 deployment (does not wait for deployment tx to be mined if set, does not send tx if contract already exists).
794
1655
  * @returns The ETH address the contract was deployed to.
795
1656
  */
796
1657
  export async function deployL1Contract(
797
- walletClient: ViemWalletClient,
798
- publicClient: ViemPublicClient,
1658
+ extendedClient: ExtendedViemWalletClient,
799
1659
  abi: Narrow<Abi | readonly unknown[]>,
800
1660
  bytecode: Hex,
801
1661
  args: readonly unknown[] = [],
802
- maybeSalt?: Hex,
803
- libraries?: Libraries,
804
- logger?: Logger,
805
- l1TxUtils?: L1TxUtils,
806
- acceleratedTestDeployments: boolean = false,
807
- ): Promise<{ address: EthAddress; txHash: Hex | undefined }> {
1662
+ opts: {
1663
+ salt?: Hex;
1664
+ libraries?: Libraries;
1665
+ logger?: Logger;
1666
+ l1TxUtils?: L1TxUtils;
1667
+ gasLimit?: bigint;
1668
+ acceleratedTestDeployments?: boolean;
1669
+ } = {},
1670
+ ): Promise<{
1671
+ address: EthAddress;
1672
+ txHash: Hex | undefined;
1673
+ deployedLibraries?: VerificationLibraryEntry[];
1674
+ existed: boolean;
1675
+ }> {
808
1676
  let txHash: Hex | undefined = undefined;
809
1677
  let resultingAddress: Hex | null | undefined = undefined;
1678
+ const deployedLibraries: VerificationLibraryEntry[] = [];
1679
+
1680
+ const { salt: saltFromOpts, libraries, logger, gasLimit, acceleratedTestDeployments } = opts;
1681
+ let { l1TxUtils } = opts;
810
1682
 
811
1683
  if (!l1TxUtils) {
812
- l1TxUtils = new L1TxUtils(publicClient, walletClient, logger, undefined, acceleratedTestDeployments);
1684
+ const config = getL1TxUtilsConfigEnvVars();
1685
+ l1TxUtils = createL1TxUtilsFromViemWallet(
1686
+ extendedClient,
1687
+ { logger },
1688
+ { ...config, debugMaxGasLimit: acceleratedTestDeployments },
1689
+ );
813
1690
  }
814
1691
 
815
1692
  if (libraries) {
@@ -828,24 +1705,38 @@ export async function deployL1Contract(
828
1705
  const libraryTxs: Hex[] = [];
829
1706
  for (const libraryName in libraries?.libraryCode) {
830
1707
  const lib = libraries.libraryCode[libraryName];
831
-
1708
+ const { libraries: _libraries, ...optsWithoutLibraries } = opts;
832
1709
  const { address, txHash } = await deployL1Contract(
833
- walletClient,
834
- publicClient,
1710
+ extendedClient,
835
1711
  lib.contractAbi,
836
1712
  lib.contractBytecode,
837
1713
  [],
838
- maybeSalt,
839
- undefined,
840
- logger,
841
- l1TxUtils,
842
- acceleratedTestDeployments,
1714
+ optsWithoutLibraries,
843
1715
  );
844
1716
 
1717
+ // Log deployed library name and address for easier verification/triage
1718
+ logger?.verbose(`Linked library deployed`, { library: libraryName, address: address.toString(), txHash });
1719
+
845
1720
  if (txHash) {
846
1721
  libraryTxs.push(txHash);
847
1722
  }
848
1723
 
1724
+ // Try to find the source file for this library from linkReferences
1725
+ let fileNameForLibrary: string | undefined = undefined;
1726
+ for (const fileName in libraries.linkReferences) {
1727
+ if (libraries.linkReferences[fileName] && libraries.linkReferences[fileName][libraryName]) {
1728
+ fileNameForLibrary = fileName;
1729
+ break;
1730
+ }
1731
+ }
1732
+ if (fileNameForLibrary) {
1733
+ deployedLibraries.push({
1734
+ file: fileNameForLibrary,
1735
+ contract: libraryName,
1736
+ address: address.toString(),
1737
+ });
1738
+ }
1739
+
849
1740
  for (const linkRef in libraries.linkReferences) {
850
1741
  for (const contractName in libraries.linkReferences[linkRef]) {
851
1742
  // If the library name matches the one we just deployed, we replace it.
@@ -877,7 +1768,7 @@ export async function deployL1Contract(
877
1768
  // However, if we are in fast mode or using debugMaxGasLimit, we will skip simulation, so we can skip waiting.
878
1769
  if (libraryTxs.length > 0 && !acceleratedTestDeployments) {
879
1770
  logger?.verbose(`Awaiting for linked libraries to be deployed`);
880
- await Promise.all(libraryTxs.map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })));
1771
+ await Promise.all(libraryTxs.map(txHash => extendedClient.waitForTransactionReceipt({ hash: txHash })));
881
1772
  } else {
882
1773
  logger?.verbose(
883
1774
  `Skipping waiting for linked libraries to be deployed ${
@@ -887,28 +1778,40 @@ export async function deployL1Contract(
887
1778
  }
888
1779
  }
889
1780
 
890
- if (maybeSalt) {
891
- const { address, paddedSalt: salt, calldata } = getExpectedAddress(abi, bytecode, args, maybeSalt);
1781
+ let existed = false;
1782
+
1783
+ if (saltFromOpts) {
1784
+ logger?.info(`Deploying contract with salt ${saltFromOpts}`);
1785
+ const { address, paddedSalt: salt, calldata } = getExpectedAddress(abi, bytecode, args, saltFromOpts);
892
1786
  resultingAddress = address;
893
- const existing = await publicClient.getBytecode({ address: resultingAddress });
1787
+ const existing = await extendedClient.getCode({ address: resultingAddress });
894
1788
  if (existing === undefined || existing === '0x') {
895
- const res = await l1TxUtils.sendTransaction({
896
- to: DEPLOYER_ADDRESS,
897
- data: concatHex([salt, calldata]),
898
- });
1789
+ try {
1790
+ await l1TxUtils.simulate({ to: DEPLOYER_ADDRESS, data: concatHex([salt, calldata]), gas: gasLimit });
1791
+ } catch (err) {
1792
+ logger?.error(`Failed to simulate deployment tx using universal deployer`, err);
1793
+ await l1TxUtils.simulate({ to: null, data: encodeDeployData({ abi, bytecode, args }), gas: gasLimit });
1794
+ }
1795
+ const res = await l1TxUtils.sendTransaction(
1796
+ { to: DEPLOYER_ADDRESS, data: concatHex([salt, calldata]) },
1797
+ { gasLimit },
1798
+ );
899
1799
  txHash = res.txHash;
900
1800
 
901
1801
  logger?.verbose(`Deployed contract with salt ${salt} to address ${resultingAddress} in tx ${txHash}.`);
902
1802
  } else {
903
1803
  logger?.verbose(`Skipping existing deployment of contract with salt ${salt} to address ${resultingAddress}`);
1804
+ existed = true;
904
1805
  }
905
1806
  } else {
906
- // Regular deployment path
907
1807
  const deployData = encodeDeployData({ abi, bytecode, args });
908
- const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
909
- to: null,
910
- data: deployData,
911
- });
1808
+ const { receipt } = await l1TxUtils.sendAndMonitorTransaction(
1809
+ {
1810
+ to: null,
1811
+ data: deployData,
1812
+ },
1813
+ { gasLimit },
1814
+ );
912
1815
 
913
1816
  txHash = receipt.transactionHash;
914
1817
  resultingAddress = receipt.contractAddress;
@@ -921,7 +1824,7 @@ export async function deployL1Contract(
921
1824
  }
922
1825
  }
923
1826
 
924
- return { address: EthAddress.fromString(resultingAddress!), txHash };
1827
+ return { address: EthAddress.fromString(resultingAddress!), txHash, deployedLibraries, existed };
925
1828
  }
926
1829
 
927
1830
  export function getExpectedAddress(
@@ -944,5 +1847,3 @@ export function getExpectedAddress(
944
1847
  calldata,
945
1848
  };
946
1849
  }
947
-
948
- // docs:end:deployL1Contract