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