@aztec/ethereum 0.0.0-test.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dest/chain.d.ts +25 -0
- package/dest/chain.d.ts.map +1 -0
- package/dest/chain.js +53 -0
- package/dest/client.d.ts +16 -0
- package/dest/client.d.ts.map +1 -0
- package/dest/client.js +31 -0
- package/dest/config.d.ts +39 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +70 -0
- package/dest/constants.d.ts +4 -0
- package/dest/constants.d.ts.map +1 -0
- package/dest/constants.js +2 -0
- package/dest/contracts/empire_base.d.ts +13 -0
- package/dest/contracts/empire_base.d.ts.map +1 -0
- package/dest/contracts/empire_base.js +11 -0
- package/dest/contracts/fee_juice.d.ts +15 -0
- package/dest/contracts/fee_juice.d.ts.map +1 -0
- package/dest/contracts/fee_juice.js +52 -0
- package/dest/contracts/forwarder.d.ts +24 -0
- package/dest/contracts/forwarder.d.ts.map +1 -0
- package/dest/contracts/forwarder.js +101 -0
- package/dest/contracts/governance.d.ts +79 -0
- package/dest/contracts/governance.d.ts.map +1 -0
- package/dest/contracts/governance.js +247 -0
- package/dest/contracts/governance_proposer.d.ts +28 -0
- package/dest/contracts/governance_proposer.d.ts.map +1 -0
- package/dest/contracts/governance_proposer.js +82 -0
- package/dest/contracts/index.d.ts +9 -0
- package/dest/contracts/index.d.ts.map +1 -0
- package/dest/contracts/index.js +8 -0
- package/dest/contracts/registry.d.ts +24 -0
- package/dest/contracts/registry.d.ts.map +1 -0
- package/dest/contracts/registry.js +85 -0
- package/dest/contracts/rollup.d.ts +92 -0
- package/dest/contracts/rollup.d.ts.map +1 -0
- package/dest/contracts/rollup.js +234 -0
- package/dest/contracts/slashing_proposer.d.ts +21 -0
- package/dest/contracts/slashing_proposer.d.ts.map +1 -0
- package/dest/contracts/slashing_proposer.js +47 -0
- package/dest/deploy_l1_contracts.d.ts +21210 -0
- package/dest/deploy_l1_contracts.d.ts.map +1 -0
- package/dest/deploy_l1_contracts.js +687 -0
- package/dest/eth_cheat_codes.d.ts +147 -0
- package/dest/eth_cheat_codes.d.ts.map +1 -0
- package/dest/eth_cheat_codes.js +303 -0
- package/dest/index.d.ts +14 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +13 -0
- package/dest/l1_contract_addresses.d.ts +57 -0
- package/dest/l1_contract_addresses.d.ts.map +1 -0
- package/dest/l1_contract_addresses.js +97 -0
- package/dest/l1_reader.d.ts +16 -0
- package/dest/l1_reader.d.ts.map +1 -0
- package/dest/l1_reader.js +27 -0
- package/dest/l1_tx_utils.d.ts +192 -0
- package/dest/l1_tx_utils.d.ts.map +1 -0
- package/dest/l1_tx_utils.js +641 -0
- package/dest/l1_tx_utils_with_blobs.d.ts +12 -0
- package/dest/l1_tx_utils_with_blobs.d.ts.map +1 -0
- package/dest/l1_tx_utils_with_blobs.js +64 -0
- package/dest/queries.d.ts +12 -0
- package/dest/queries.d.ts.map +1 -0
- package/dest/queries.js +35 -0
- package/dest/test/delayed_tx_utils.d.ts +8 -0
- package/dest/test/delayed_tx_utils.d.ts.map +1 -0
- package/dest/test/delayed_tx_utils.js +21 -0
- package/dest/test/eth_cheat_codes_with_state.d.ts +18 -0
- package/dest/test/eth_cheat_codes_with_state.d.ts.map +1 -0
- package/dest/test/eth_cheat_codes_with_state.js +34 -0
- package/dest/test/index.d.ts +6 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +5 -0
- package/dest/test/start_anvil.d.ts +12 -0
- package/dest/test/start_anvil.d.ts.map +1 -0
- package/dest/test/start_anvil.js +46 -0
- package/dest/test/tx_delayer.d.ts +25 -0
- package/dest/test/tx_delayer.d.ts.map +1 -0
- package/dest/test/tx_delayer.js +116 -0
- package/dest/test/upgrade_utils.d.ts +11 -0
- package/dest/test/upgrade_utils.d.ts.map +1 -0
- package/dest/test/upgrade_utils.js +104 -0
- package/dest/types.d.ts +14 -0
- package/dest/types.d.ts.map +1 -0
- package/dest/types.js +1 -0
- package/dest/utils.d.ts +24 -0
- package/dest/utils.d.ts.map +1 -0
- package/dest/utils.js +209 -0
- package/package.json +98 -0
- package/src/chain.ts +71 -0
- package/src/client.ts +58 -0
- package/src/config.ts +103 -0
- package/src/constants.ts +4 -0
- package/src/contracts/empire_base.ts +19 -0
- package/src/contracts/fee_juice.ts +43 -0
- package/src/contracts/forwarder.ts +132 -0
- package/src/contracts/governance.ts +285 -0
- package/src/contracts/governance_proposer.ts +82 -0
- package/src/contracts/index.ts +8 -0
- package/src/contracts/registry.ts +106 -0
- package/src/contracts/rollup.ts +274 -0
- package/src/contracts/slashing_proposer.ts +51 -0
- package/src/deploy_l1_contracts.ts +948 -0
- package/src/eth_cheat_codes.ts +314 -0
- package/src/index.ts +13 -0
- package/src/l1_contract_addresses.ts +109 -0
- package/src/l1_reader.ts +42 -0
- package/src/l1_tx_utils.ts +847 -0
- package/src/l1_tx_utils_with_blobs.ts +86 -0
- package/src/queries.ts +58 -0
- package/src/test/delayed_tx_utils.ts +24 -0
- package/src/test/eth_cheat_codes_with_state.ts +38 -0
- package/src/test/index.ts +5 -0
- package/src/test/start_anvil.ts +52 -0
- package/src/test/tx_delayer.ts +163 -0
- package/src/test/upgrade_utils.ts +100 -0
- package/src/types.ts +33 -0
- package/src/utils.ts +276 -0
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
|
+
import type { Fr } from '@aztec/foundation/fields';
|
|
3
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
4
|
+
import {
|
|
5
|
+
CoinIssuerAbi,
|
|
6
|
+
CoinIssuerBytecode,
|
|
7
|
+
ExtRollupLibAbi,
|
|
8
|
+
ExtRollupLibBytecode,
|
|
9
|
+
FeeJuicePortalAbi,
|
|
10
|
+
FeeJuicePortalBytecode,
|
|
11
|
+
ForwarderAbi,
|
|
12
|
+
ForwarderBytecode,
|
|
13
|
+
GovernanceAbi,
|
|
14
|
+
GovernanceBytecode,
|
|
15
|
+
GovernanceProposerAbi,
|
|
16
|
+
GovernanceProposerBytecode,
|
|
17
|
+
InboxAbi,
|
|
18
|
+
InboxBytecode,
|
|
19
|
+
OutboxAbi,
|
|
20
|
+
OutboxBytecode,
|
|
21
|
+
RegisterNewRollupVersionPayloadAbi,
|
|
22
|
+
RegisterNewRollupVersionPayloadBytecode,
|
|
23
|
+
RegistryAbi,
|
|
24
|
+
RegistryBytecode,
|
|
25
|
+
RewardDistributorAbi,
|
|
26
|
+
RewardDistributorBytecode,
|
|
27
|
+
RollupAbi,
|
|
28
|
+
RollupBytecode,
|
|
29
|
+
RollupLinkReferences,
|
|
30
|
+
SlashFactoryAbi,
|
|
31
|
+
SlashFactoryBytecode,
|
|
32
|
+
TestERC20Abi,
|
|
33
|
+
TestERC20Bytecode,
|
|
34
|
+
ValidatorSelectionLibAbi,
|
|
35
|
+
ValidatorSelectionLibBytecode,
|
|
36
|
+
} from '@aztec/l1-artifacts';
|
|
37
|
+
|
|
38
|
+
import type { Abi, Narrow } from 'abitype';
|
|
39
|
+
import {
|
|
40
|
+
type Chain,
|
|
41
|
+
type Hex,
|
|
42
|
+
concatHex,
|
|
43
|
+
createPublicClient,
|
|
44
|
+
createWalletClient,
|
|
45
|
+
encodeDeployData,
|
|
46
|
+
encodeFunctionData,
|
|
47
|
+
fallback,
|
|
48
|
+
getAddress,
|
|
49
|
+
getContract,
|
|
50
|
+
getContractAddress,
|
|
51
|
+
http,
|
|
52
|
+
numberToHex,
|
|
53
|
+
padHex,
|
|
54
|
+
publicActions,
|
|
55
|
+
} from 'viem';
|
|
56
|
+
import { type HDAccount, type PrivateKeyAccount, mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
57
|
+
import { foundry } from 'viem/chains';
|
|
58
|
+
|
|
59
|
+
import { isAnvilTestChain } from './chain.js';
|
|
60
|
+
import type { L1ContractsConfig } from './config.js';
|
|
61
|
+
import { RegistryContract } from './contracts/registry.js';
|
|
62
|
+
import { RollupContract } from './contracts/rollup.js';
|
|
63
|
+
import type { L1ContractAddresses } from './l1_contract_addresses.js';
|
|
64
|
+
import {
|
|
65
|
+
type GasPrice,
|
|
66
|
+
type L1TxRequest,
|
|
67
|
+
L1TxUtils,
|
|
68
|
+
type L1TxUtilsConfig,
|
|
69
|
+
defaultL1TxUtilsConfig,
|
|
70
|
+
} from './l1_tx_utils.js';
|
|
71
|
+
import type { L1Clients, ViemPublicClient, ViemWalletClient } from './types.js';
|
|
72
|
+
|
|
73
|
+
export const DEPLOYER_ADDRESS: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Return type of the deployL1Contract function.
|
|
77
|
+
*/
|
|
78
|
+
export type DeployL1ContractsReturnType = {
|
|
79
|
+
/**
|
|
80
|
+
* Wallet Client Type.
|
|
81
|
+
*/
|
|
82
|
+
walletClient: ViemWalletClient;
|
|
83
|
+
/**
|
|
84
|
+
* Public Client Type.
|
|
85
|
+
*/
|
|
86
|
+
publicClient: ViemPublicClient;
|
|
87
|
+
/**
|
|
88
|
+
* The currently deployed l1 contract addresses
|
|
89
|
+
*/
|
|
90
|
+
l1ContractAddresses: L1ContractAddresses;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export interface LinkReferences {
|
|
94
|
+
[fileName: string]: {
|
|
95
|
+
[contractName: string]: ReadonlyArray<{
|
|
96
|
+
start: number;
|
|
97
|
+
length: number;
|
|
98
|
+
}>;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface Libraries {
|
|
103
|
+
linkReferences: LinkReferences;
|
|
104
|
+
libraryCode: Record<string, ContractArtifacts>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Contract artifacts
|
|
109
|
+
*/
|
|
110
|
+
export interface ContractArtifacts {
|
|
111
|
+
/**
|
|
112
|
+
* The contract abi.
|
|
113
|
+
*/
|
|
114
|
+
contractAbi: Narrow<Abi | readonly unknown[]>;
|
|
115
|
+
/**
|
|
116
|
+
* The contract bytecode
|
|
117
|
+
*/
|
|
118
|
+
contractBytecode: Hex;
|
|
119
|
+
/**
|
|
120
|
+
* The contract libraries
|
|
121
|
+
*/
|
|
122
|
+
libraries?: Libraries;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const l1Artifacts = {
|
|
126
|
+
registry: {
|
|
127
|
+
contractAbi: RegistryAbi,
|
|
128
|
+
contractBytecode: RegistryBytecode as Hex,
|
|
129
|
+
},
|
|
130
|
+
inbox: {
|
|
131
|
+
contractAbi: InboxAbi,
|
|
132
|
+
contractBytecode: InboxBytecode as Hex,
|
|
133
|
+
},
|
|
134
|
+
outbox: {
|
|
135
|
+
contractAbi: OutboxAbi,
|
|
136
|
+
contractBytecode: OutboxBytecode as Hex,
|
|
137
|
+
},
|
|
138
|
+
rollup: {
|
|
139
|
+
contractAbi: RollupAbi,
|
|
140
|
+
contractBytecode: RollupBytecode as Hex,
|
|
141
|
+
libraries: {
|
|
142
|
+
linkReferences: RollupLinkReferences,
|
|
143
|
+
libraryCode: {
|
|
144
|
+
ValidatorSelectionLib: {
|
|
145
|
+
contractAbi: ValidatorSelectionLibAbi,
|
|
146
|
+
contractBytecode: ValidatorSelectionLibBytecode as Hex,
|
|
147
|
+
},
|
|
148
|
+
ExtRollupLib: {
|
|
149
|
+
contractAbi: ExtRollupLibAbi,
|
|
150
|
+
contractBytecode: ExtRollupLibBytecode as Hex,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
stakingAsset: {
|
|
156
|
+
contractAbi: TestERC20Abi,
|
|
157
|
+
contractBytecode: TestERC20Bytecode as Hex,
|
|
158
|
+
},
|
|
159
|
+
feeAsset: {
|
|
160
|
+
contractAbi: TestERC20Abi,
|
|
161
|
+
contractBytecode: TestERC20Bytecode as Hex,
|
|
162
|
+
},
|
|
163
|
+
feeJuicePortal: {
|
|
164
|
+
contractAbi: FeeJuicePortalAbi,
|
|
165
|
+
contractBytecode: FeeJuicePortalBytecode as Hex,
|
|
166
|
+
},
|
|
167
|
+
rewardDistributor: {
|
|
168
|
+
contractAbi: RewardDistributorAbi,
|
|
169
|
+
contractBytecode: RewardDistributorBytecode as Hex,
|
|
170
|
+
},
|
|
171
|
+
coinIssuer: {
|
|
172
|
+
contractAbi: CoinIssuerAbi,
|
|
173
|
+
contractBytecode: CoinIssuerBytecode as Hex,
|
|
174
|
+
},
|
|
175
|
+
governanceProposer: {
|
|
176
|
+
contractAbi: GovernanceProposerAbi,
|
|
177
|
+
contractBytecode: GovernanceProposerBytecode as Hex,
|
|
178
|
+
},
|
|
179
|
+
governance: {
|
|
180
|
+
contractAbi: GovernanceAbi,
|
|
181
|
+
contractBytecode: GovernanceBytecode as Hex,
|
|
182
|
+
},
|
|
183
|
+
slashFactory: {
|
|
184
|
+
contractAbi: SlashFactoryAbi,
|
|
185
|
+
contractBytecode: SlashFactoryBytecode as Hex,
|
|
186
|
+
},
|
|
187
|
+
registerNewRollupVersionPayload: {
|
|
188
|
+
contractAbi: RegisterNewRollupVersionPayloadAbi,
|
|
189
|
+
contractBytecode: RegisterNewRollupVersionPayloadBytecode as Hex,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export interface DeployL1ContractsArgs extends L1ContractsConfig {
|
|
194
|
+
/**
|
|
195
|
+
* The address of the L2 Fee Juice contract.
|
|
196
|
+
* It should be an AztecAddress, but the type is defined in stdlib,
|
|
197
|
+
* which would create a circular import
|
|
198
|
+
* */
|
|
199
|
+
l2FeeJuiceAddress: Fr;
|
|
200
|
+
/** The vk tree root. */
|
|
201
|
+
vkTreeRoot: Fr;
|
|
202
|
+
/** The protocol contract tree root. */
|
|
203
|
+
protocolContractTreeRoot: Fr;
|
|
204
|
+
/** The genesis root of the archive tree. */
|
|
205
|
+
genesisArchiveRoot: Fr;
|
|
206
|
+
/** The hash of the genesis block header. */
|
|
207
|
+
genesisBlockHash: Fr;
|
|
208
|
+
/** The salt for CREATE2 deployment. */
|
|
209
|
+
salt: number | undefined;
|
|
210
|
+
/** The initial validators for the rollup contract. */
|
|
211
|
+
initialValidators?: EthAddress[];
|
|
212
|
+
/** Configuration for the L1 tx utils module. */
|
|
213
|
+
l1TxConfig?: Partial<L1TxUtilsConfig>;
|
|
214
|
+
/** Enable fast mode for deployments (fire and forget transactions) */
|
|
215
|
+
acceleratedTestDeployments?: boolean;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Creates a wallet and a public viem client for interacting with L1.
|
|
220
|
+
* @param rpcUrls - List of RPC URLs to connect to L1.
|
|
221
|
+
* @param mnemonicOrPrivateKeyOrHdAccount - Mnemonic or account for the wallet client.
|
|
222
|
+
* @param chain - Optional chain spec (defaults to local foundry).
|
|
223
|
+
* @param addressIndex - Optional index of the address to use from the mnemonic.
|
|
224
|
+
* @returns - A wallet and a public client.
|
|
225
|
+
*/
|
|
226
|
+
export function createL1Clients(
|
|
227
|
+
rpcUrls: string[],
|
|
228
|
+
mnemonicOrPrivateKeyOrHdAccount: string | `0x${string}` | HDAccount | PrivateKeyAccount,
|
|
229
|
+
chain: Chain = foundry,
|
|
230
|
+
addressIndex?: number,
|
|
231
|
+
): L1Clients {
|
|
232
|
+
const hdAccount =
|
|
233
|
+
typeof mnemonicOrPrivateKeyOrHdAccount === 'string'
|
|
234
|
+
? mnemonicOrPrivateKeyOrHdAccount.startsWith('0x')
|
|
235
|
+
? privateKeyToAccount(mnemonicOrPrivateKeyOrHdAccount as `0x${string}`)
|
|
236
|
+
: mnemonicToAccount(mnemonicOrPrivateKeyOrHdAccount, { addressIndex })
|
|
237
|
+
: mnemonicOrPrivateKeyOrHdAccount;
|
|
238
|
+
|
|
239
|
+
// From what I can see, this is the difference between the HDAccount and the PrivateKeyAccount
|
|
240
|
+
// and we don't need it for anything. This lets us use the same type for both.
|
|
241
|
+
// eslint-disable-next-line camelcase
|
|
242
|
+
hdAccount.experimental_signAuthorization ??= () => {
|
|
243
|
+
throw new Error('experimental_signAuthorization not implemented for HDAccount');
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const walletClient = createWalletClient({
|
|
247
|
+
account: hdAccount,
|
|
248
|
+
chain,
|
|
249
|
+
transport: fallback(rpcUrls.map(url => http(url))),
|
|
250
|
+
}).extend(publicActions);
|
|
251
|
+
const publicClient = createPublicClient({
|
|
252
|
+
chain,
|
|
253
|
+
transport: fallback(rpcUrls.map(url => http(url))),
|
|
254
|
+
pollingInterval: 100,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return { walletClient, publicClient } as L1Clients;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export const deployRollupAndPeriphery = async (
|
|
261
|
+
clients: L1Clients,
|
|
262
|
+
args: DeployL1ContractsArgs,
|
|
263
|
+
registryAddress: EthAddress,
|
|
264
|
+
logger: Logger,
|
|
265
|
+
txUtilsConfig: L1TxUtilsConfig,
|
|
266
|
+
) => {
|
|
267
|
+
const deployer = new L1Deployer(
|
|
268
|
+
clients.walletClient,
|
|
269
|
+
clients.publicClient,
|
|
270
|
+
args.salt,
|
|
271
|
+
args.acceleratedTestDeployments,
|
|
272
|
+
logger,
|
|
273
|
+
txUtilsConfig,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const addresses = await RegistryContract.collectAddresses(clients.publicClient, registryAddress, 'canonical');
|
|
277
|
+
|
|
278
|
+
const rollup = await deployRollup(clients, deployer, args, addresses, logger);
|
|
279
|
+
const payloadAddress = await deployUpgradePayload(deployer, {
|
|
280
|
+
registryAddress: addresses.registryAddress,
|
|
281
|
+
rollupAddress: EthAddress.fromString(rollup.address),
|
|
282
|
+
});
|
|
283
|
+
const slashFactoryAddress = await deploySlashFactory(deployer, rollup.address, logger);
|
|
284
|
+
|
|
285
|
+
await deployer.waitForDeployments();
|
|
286
|
+
|
|
287
|
+
return { rollup, payloadAddress, slashFactoryAddress };
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export const deploySlashFactory = async (deployer: L1Deployer, rollupAddress: Hex, logger: Logger) => {
|
|
291
|
+
const slashFactoryAddress = await deployer.deploy(l1Artifacts.slashFactory, [rollupAddress]);
|
|
292
|
+
logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
|
|
293
|
+
return slashFactoryAddress;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export const deployUpgradePayload = async (
|
|
297
|
+
deployer: L1Deployer,
|
|
298
|
+
addresses: Pick<L1ContractAddresses, 'registryAddress' | 'rollupAddress'>,
|
|
299
|
+
) => {
|
|
300
|
+
const payloadAddress = await deployer.deploy(l1Artifacts.registerNewRollupVersionPayload, [
|
|
301
|
+
addresses.registryAddress.toString(),
|
|
302
|
+
addresses.rollupAddress.toString(),
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
return payloadAddress;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export const deployRollup = async (
|
|
309
|
+
clients: L1Clients,
|
|
310
|
+
deployer: L1Deployer,
|
|
311
|
+
args: DeployL1ContractsArgs,
|
|
312
|
+
addresses: Pick<L1ContractAddresses, 'feeJuicePortalAddress' | 'rewardDistributorAddress' | 'stakingAssetAddress'>,
|
|
313
|
+
logger: Logger,
|
|
314
|
+
): Promise<RollupContract> => {
|
|
315
|
+
const rollupConfigArgs = {
|
|
316
|
+
aztecSlotDuration: args.aztecSlotDuration,
|
|
317
|
+
aztecEpochDuration: args.aztecEpochDuration,
|
|
318
|
+
targetCommitteeSize: args.aztecTargetCommitteeSize,
|
|
319
|
+
aztecProofSubmissionWindow: args.aztecProofSubmissionWindow,
|
|
320
|
+
minimumStake: args.minimumStake,
|
|
321
|
+
slashingQuorum: args.slashingQuorum,
|
|
322
|
+
slashingRoundSize: args.slashingRoundSize,
|
|
323
|
+
};
|
|
324
|
+
const genesisStateArgs = {
|
|
325
|
+
vkTreeRoot: args.vkTreeRoot.toString(),
|
|
326
|
+
protocolContractTreeRoot: args.protocolContractTreeRoot.toString(),
|
|
327
|
+
genesisArchiveRoot: args.genesisArchiveRoot.toString(),
|
|
328
|
+
genesisBlockHash: args.genesisBlockHash.toString(),
|
|
329
|
+
};
|
|
330
|
+
logger.verbose(`Rollup config args`, rollupConfigArgs);
|
|
331
|
+
const rollupArgs = [
|
|
332
|
+
addresses.feeJuicePortalAddress.toString(),
|
|
333
|
+
addresses.rewardDistributorAddress.toString(),
|
|
334
|
+
addresses.stakingAssetAddress.toString(),
|
|
335
|
+
clients.walletClient.account.address.toString(),
|
|
336
|
+
genesisStateArgs,
|
|
337
|
+
rollupConfigArgs,
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
const rollupAddress = await deployer.deploy(l1Artifacts.rollup, rollupArgs);
|
|
341
|
+
logger.verbose(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs);
|
|
342
|
+
|
|
343
|
+
await deployer.waitForDeployments();
|
|
344
|
+
logger.verbose(`All core contracts have been deployed`);
|
|
345
|
+
|
|
346
|
+
const rollup = getContract({
|
|
347
|
+
address: getAddress(rollupAddress.toString()),
|
|
348
|
+
abi: l1Artifacts.rollup.contractAbi,
|
|
349
|
+
client: clients.walletClient,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const txHashes: Hex[] = [];
|
|
353
|
+
|
|
354
|
+
if (args.initialValidators && args.initialValidators.length > 0) {
|
|
355
|
+
// Check if some of the initial validators are already registered, so we support idempotent deployments
|
|
356
|
+
let newValidatorsAddresses = args.initialValidators.map(v => v.toString());
|
|
357
|
+
if (!args.acceleratedTestDeployments) {
|
|
358
|
+
const validatorsInfo = await Promise.all(
|
|
359
|
+
args.initialValidators.map(async address => ({
|
|
360
|
+
address,
|
|
361
|
+
...(await rollup.read.getInfo([address.toString()])),
|
|
362
|
+
})),
|
|
363
|
+
);
|
|
364
|
+
const existingValidators = validatorsInfo.filter(v => v.status !== 0);
|
|
365
|
+
if (existingValidators.length > 0) {
|
|
366
|
+
logger.warn(
|
|
367
|
+
`Validators ${existingValidators
|
|
368
|
+
.map(v => v.address)
|
|
369
|
+
.join(', ')} already exist. Skipping from initialization.`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
newValidatorsAddresses = validatorsInfo.filter(v => v.status === 0).map(v => v.address.toString());
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (newValidatorsAddresses.length > 0) {
|
|
377
|
+
// Mint tokens, approve them, use cheat code to initialise validator set without setting up the epoch.
|
|
378
|
+
const stakeNeeded = args.minimumStake * BigInt(newValidatorsAddresses.length);
|
|
379
|
+
await Promise.all(
|
|
380
|
+
[
|
|
381
|
+
await deployer.sendTransaction({
|
|
382
|
+
to: addresses.stakingAssetAddress.toString(),
|
|
383
|
+
data: encodeFunctionData({
|
|
384
|
+
abi: l1Artifacts.stakingAsset.contractAbi,
|
|
385
|
+
functionName: 'mint',
|
|
386
|
+
args: [clients.walletClient.account.address, stakeNeeded],
|
|
387
|
+
}),
|
|
388
|
+
}),
|
|
389
|
+
await deployer.sendTransaction({
|
|
390
|
+
to: addresses.stakingAssetAddress.toString(),
|
|
391
|
+
data: encodeFunctionData({
|
|
392
|
+
abi: l1Artifacts.stakingAsset.contractAbi,
|
|
393
|
+
functionName: 'approve',
|
|
394
|
+
args: [rollupAddress.toString(), stakeNeeded],
|
|
395
|
+
}),
|
|
396
|
+
}),
|
|
397
|
+
].map(tx => clients.publicClient.waitForTransactionReceipt({ hash: tx.txHash })),
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
const validators = newValidatorsAddresses.map(v => ({
|
|
401
|
+
attester: v,
|
|
402
|
+
proposer: getExpectedAddress(ForwarderAbi, ForwarderBytecode, [v], v).address,
|
|
403
|
+
withdrawer: v,
|
|
404
|
+
amount: args.minimumStake,
|
|
405
|
+
}));
|
|
406
|
+
// const initiateValidatorSetTxHash = await rollup.write.cheat__InitialiseValidatorSet([validators]);
|
|
407
|
+
const initiateValidatorSetTxHash = await deployer.walletClient.writeContract({
|
|
408
|
+
address: rollupAddress.toString(),
|
|
409
|
+
abi: l1Artifacts.rollup.contractAbi,
|
|
410
|
+
functionName: 'cheat__InitialiseValidatorSet',
|
|
411
|
+
args: [validators],
|
|
412
|
+
});
|
|
413
|
+
txHashes.push(initiateValidatorSetTxHash);
|
|
414
|
+
logger.info(`Initialized validator set`, {
|
|
415
|
+
validators,
|
|
416
|
+
txHash: initiateValidatorSetTxHash,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
await Promise.all(txHashes.map(txHash => clients.publicClient.waitForTransactionReceipt({ hash: txHash })));
|
|
422
|
+
|
|
423
|
+
return new RollupContract(clients.publicClient, rollupAddress);
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Deploys the aztec L1 contracts; Rollup & (optionally) Decoder Helper.
|
|
428
|
+
* @param rpcUrls - List of URLs of the ETH RPC to use for deployment.
|
|
429
|
+
* @param account - Private Key or HD Account that will deploy the contracts.
|
|
430
|
+
* @param chain - The chain instance to deploy to.
|
|
431
|
+
* @param logger - A logger object.
|
|
432
|
+
* @param args - Arguments for initialization of L1 contracts
|
|
433
|
+
* @returns A list of ETH addresses of the deployed contracts.
|
|
434
|
+
*/
|
|
435
|
+
export const deployL1Contracts = async (
|
|
436
|
+
rpcUrls: string[],
|
|
437
|
+
account: HDAccount | PrivateKeyAccount,
|
|
438
|
+
chain: Chain,
|
|
439
|
+
logger: Logger,
|
|
440
|
+
args: DeployL1ContractsArgs,
|
|
441
|
+
txUtilsConfig: L1TxUtilsConfig = defaultL1TxUtilsConfig,
|
|
442
|
+
): Promise<DeployL1ContractsReturnType> => {
|
|
443
|
+
const clients = createL1Clients(rpcUrls, account, chain);
|
|
444
|
+
const { walletClient, publicClient } = clients;
|
|
445
|
+
|
|
446
|
+
// We are assuming that you are running this on a local anvil node which have 1s block times
|
|
447
|
+
// To align better with actual deployment, we update the block interval to 12s
|
|
448
|
+
|
|
449
|
+
const rpcCall = async (method: string, params: any[]) => {
|
|
450
|
+
logger.info(`Calling ${method} with params: ${JSON.stringify(params)}`);
|
|
451
|
+
return (await publicClient.transport.request({
|
|
452
|
+
method,
|
|
453
|
+
params,
|
|
454
|
+
})) as any;
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
if (isAnvilTestChain(chain.id)) {
|
|
458
|
+
try {
|
|
459
|
+
await rpcCall('anvil_setBlockTimestampInterval', [args.ethereumSlotDuration]);
|
|
460
|
+
logger.warn(`Set block interval to ${args.ethereumSlotDuration}`);
|
|
461
|
+
} catch (e) {
|
|
462
|
+
logger.error(`Error setting block interval: ${e}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
logger.verbose(`Deploying contracts from ${account.address.toString()}`);
|
|
467
|
+
|
|
468
|
+
// Governance stuff
|
|
469
|
+
const deployer = new L1Deployer(
|
|
470
|
+
walletClient,
|
|
471
|
+
publicClient,
|
|
472
|
+
args.salt,
|
|
473
|
+
args.acceleratedTestDeployments,
|
|
474
|
+
logger,
|
|
475
|
+
txUtilsConfig,
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
const registryAddress = await deployer.deploy(l1Artifacts.registry, [account.address.toString()]);
|
|
479
|
+
logger.verbose(`Deployed Registry at ${registryAddress}`);
|
|
480
|
+
|
|
481
|
+
const feeAssetAddress = await deployer.deploy(l1Artifacts.feeAsset, ['FeeJuice', 'FEE', account.address.toString()]);
|
|
482
|
+
logger.verbose(`Deployed Fee Juice at ${feeAssetAddress}`);
|
|
483
|
+
|
|
484
|
+
const stakingAssetAddress = await deployer.deploy(l1Artifacts.stakingAsset, [
|
|
485
|
+
'Staking',
|
|
486
|
+
'STK',
|
|
487
|
+
account.address.toString(),
|
|
488
|
+
]);
|
|
489
|
+
logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
|
|
490
|
+
|
|
491
|
+
const governanceProposerAddress = await deployer.deploy(l1Artifacts.governanceProposer, [
|
|
492
|
+
registryAddress.toString(),
|
|
493
|
+
args.governanceProposerQuorum,
|
|
494
|
+
args.governanceProposerRoundSize,
|
|
495
|
+
]);
|
|
496
|
+
logger.verbose(`Deployed GovernanceProposer at ${governanceProposerAddress}`);
|
|
497
|
+
|
|
498
|
+
// @note @LHerskind the assets are expected to be the same at some point, but for better
|
|
499
|
+
// configurability they are different for now.
|
|
500
|
+
const governanceAddress = await deployer.deploy(l1Artifacts.governance, [
|
|
501
|
+
feeAssetAddress.toString(),
|
|
502
|
+
governanceProposerAddress.toString(),
|
|
503
|
+
]);
|
|
504
|
+
logger.verbose(`Deployed Governance at ${governanceAddress}`);
|
|
505
|
+
|
|
506
|
+
const coinIssuerAddress = await deployer.deploy(l1Artifacts.coinIssuer, [
|
|
507
|
+
feeAssetAddress.toString(),
|
|
508
|
+
1n * 10n ** 18n, // @todo #8084
|
|
509
|
+
governanceAddress.toString(),
|
|
510
|
+
]);
|
|
511
|
+
logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
|
|
512
|
+
|
|
513
|
+
const rewardDistributorAddress = await deployer.deploy(l1Artifacts.rewardDistributor, [
|
|
514
|
+
feeAssetAddress.toString(),
|
|
515
|
+
registryAddress.toString(),
|
|
516
|
+
governanceAddress.toString(),
|
|
517
|
+
]);
|
|
518
|
+
logger.verbose(`Deployed RewardDistributor at ${rewardDistributorAddress}`);
|
|
519
|
+
|
|
520
|
+
const feeJuicePortalAddress = await deployer.deploy(l1Artifacts.feeJuicePortal, [
|
|
521
|
+
registryAddress.toString(),
|
|
522
|
+
feeAssetAddress.toString(),
|
|
523
|
+
args.l2FeeJuiceAddress.toString(),
|
|
524
|
+
]);
|
|
525
|
+
logger.verbose(`Deployed Fee Juice Portal at ${feeJuicePortalAddress}`);
|
|
526
|
+
|
|
527
|
+
logger.verbose(`Waiting for governance contracts to be deployed`);
|
|
528
|
+
await deployer.waitForDeployments();
|
|
529
|
+
logger.verbose(`All governance contracts deployed`);
|
|
530
|
+
|
|
531
|
+
const feeJuicePortal = getContract({
|
|
532
|
+
address: feeJuicePortalAddress.toString(),
|
|
533
|
+
abi: l1Artifacts.feeJuicePortal.contractAbi,
|
|
534
|
+
client: walletClient,
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const feeAsset = getContract({
|
|
538
|
+
address: feeAssetAddress.toString(),
|
|
539
|
+
abi: l1Artifacts.feeAsset.contractAbi,
|
|
540
|
+
client: walletClient,
|
|
541
|
+
});
|
|
542
|
+
// Transaction hashes to await
|
|
543
|
+
const txHashes: Hex[] = [];
|
|
544
|
+
|
|
545
|
+
if (args.acceleratedTestDeployments || !(await feeAsset.read.freeForAll())) {
|
|
546
|
+
const { txHash } = await deployer.sendTransaction({
|
|
547
|
+
to: feeAssetAddress.toString(),
|
|
548
|
+
data: encodeFunctionData({
|
|
549
|
+
abi: l1Artifacts.feeAsset.contractAbi,
|
|
550
|
+
functionName: 'setFreeForAll',
|
|
551
|
+
args: [true],
|
|
552
|
+
}),
|
|
553
|
+
});
|
|
554
|
+
logger.verbose(`Fee asset set to free for all in ${txHash}`);
|
|
555
|
+
txHashes.push(txHash);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (args.acceleratedTestDeployments || (await feeAsset.read.owner()) !== getAddress(coinIssuerAddress.toString())) {
|
|
559
|
+
const { txHash } = await deployer.sendTransaction({
|
|
560
|
+
to: feeAssetAddress.toString(),
|
|
561
|
+
data: encodeFunctionData({
|
|
562
|
+
abi: l1Artifacts.feeAsset.contractAbi,
|
|
563
|
+
functionName: 'transferOwnership',
|
|
564
|
+
args: [coinIssuerAddress.toString()],
|
|
565
|
+
}),
|
|
566
|
+
});
|
|
567
|
+
logger.verbose(`Fee asset transferred ownership to coin issuer in ${txHash}`);
|
|
568
|
+
txHashes.push(txHash);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// @note This value MUST match what is in `constants.nr`. It is currently specified here instead of just importing
|
|
572
|
+
// because there is circular dependency hell. This is a temporary solution. #3342
|
|
573
|
+
// @todo #8084
|
|
574
|
+
// fund the portal contract with Fee Juice
|
|
575
|
+
const FEE_JUICE_INITIAL_MINT = 200000000000000000000000n;
|
|
576
|
+
|
|
577
|
+
// In fast mode, use the L1TxUtils to send transactions with nonce management
|
|
578
|
+
const { txHash: mintTxHash } = await deployer.sendTransaction({
|
|
579
|
+
to: feeAssetAddress.toString(),
|
|
580
|
+
data: encodeFunctionData({
|
|
581
|
+
abi: l1Artifacts.feeAsset.contractAbi,
|
|
582
|
+
functionName: 'mint',
|
|
583
|
+
args: [feeJuicePortalAddress.toString(), FEE_JUICE_INITIAL_MINT],
|
|
584
|
+
}),
|
|
585
|
+
});
|
|
586
|
+
logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash} (accelerated test deployments)`);
|
|
587
|
+
txHashes.push(mintTxHash);
|
|
588
|
+
|
|
589
|
+
// @note This is used to ensure we fully wait for the transaction when running against a real chain
|
|
590
|
+
// otherwise we execute subsequent transactions too soon
|
|
591
|
+
if (!args.acceleratedTestDeployments) {
|
|
592
|
+
await publicClient.waitForTransactionReceipt({ hash: mintTxHash });
|
|
593
|
+
logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash}`);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Check if portal needs initialization
|
|
597
|
+
let needsInitialization = args.acceleratedTestDeployments;
|
|
598
|
+
if (!args.acceleratedTestDeployments) {
|
|
599
|
+
// Only check if not in fast mode and not already known to need initialization
|
|
600
|
+
needsInitialization = !(await feeJuicePortal.read.initialized());
|
|
601
|
+
}
|
|
602
|
+
if (needsInitialization) {
|
|
603
|
+
const { txHash: initPortalTxHash } = await deployer.sendTransaction({
|
|
604
|
+
to: feeJuicePortalAddress.toString(),
|
|
605
|
+
data: encodeFunctionData({
|
|
606
|
+
abi: l1Artifacts.feeJuicePortal.contractAbi,
|
|
607
|
+
functionName: 'initialize',
|
|
608
|
+
args: [],
|
|
609
|
+
}),
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
txHashes.push(initPortalTxHash);
|
|
613
|
+
logger.verbose(`Fee juice portal initializing in tx ${initPortalTxHash}`);
|
|
614
|
+
} else {
|
|
615
|
+
logger.verbose(`Fee juice portal is already initialized`);
|
|
616
|
+
}
|
|
617
|
+
logger.verbose(
|
|
618
|
+
`Initialized Fee Juice Portal at ${feeJuicePortalAddress} to bridge between L1 ${feeAssetAddress} to L2 ${args.l2FeeJuiceAddress}`,
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
const rollup = await deployRollup(
|
|
622
|
+
{
|
|
623
|
+
walletClient,
|
|
624
|
+
publicClient,
|
|
625
|
+
},
|
|
626
|
+
deployer,
|
|
627
|
+
args,
|
|
628
|
+
{ feeJuicePortalAddress, rewardDistributorAddress, stakingAssetAddress },
|
|
629
|
+
logger,
|
|
630
|
+
);
|
|
631
|
+
const slashFactoryAddress = await deploySlashFactory(deployer, rollup.address, logger);
|
|
632
|
+
|
|
633
|
+
logger.verbose('Waiting for rollup and slash factory to be deployed');
|
|
634
|
+
await deployer.waitForDeployments();
|
|
635
|
+
logger.verbose(`Rollup and slash factory deployed`);
|
|
636
|
+
|
|
637
|
+
// We need to call a function on the registry to set the various contract addresses.
|
|
638
|
+
const registryContract = getContract({
|
|
639
|
+
address: getAddress(registryAddress.toString()),
|
|
640
|
+
abi: l1Artifacts.registry.contractAbi,
|
|
641
|
+
client: walletClient,
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
if (
|
|
645
|
+
args.acceleratedTestDeployments ||
|
|
646
|
+
!(await registryContract.read.isRollupRegistered([getAddress(rollup.address.toString())]))
|
|
647
|
+
) {
|
|
648
|
+
const { txHash: upgradeTxHash } = await deployer.sendTransaction({
|
|
649
|
+
to: registryAddress.toString(),
|
|
650
|
+
data: encodeFunctionData({
|
|
651
|
+
abi: l1Artifacts.registry.contractAbi,
|
|
652
|
+
functionName: 'upgrade',
|
|
653
|
+
args: [getAddress(rollup.address.toString())],
|
|
654
|
+
}),
|
|
655
|
+
});
|
|
656
|
+
logger.verbose(
|
|
657
|
+
`Upgrading registry contract at ${registryAddress} to rollup ${rollup.address} in tx ${upgradeTxHash}`,
|
|
658
|
+
);
|
|
659
|
+
txHashes.push(upgradeTxHash);
|
|
660
|
+
} else {
|
|
661
|
+
logger.verbose(`Registry ${registryAddress} has already registered rollup ${rollup.address}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// If the owner is not the Governance contract, transfer ownership to the Governance contract
|
|
665
|
+
if (
|
|
666
|
+
args.acceleratedTestDeployments ||
|
|
667
|
+
(await registryContract.read.owner()) !== getAddress(governanceAddress.toString())
|
|
668
|
+
) {
|
|
669
|
+
// TODO(md): add send transaction to the deployer such that we do not need to manage tx hashes here
|
|
670
|
+
const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
|
|
671
|
+
to: registryAddress.toString(),
|
|
672
|
+
data: encodeFunctionData({
|
|
673
|
+
abi: l1Artifacts.registry.contractAbi,
|
|
674
|
+
functionName: 'transferOwnership',
|
|
675
|
+
args: [getAddress(governanceAddress.toString())],
|
|
676
|
+
}),
|
|
677
|
+
});
|
|
678
|
+
logger.verbose(
|
|
679
|
+
`Transferring the ownership of the registry contract at ${registryAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
|
|
680
|
+
);
|
|
681
|
+
txHashes.push(transferOwnershipTxHash);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Wait for all actions to be mined
|
|
685
|
+
await Promise.all(txHashes.map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })));
|
|
686
|
+
logger.verbose(`All transactions for L1 deployment have been mined`);
|
|
687
|
+
const l1Contracts = await RegistryContract.collectAddresses(publicClient, registryAddress, 'canonical');
|
|
688
|
+
|
|
689
|
+
logger.info(`Aztec L1 contracts initialized`, l1Contracts);
|
|
690
|
+
|
|
691
|
+
if (isAnvilTestChain(chain.id)) {
|
|
692
|
+
// @note We make a time jump PAST the very first slot to not have to deal with the edge case of the first slot.
|
|
693
|
+
// The edge case being that the genesis block is already occupying slot 0, so we cannot have another block.
|
|
694
|
+
try {
|
|
695
|
+
// Need to get the time
|
|
696
|
+
const currentSlot = await rollup.getSlotNumber();
|
|
697
|
+
|
|
698
|
+
if (BigInt(currentSlot) === 0n) {
|
|
699
|
+
const ts = Number(await rollup.getTimestampForSlot(1n));
|
|
700
|
+
await rpcCall('evm_setNextBlockTimestamp', [ts]);
|
|
701
|
+
await rpcCall('hardhat_mine', [1]);
|
|
702
|
+
const currentSlot = await rollup.getSlotNumber();
|
|
703
|
+
|
|
704
|
+
if (BigInt(currentSlot) !== 1n) {
|
|
705
|
+
throw new Error(`Error jumping time: current slot is ${currentSlot}`);
|
|
706
|
+
}
|
|
707
|
+
logger.info(`Jumped to slot 1`);
|
|
708
|
+
}
|
|
709
|
+
} catch (e) {
|
|
710
|
+
throw new Error(`Error jumping time: ${e}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return {
|
|
715
|
+
walletClient,
|
|
716
|
+
publicClient,
|
|
717
|
+
l1ContractAddresses: {
|
|
718
|
+
...l1Contracts,
|
|
719
|
+
slashFactoryAddress,
|
|
720
|
+
},
|
|
721
|
+
};
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
class L1Deployer {
|
|
725
|
+
private salt: Hex | undefined;
|
|
726
|
+
private txHashes: Hex[] = [];
|
|
727
|
+
public readonly l1TxUtils: L1TxUtils;
|
|
728
|
+
|
|
729
|
+
constructor(
|
|
730
|
+
public readonly walletClient: ViemWalletClient,
|
|
731
|
+
private publicClient: ViemPublicClient,
|
|
732
|
+
maybeSalt: number | undefined,
|
|
733
|
+
private acceleratedTestDeployments: boolean = false,
|
|
734
|
+
private logger: Logger = createLogger('L1Deployer'),
|
|
735
|
+
private txUtilsConfig?: L1TxUtilsConfig,
|
|
736
|
+
) {
|
|
737
|
+
this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), { size: 32 }) : undefined;
|
|
738
|
+
this.l1TxUtils = new L1TxUtils(
|
|
739
|
+
this.publicClient,
|
|
740
|
+
this.walletClient,
|
|
741
|
+
this.logger,
|
|
742
|
+
this.txUtilsConfig,
|
|
743
|
+
this.acceleratedTestDeployments,
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async deploy(params: ContractArtifacts, args: readonly unknown[] = []): Promise<EthAddress> {
|
|
748
|
+
const { txHash, address } = await deployL1Contract(
|
|
749
|
+
this.walletClient,
|
|
750
|
+
this.publicClient,
|
|
751
|
+
params.contractAbi,
|
|
752
|
+
params.contractBytecode,
|
|
753
|
+
args,
|
|
754
|
+
this.salt,
|
|
755
|
+
params.libraries,
|
|
756
|
+
this.logger,
|
|
757
|
+
this.l1TxUtils,
|
|
758
|
+
this.acceleratedTestDeployments,
|
|
759
|
+
);
|
|
760
|
+
if (txHash) {
|
|
761
|
+
this.txHashes.push(txHash);
|
|
762
|
+
}
|
|
763
|
+
return address;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async waitForDeployments(): Promise<void> {
|
|
767
|
+
if (this.acceleratedTestDeployments) {
|
|
768
|
+
this.logger.info('Accelerated test deployments - skipping waiting for deployments');
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (this.txHashes.length === 0) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
this.logger.info(`Waiting for ${this.txHashes.length} transactions to be mined...`);
|
|
776
|
+
await Promise.all(this.txHashes.map(txHash => this.publicClient.waitForTransactionReceipt({ hash: txHash })));
|
|
777
|
+
this.logger.info('All transactions mined successfully');
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
sendTransaction(tx: L1TxRequest): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> {
|
|
781
|
+
return this.l1TxUtils.sendTransaction(tx);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// docs:start:deployL1Contract
|
|
786
|
+
/**
|
|
787
|
+
* Helper function to deploy ETH contracts.
|
|
788
|
+
* @param walletClient - A viem WalletClient.
|
|
789
|
+
* @param publicClient - A viem PublicClient.
|
|
790
|
+
* @param abi - The ETH contract's ABI (as abitype's Abi).
|
|
791
|
+
* @param bytecode - The ETH contract's bytecode.
|
|
792
|
+
* @param args - Constructor arguments for the contract.
|
|
793
|
+
* @param maybeSalt - Optional salt for CREATE2 deployment (does not wait for deployment tx to be mined if set, does not send tx if contract already exists).
|
|
794
|
+
* @returns The ETH address the contract was deployed to.
|
|
795
|
+
*/
|
|
796
|
+
export async function deployL1Contract(
|
|
797
|
+
walletClient: ViemWalletClient,
|
|
798
|
+
publicClient: ViemPublicClient,
|
|
799
|
+
abi: Narrow<Abi | readonly unknown[]>,
|
|
800
|
+
bytecode: Hex,
|
|
801
|
+
args: readonly unknown[] = [],
|
|
802
|
+
maybeSalt?: Hex,
|
|
803
|
+
libraries?: Libraries,
|
|
804
|
+
logger?: Logger,
|
|
805
|
+
l1TxUtils?: L1TxUtils,
|
|
806
|
+
acceleratedTestDeployments: boolean = false,
|
|
807
|
+
): Promise<{ address: EthAddress; txHash: Hex | undefined }> {
|
|
808
|
+
let txHash: Hex | undefined = undefined;
|
|
809
|
+
let resultingAddress: Hex | null | undefined = undefined;
|
|
810
|
+
|
|
811
|
+
if (!l1TxUtils) {
|
|
812
|
+
l1TxUtils = new L1TxUtils(publicClient, walletClient, logger, undefined, acceleratedTestDeployments);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (libraries) {
|
|
816
|
+
// Note that this does NOT work well for linked libraries having linked libraries.
|
|
817
|
+
|
|
818
|
+
// Verify that all link references have corresponding code
|
|
819
|
+
for (const linkRef in libraries.linkReferences) {
|
|
820
|
+
for (const contractName in libraries.linkReferences[linkRef]) {
|
|
821
|
+
if (!libraries.libraryCode[contractName]) {
|
|
822
|
+
throw new Error(`Missing library code for ${contractName}`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const replacements: Record<string, EthAddress> = {};
|
|
828
|
+
const libraryTxs: Hex[] = [];
|
|
829
|
+
for (const libraryName in libraries?.libraryCode) {
|
|
830
|
+
const lib = libraries.libraryCode[libraryName];
|
|
831
|
+
|
|
832
|
+
const { address, txHash } = await deployL1Contract(
|
|
833
|
+
walletClient,
|
|
834
|
+
publicClient,
|
|
835
|
+
lib.contractAbi,
|
|
836
|
+
lib.contractBytecode,
|
|
837
|
+
[],
|
|
838
|
+
maybeSalt,
|
|
839
|
+
undefined,
|
|
840
|
+
logger,
|
|
841
|
+
l1TxUtils,
|
|
842
|
+
acceleratedTestDeployments,
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
if (txHash) {
|
|
846
|
+
libraryTxs.push(txHash);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
for (const linkRef in libraries.linkReferences) {
|
|
850
|
+
for (const contractName in libraries.linkReferences[linkRef]) {
|
|
851
|
+
// If the library name matches the one we just deployed, we replace it.
|
|
852
|
+
if (contractName !== libraryName) {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// We read the first instance to figure out what we are to replace.
|
|
857
|
+
const start = 2 + 2 * libraries.linkReferences[linkRef][contractName][0].start;
|
|
858
|
+
const length = 2 * libraries.linkReferences[linkRef][contractName][0].length;
|
|
859
|
+
|
|
860
|
+
const toReplace = bytecode.slice(start, start + length);
|
|
861
|
+
replacements[toReplace] = address;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const escapeRegExp = (s: string) => {
|
|
867
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escape special characters
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
for (const toReplace in replacements) {
|
|
871
|
+
const replacement = replacements[toReplace].toString().slice(2);
|
|
872
|
+
bytecode = bytecode.replace(new RegExp(escapeRegExp(toReplace), 'g'), replacement) as Hex;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Reth fails gas estimation if the deployed contract attempts to call a library that is not yet deployed,
|
|
876
|
+
// so we wait for all library deployments to be mined before deploying the contract.
|
|
877
|
+
// However, if we are in fast mode or using debugMaxGasLimit, we will skip simulation, so we can skip waiting.
|
|
878
|
+
if (libraryTxs.length > 0 && !acceleratedTestDeployments) {
|
|
879
|
+
logger?.verbose(`Awaiting for linked libraries to be deployed`);
|
|
880
|
+
await Promise.all(libraryTxs.map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })));
|
|
881
|
+
} else {
|
|
882
|
+
logger?.verbose(
|
|
883
|
+
`Skipping waiting for linked libraries to be deployed ${
|
|
884
|
+
acceleratedTestDeployments ? '(accelerated test deployments)' : ''
|
|
885
|
+
}`,
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (maybeSalt) {
|
|
891
|
+
const { address, paddedSalt: salt, calldata } = getExpectedAddress(abi, bytecode, args, maybeSalt);
|
|
892
|
+
resultingAddress = address;
|
|
893
|
+
const existing = await publicClient.getBytecode({ address: resultingAddress });
|
|
894
|
+
if (existing === undefined || existing === '0x') {
|
|
895
|
+
const res = await l1TxUtils.sendTransaction({
|
|
896
|
+
to: DEPLOYER_ADDRESS,
|
|
897
|
+
data: concatHex([salt, calldata]),
|
|
898
|
+
});
|
|
899
|
+
txHash = res.txHash;
|
|
900
|
+
|
|
901
|
+
logger?.verbose(`Deployed contract with salt ${salt} to address ${resultingAddress} in tx ${txHash}.`);
|
|
902
|
+
} else {
|
|
903
|
+
logger?.verbose(`Skipping existing deployment of contract with salt ${salt} to address ${resultingAddress}`);
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
// Regular deployment path
|
|
907
|
+
const deployData = encodeDeployData({ abi, bytecode, args });
|
|
908
|
+
const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
|
|
909
|
+
to: null,
|
|
910
|
+
data: deployData,
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
txHash = receipt.transactionHash;
|
|
914
|
+
resultingAddress = receipt.contractAddress;
|
|
915
|
+
if (!resultingAddress) {
|
|
916
|
+
throw new Error(
|
|
917
|
+
`No contract address found in receipt: ${JSON.stringify(receipt, (_, val) =>
|
|
918
|
+
typeof val === 'bigint' ? String(val) : val,
|
|
919
|
+
)}`,
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return { address: EthAddress.fromString(resultingAddress!), txHash };
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
export function getExpectedAddress(
|
|
928
|
+
abi: Narrow<Abi | readonly unknown[]>,
|
|
929
|
+
bytecode: Hex,
|
|
930
|
+
args: readonly unknown[],
|
|
931
|
+
salt: Hex,
|
|
932
|
+
) {
|
|
933
|
+
const paddedSalt = padHex(salt, { size: 32 });
|
|
934
|
+
const calldata = encodeDeployData({ abi, bytecode, args });
|
|
935
|
+
const address = getContractAddress({
|
|
936
|
+
from: DEPLOYER_ADDRESS,
|
|
937
|
+
salt: paddedSalt,
|
|
938
|
+
bytecode: calldata,
|
|
939
|
+
opcode: 'CREATE2',
|
|
940
|
+
});
|
|
941
|
+
return {
|
|
942
|
+
address,
|
|
943
|
+
paddedSalt,
|
|
944
|
+
calldata,
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// docs:end:deployL1Contract
|