-
Notifications
You must be signed in to change notification settings - Fork 94
Expand file tree
/
Copy pathethcommands.ts
More file actions
830 lines (730 loc) · 35.7 KB
/
ethcommands.ts
File metadata and controls
830 lines (730 loc) · 35.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
import { runStress } from "./stress";
import { BigNumber, ContractFactory, ethers, Wallet } from "ethers";
import * as consts from "./consts";
import { namedAccount, namedAddress } from "./accounts";
import * as L1GatewayRouter from "@arbitrum/token-bridge-contracts/build/contracts/contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol/L1GatewayRouter.json";
import * as L1AtomicTokenBridgeCreator from "@arbitrum/token-bridge-contracts/build/contracts/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol/L1AtomicTokenBridgeCreator.json";
import * as ERC20 from "@openzeppelin/contracts/build/contracts/ERC20.json";
import * as TestWETH9 from "@arbitrum/token-bridge-contracts/build/contracts/contracts/tokenbridge/test/TestWETH9.sol/TestWETH9.json";
import * as fs from "fs";
import { ARB_OWNER } from "./consts";
import * as TransparentUpgradeableProxy from "@openzeppelin/contracts/build/contracts/TransparentUpgradeableProxy.json"
import * as ExpressLaneAuctionContract from "@arbitrum/nitro-contracts/build/contracts/src/express-lane-auction/ExpressLaneAuction.sol/ExpressLaneAuction.json"
import * as StylusDeployerContract from "@arbitrum/nitro-contracts/build/contracts/src/stylus/StylusDeployer.sol/StylusDeployer.json"
const path = require("path");
async function sendTransaction(argv: any, threadId: number) {
const account = namedAccount(argv.from, threadId).connect(argv.provider)
const startNonce = await account.getTransactionCount("pending")
for (let index = 0; index < argv.times; index++) {
const response = await
account.sendTransaction({
to: namedAddress(argv.to, threadId),
value: ethers.utils.parseEther(argv.ethamount),
data: argv.data,
nonce: startNonce + index,
})
console.log(response)
if (argv.wait) {
const receipt = await response.wait()
console.log(receipt)
}
if (argv.delay > 0) {
await new Promise(f => setTimeout(f, argv.delay));
}
}
}
async function bridgeFunds(argv: any, parentChainUrl: string, chainUrl: string, inboxAddr: string) {
argv.provider = new ethers.providers.WebSocketProvider(parentChainUrl);
argv.to = "address_" + inboxAddr;
argv.data =
"0x0f4d14e9000000000000000000000000000000000000000000000000000082f79cd90000";
await runStress(argv, sendTransaction);
argv.provider.destroy();
if (argv.wait) {
const l2provider = new ethers.providers.WebSocketProvider(chainUrl);
const account = namedAccount(argv.from, argv.threadId).connect(l2provider)
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
while (true) {
const balance = await account.getBalance()
if (balance.gte(ethers.utils.parseEther(argv.ethamount))) {
return
}
await sleep(100)
}
}
}
async function bridgeNativeToken(argv: any, parentChainUrl: string, chainUrl: string, inboxAddr: string, token: string) {
argv.provider = new ethers.providers.WebSocketProvider(parentChainUrl);
argv.to = "address_" + inboxAddr;
// snapshot balance before deposit
const childProvider = new ethers.providers.WebSocketProvider(chainUrl);
const bridger = namedAccount(argv.from, argv.threadId).connect(childProvider)
const bridgerBalanceBefore = await bridger.getBalance()
// get token contract
const bridgerParentChain = namedAccount(argv.from, argv.threadId).connect(argv.provider)
const nativeTokenContract = new ethers.Contract(token, ERC20.abi, bridgerParentChain)
// scale deposit amount
const decimals = await nativeTokenContract.decimals()
const depositAmount = BigNumber.from(argv.amount).mul(BigNumber.from('10').pow(decimals))
/// approve inbox to use fee token
await nativeTokenContract.approve(inboxAddr, depositAmount)
/// deposit fee token
const iface = new ethers.utils.Interface(["function depositERC20(uint256 amount)"])
argv.data = iface.encodeFunctionData("depositERC20", [depositAmount]);
await runStress(argv, sendTransaction);
argv.provider.destroy();
if (argv.wait) {
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
// calculate amount being minted on child chain
let expectedMintedAmount = depositAmount
if(decimals < 18) {
// inflate up to 18 decimals
expectedMintedAmount = depositAmount.mul(BigNumber.from('10').pow(18 - decimals))
} else if(decimals > 18) {
// deflate down to 18 decimals, rounding up
const quotient = BigNumber.from('10').pow(decimals - 18)
expectedMintedAmount = depositAmount.div(quotient)
if(expectedMintedAmount.mul(quotient).lt(depositAmount)) {
expectedMintedAmount = expectedMintedAmount.add(1)
}
}
while (true) {
const bridgerBalanceAfter = await bridger.getBalance()
if (bridgerBalanceAfter.sub(bridgerBalanceBefore).eq(expectedMintedAmount)) {
return
}
await sleep(100)
}
}
}
async function deployERC20Contract(deployerWallet: Wallet, decimals: number): Promise<string> {
//// Bytecode below is generated from this simple ERC20 token contract which uses custom number of decimals
// pragma solidity 0.8.16;
//
// import {ERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
//
// contract TestToken is ERC20 {
// uint8 private immutable _decimals;
//
// constructor(uint8 decimals_, address mintTo) ERC20("testnode", "TN") {
// _decimals = decimals_;
// _mint(mintTo, 1_000_000_000 * 10 ** decimals_);
// }
//
// function decimals() public view virtual override returns (uint8) {
// return _decimals;
// }
// }
const erc20TokenBytecode = "0x60a06040523480156200001157600080fd5b5060405162000d4938038062000d49833981016040819052620000349162000195565b60405180604001604052806008815260200167746573746e6f646560c01b815250604051806040016040528060028152602001612a2760f11b815250816003908162000081919062000288565b50600462000090828262000288565b50505060ff8216608052620000c281620000ac84600a62000469565b620000bc90633b9aca0062000481565b620000ca565b5050620004b9565b6001600160a01b038216620001255760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015260640160405180910390fd5b8060026000828254620001399190620004a3565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b505050565b60008060408385031215620001a957600080fd5b825160ff81168114620001bb57600080fd5b60208401519092506001600160a01b0381168114620001d957600080fd5b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806200020f57607f821691505b6020821081036200023057634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200019057600081815260208120601f850160051c810160208610156200025f5750805b601f850160051c820191505b8181101562000280578281556001016200026b565b505050505050565b81516001600160401b03811115620002a457620002a4620001e4565b620002bc81620002b58454620001fa565b8462000236565b602080601f831160018114620002f45760008415620002db5750858301515b600019600386901b1c1916600185901b17855562000280565b600085815260208120601f198616915b82811015620003255788860151825594840194600190910190840162000304565b5085821015620003445787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b600052601160045260246000fd5b600181815b80851115620003ab5781600019048211156200038f576200038f62000354565b808516156200039d57918102915b93841c93908002906200036f565b509250929050565b600082620003c45750600162000463565b81620003d35750600062000463565b8160018114620003ec5760028114620003f75762000417565b600191505062000463565b60ff8411156200040b576200040b62000354565b50506001821b62000463565b5060208310610133831016604e8410600b84101617156200043c575081810a62000463565b6200044883836200036a565b80600019048211156200045f576200045f62000354565b0290505b92915050565b60006200047a60ff841683620003b3565b9392505050565b60008160001904831182151516156200049e576200049e62000354565b500290565b8082018082111562000463576200046362000354565b608051610874620004d5600039600061011b01526108746000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461014557806370a082311461015857806395d89b4114610181578063a457c2d714610189578063a9059cbb1461019c578063dd62ed3e146101af57600080fd5b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100ef57806323b872dd14610101578063313ce56714610114575b600080fd5b6100b66101c2565b6040516100c391906106be565b60405180910390f35b6100df6100da366004610728565b610254565b60405190151581526020016100c3565b6002545b6040519081526020016100c3565b6100df61010f366004610752565b61026e565b60405160ff7f00000000000000000000000000000000000000000000000000000000000000001681526020016100c3565b6100df610153366004610728565b610292565b6100f361016636600461078e565b6001600160a01b031660009081526020819052604090205490565b6100b66102b4565b6100df610197366004610728565b6102c3565b6100df6101aa366004610728565b610343565b6100f36101bd3660046107b0565b610351565b6060600380546101d1906107e3565b80601f01602080910402602001604051908101604052809291908181526020018280546101fd906107e3565b801561024a5780601f1061021f5761010080835404028352916020019161024a565b820191906000526020600020905b81548152906001019060200180831161022d57829003601f168201915b5050505050905090565b60003361026281858561037c565b60019150505b92915050565b60003361027c8582856104a0565b61028785858561051a565b506001949350505050565b6000336102628185856102a58383610351565b6102af919061081d565b61037c565b6060600480546101d1906107e3565b600033816102d18286610351565b9050838110156103365760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b610287828686840361037c565b60003361026281858561051a565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103de5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b606482015260840161032d565b6001600160a01b03821661043f5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b606482015260840161032d565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104ac8484610351565b9050600019811461051457818110156105075760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000604482015260640161032d565b610514848484840361037c565b50505050565b6001600160a01b03831661057e5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b606482015260840161032d565b6001600160a01b0382166105e05760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b606482015260840161032d565b6001600160a01b038316600090815260208190526040902054818110156106585760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b606482015260840161032d565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610514565b600060208083528351808285015260005b818110156106eb578581018301518582016040015282016106cf565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461072357600080fd5b919050565b6000806040838503121561073b57600080fd5b6107448361070c565b946020939093013593505050565b60008060006060848603121561076757600080fd5b6107708461070c565b925061077e6020850161070c565b9150604084013590509250925092565b6000602082840312156107a057600080fd5b6107a98261070c565b9392505050565b600080604083850312156107c357600080fd5b6107cc8361070c565b91506107da6020840161070c565b90509250929050565b600181811c908216806107f757607f821691505b60208210810361081757634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026857634e487b7160e01b600052601160045260246000fdfea2646970667358221220257f3d763bae7b8c0189ed676531d85a1046e0bea68722f67c2616d46f01c02964736f6c63430008100033";
const abi = ["constructor(uint8 decimals_, address mintTo)"];
const tokenFactory = new ContractFactory(abi, erc20TokenBytecode, deployerWallet);
const token = await tokenFactory.deploy(decimals, deployerWallet.address);
await token.deployTransaction.wait();
return token.address;
}
async function deployFeeTokenPricerContract(deployerWallet: Wallet, exchangeRate: BigNumber): Promise<string> {
//// Bytecode below is generated from this simple FeeTokenPricer contract
// pragma solidity ^0.8.16;
// interface IFeeTokenPricer {
// /**
// * @notice Get the number of child chain's fee tokens per 1 parent chain's native token. Exchange rate must be
// * denominated in 18 decimals.
// * @dev For example, parent chain's native token is ETH, fee token is DAI. If price of 1ETH = 2000DAI, then function should return 2000*1e18.
// * If fee token is USDC instead and price of 1ETH = 2000USDC, function should still return 2000*1e18, no matter that USDC uses 6 decimals.
// */
// function getExchangeRate() external returns (uint256);
// }
// contract ConstantFeeTokenPricer is IFeeTokenPricer {
// uint256 immutable public constExchangeRate;
// constructor(uint256 _constExchangeRate) {
// constExchangeRate = _constExchangeRate;
// }
// function getExchangeRate() external view returns (uint256) {
// return constExchangeRate;
// }
// }
const feeTokenPricerBytecode = "0x60a0604052348015600e575f80fd5b506040516101c63803806101c68339818101604052810190602e9190606d565b8060808181525050506093565b5f80fd5b5f819050919050565b604f81603f565b81146058575f80fd5b50565b5f815190506067816048565b92915050565b5f60208284031215607f57607e603b565b5b5f608a84828501605b565b91505092915050565b6080516101166100b05f395f8181606a0152608f01526101165ff3fe6080604052348015600e575f80fd5b50600436106030575f3560e01c8063b8910a29146034578063e6aa216c14604e575b5f80fd5b603a6068565b6040516045919060c9565b60405180910390f35b6054608c565b604051605f919060c9565b60405180910390f35b7f000000000000000000000000000000000000000000000000000000000000000081565b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b5f819050919050565b60c38160b3565b82525050565b5f60208201905060da5f83018460bc565b9291505056fea2646970667358221220ee17f22614d853ccf8b3f854137f68f06ff92f9f71ba8b811d78b1313eead0c564736f6c634300081a0033";
const abi = ["constructor(uint256 exchangeRate)"];
const feeTokenPricerFactory = new ContractFactory(abi, feeTokenPricerBytecode, deployerWallet);
const feeTokenPricer = await feeTokenPricerFactory.deploy(exchangeRate);
await feeTokenPricer.deployTransaction.wait();
return feeTokenPricer.address;
}
async function deployWETHContract(deployerWallet: Wallet): Promise<string> {
const wethFactory = new ContractFactory(TestWETH9.abi, TestWETH9.bytecode, deployerWallet);
const weth = await wethFactory.deploy("Wrapped Ether", "WETH");
await weth.deployTransaction.wait();
return weth.address;
}
async function createStylusDeployer(deployerWallet: Wallet): Promise<string> {
// this factory should be deployed by the rollupcreator when deploy helper is used
const create2factory = '0x4e59b44847b379578588920ca78fbf26c0b4956c'
if (await deployerWallet.provider.getCode(create2factory) === '0x') {
// wait for 30 seconds, check again before throwing an error
await new Promise(resolve => setTimeout(resolve, 30000));
if (await deployerWallet.provider.getCode(create2factory) === '0x') {
throw new Error('Create2 factory not yet deployed')
}
}
const salt = ethers.constants.HashZero
const stylusDeployerBytecode = StylusDeployerContract.bytecode
const stylusDeployerAddress = ethers.utils.getCreate2Address(create2factory, salt, ethers.utils.keccak256(stylusDeployerBytecode))
// check if the address is already deployed
const code = await deployerWallet.provider.getCode(stylusDeployerAddress)
if (code !== '0x') {
console.log("Stylus deployer already deployed")
} else {
const stylusDeployerTx = await deployerWallet.sendTransaction({
to: create2factory,
data: ethers.utils.concat([salt, stylusDeployerBytecode])
})
await stylusDeployerTx.wait()
}
return stylusDeployerAddress
}
export const bridgeFundsCommand = {
command: "bridge-funds",
describe: "sends funds from l1 to l2",
builder: {
ethamount: {
string: true,
describe: "amount to transfer (in eth)",
default: "10",
},
from: {
string: true,
describe: "account (see general help)",
default: "funnel",
},
wait: {
boolean: true,
describe: "wait till l2 has balance of ethamount",
default: false,
},
},
handler: async (argv: any) => {
const deploydata = JSON.parse(
fs
.readFileSync(path.join(consts.configpath, "deployment.json"))
.toString()
);
const inboxAddr = ethers.utils.hexlify(deploydata.inbox);
await bridgeFunds(argv, argv.l1url, argv.l2url, inboxAddr)
},
};
export const bridgeToL3Command = {
command: "bridge-to-l3",
describe: "sends funds from l2 to l3",
builder: {
ethamount: {
string: true,
describe: "amount to transfer (in eth)",
default: "10",
},
from: {
string: true,
describe: "account (see general help)",
default: "funnel",
},
wait: {
boolean: true,
describe: "wait till l3 has balance of ethamount",
default: false,
},
},
handler: async (argv: any) => {
const deploydata = JSON.parse(
fs
.readFileSync(path.join(consts.configpath, "l3deployment.json"))
.toString()
);
const inboxAddr = ethers.utils.hexlify(deploydata.inbox);
await bridgeFunds(argv, argv.l2url, argv.l3url, inboxAddr)
},
};
export const bridgeNativeTokenToL3Command = {
command: "bridge-native-token-to-l3",
describe: "bridge native token from l2 to l3",
builder: {
amount: {
string: true,
describe: "amount to transfer",
default: "10",
},
from: {
string: true,
describe: "account (see general help)",
default: "funnel",
},
wait: {
boolean: true,
describe: "wait till l3 has balance of amount",
default: false,
},
},
handler: async (argv: any) => {
const deploydata = JSON.parse(
fs
.readFileSync(path.join(consts.configpath, "l3deployment.json"))
.toString()
);
const inboxAddr = ethers.utils.hexlify(deploydata.inbox);
const nativeTokenAddr = ethers.utils.hexlify(deploydata["native-token"]);
argv.ethamount = "0"
await bridgeNativeToken(argv, argv.l2url, argv.l3url, inboxAddr, nativeTokenAddr)
},
};
export const transferL3ChainOwnershipCommand = {
command: "transfer-l3-chain-ownership",
describe: "transfer L3 chain ownership to upgrade executor",
builder: {
creator: {
string: true,
describe: "address of the token bridge creator",
},
wait: {
boolean: true,
describe: "wait till ownership is transferred",
default: false,
},
},
handler: async (argv: any) => {
// get inbox address from config file
const deploydata = JSON.parse(
fs
.readFileSync(path.join(consts.configpath, "l3deployment.json"))
.toString()
);
const inboxAddr = ethers.utils.hexlify(deploydata.inbox);
// get L3 upgrade executor address from token bridge creator
const l2provider = new ethers.providers.WebSocketProvider(argv.l2url);
const tokenBridgeCreator = new ethers.Contract(argv.creator, L1AtomicTokenBridgeCreator.abi, l2provider);
const [,,,,,,,l3UpgradeExecutorAddress,] = await tokenBridgeCreator.inboxToL2Deployment(inboxAddr);
// set TX params
argv.provider = new ethers.providers.WebSocketProvider(argv.l3url);
argv.to = "address_" + ARB_OWNER;
argv.from = "l3owner";
argv.ethamount = "0";
// add L3 UpgradeExecutor to chain owners
const arbOwnerIface = new ethers.utils.Interface([
"function addChainOwner(address newOwner) external",
"function removeChainOwner(address ownerToRemove) external"
])
argv.data = arbOwnerIface.encodeFunctionData("addChainOwner", [l3UpgradeExecutorAddress]);
await runStress(argv, sendTransaction);
// remove L3 owner from chain owners
argv.data = arbOwnerIface.encodeFunctionData("removeChainOwner", [namedAccount("l3owner").address]);
await runStress(argv, sendTransaction);
argv.provider.destroy();
}
};
export const createERC20Command = {
command: "create-erc20",
describe: "creates simple ERC20 on L2",
builder: {
deployer: {
string: true,
describe: "account (see general help)",
demandOption: true
},
bridgeable: {
boolean: true,
describe: "if true, deploy on L1 and bridge to L2",
},
l1: {
boolean: true,
describe: "if true, deploy on L1 only",
},
decimals: {
string: true,
describe: "number of decimals for token",
default: "18",
},
},
handler: async (argv: any) => {
console.log("create-erc20");
if (argv.bridgeable || argv.l1) {
// deploy token on l1
const l1provider = new ethers.providers.WebSocketProvider(argv.l1url);
const deployerWallet = namedAccount(argv.deployer).connect(l1provider);
const tokenAddress = await deployERC20Contract(deployerWallet, argv.decimals);
const token = new ethers.Contract(tokenAddress, ERC20.abi, deployerWallet);
console.log("Contract deployed at L1 address:", token.address);
if (!argv.bridgeable) return;
// bridge to l2
const l2provider = new ethers.providers.WebSocketProvider(argv.l2url);
const l1l2tokenbridge = JSON.parse(
fs
.readFileSync(path.join(consts.tokenbridgedatapath, "l1l2_network.json"))
.toString()
);
const l1GatewayRouter = new ethers.Contract(l1l2tokenbridge.l2Network.tokenBridge.parentGatewayRouter, L1GatewayRouter.abi, deployerWallet);
await (await token.functions.approve(l1l2tokenbridge.l2Network.tokenBridge.parentErc20Gateway, ethers.constants.MaxUint256)).wait();
const supply = await token.totalSupply();
// transfer 90% of supply to l2
const transferAmount = supply.mul(9).div(10);
await (await l1GatewayRouter.functions.outboundTransfer(
token.address, deployerWallet.address, transferAmount, 100000000, 1000000000, "0x000000000000000000000000000000000000000000000000000fffffffffff0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", {
value: ethers.utils.parseEther("1"),
}
)).wait();
const tokenL2Addr = (await l1GatewayRouter.functions.calculateL2TokenAddress(token.address))[0];
// wait for l2 token to be deployed
for (let i = 0; i < 60; i++) {
if (await l2provider.getCode(tokenL2Addr) === "0x") {
await new Promise(f => setTimeout(f, 1000));
} else {
break;
}
}
if (await l2provider.getCode(tokenL2Addr) === "0x") {
throw new Error("Failed to bridge token to L2");
}
console.log("Contract deployed at L2 address:", tokenL2Addr);
l1provider.destroy();
l2provider.destroy();
return;
}
// no l1-l2 token bridge, deploy token on l2 directly
argv.provider = new ethers.providers.WebSocketProvider(argv.l2url);
const deployerWallet = namedAccount(argv.deployer).connect(argv.provider);
const tokenAddress = await deployERC20Contract(deployerWallet, argv.decimals);
console.log("Contract deployed at address:", tokenAddress);
argv.provider.destroy();
},
};
export const createFeeTokenPricerCommand = {
command: "create-fee-token-pricer",
describe: "creates Constant Fee Token Pricer on L2",
builder: {
deployer: {
string: true,
describe: "account (see general help)"
},
},
handler: async (argv: any) => {
console.log("create-fee-token-pricer");
argv.provider = new ethers.providers.WebSocketProvider(argv.l2url);
const deployerWallet = new Wallet(
ethers.utils.sha256(ethers.utils.toUtf8Bytes(argv.deployer)),
argv.provider
);
const feeTokenPricerAddress = await deployFeeTokenPricerContract(deployerWallet, BigNumber.from("15000000000000000000"));
console.log("Contract deployed at address:", feeTokenPricerAddress);
argv.provider.destroy();
},
};
export const deployExpressLaneAuctionContractCommand = {
command: "deploy-express-lane-auction",
describe: "Deploy the ExpressLaneAuction contract",
builder: {
"bidding-token": {
string: true,
describe: "bidding token address",
demandOption: true
},
"auctioneer": {
string: true,
describe: "account name to set as auctioneer and admin on contract (default auctioneer)",
default: "auctioneer"
}
},
handler: async (argv: any) => {
console.log("deploy ExpressLaneAuction contract");
argv.provider = new ethers.providers.WebSocketProvider(argv.l2url);
const l2OwnerWallet = namedAccount("l2owner").connect(argv.provider)
const contractFactory = new ContractFactory(ExpressLaneAuctionContract.abi, ExpressLaneAuctionContract.bytecode, l2OwnerWallet)
const contract = await contractFactory.deploy();
await contract.deployTransaction.wait();
console.log("ExpressLaneAuction contract deployed at address:", contract.address);
const auctioneerAddr = namedAddress(argv.auctioneer)
const initIface = new ethers.utils.Interface(["function initialize((address,address,address,(int64,uint64,uint64,uint64),uint256,address,address,address,address,address,address,address))"])
const initData = initIface.encodeFunctionData("initialize", [[
auctioneerAddr, //_auctioneer
argv.biddingToken, //_biddingToken
auctioneerAddr, //_beneficiary
[
Math.round(Date.now() / 60000) * 60, // offsetTimestamp - most recent minute
60, // roundDurationSeconds
15, // auctionClosingSeconds
15 // reserveSubmissionSeconds
],// RoundTiminginfo
1, // _minReservePrice
auctioneerAddr, //_auctioneerAdmin
auctioneerAddr, //_minReservePriceSetter,
auctioneerAddr, //_reservePriceSetter,
auctioneerAddr, //_reservePriceSetterAdmin,
auctioneerAddr, //_beneficiarySetter,
auctioneerAddr, //_roundTimingSetter,
auctioneerAddr //_masterAdmin
]]);
const proxyFactory = new ethers.ContractFactory(TransparentUpgradeableProxy.abi, TransparentUpgradeableProxy.bytecode, l2OwnerWallet)
const proxy = await proxyFactory.deploy(contract.address, namedAddress("l2owner"), initData)
await proxy.deployed()
console.log("Proxy(ExpressLaneAuction) contract deployed at address:", proxy.address);
argv.provider.destroy();
}
};
// Will revert if the keyset is already valid.
async function setValidKeyset(argv: any, upgradeExecutorAddr: string, sequencerInboxAddr: string, keyset: string){
const innerIface = new ethers.utils.Interface(["function setValidKeyset(bytes)"])
const innerData = innerIface.encodeFunctionData("setValidKeyset", [keyset]);
// The Executor contract is the owner of the SequencerInbox so calls must be made
// through it.
const outerIface = new ethers.utils.Interface(["function executeCall(address,bytes)"])
argv.data = outerIface.encodeFunctionData("executeCall", [sequencerInboxAddr, innerData]);
argv.from = "l2owner";
argv.to = "address_" + upgradeExecutorAddr
argv.ethamount = "0"
await sendTransaction(argv, 0);
argv.provider.destroy();
}
export const transferERC20Command = {
command: "transfer-erc20",
describe: "transfers ERC20 token",
builder: {
token: {
string: true,
describe: "token address",
},
amount: {
string: true,
describe: "amount to transfer",
},
from: {
string: true,
describe: "account (see general help)",
},
to: {
string: true,
describe: "address (see general help)",
},
l1: {
boolean: true,
describe: "if true, transfer on L1",
},
},
handler: async (argv: any) => {
console.log("transfer-erc20");
if (argv.l1) {
argv.provider = new ethers.providers.WebSocketProvider(argv.l1url);
} else {
argv.provider = new ethers.providers.WebSocketProvider(argv.l2url);
}
const account = namedAccount(argv.from).connect(argv.provider);
const tokenContract = new ethers.Contract(argv.token, ERC20.abi, account);
const tokenDecimals = await tokenContract.decimals();
const amountToTransfer = BigNumber.from(argv.amount).mul(BigNumber.from('10').pow(tokenDecimals));
await(await tokenContract.transfer(namedAccount(argv.to).address, amountToTransfer)).wait();
argv.provider.destroy();
},
};
export const createWETHCommand = {
command: "create-weth",
describe: "creates WETH on L1",
builder: {
deployer: {
string: true,
describe: "account (see general help)"
},
deposit: {
number: true,
describe: "amount of weth to deposit",
default: 100,
}
},
handler: async (argv: any) => {
console.log("create-weth");
const l1provider = new ethers.providers.WebSocketProvider(argv.l1url);
const deployerWallet = namedAccount(argv.deployer).connect(l1provider);
const wethAddress = await deployWETHContract(deployerWallet);
const weth = new ethers.Contract(wethAddress, TestWETH9.abi, deployerWallet);
console.log("WETH deployed at L1 address:", weth.address);
if (argv.deposit > 0) {
const amount = ethers.utils.parseEther(argv.deposit.toString());
const depositTx = await deployerWallet.sendTransaction({ to: wethAddress, value: amount, data:"0xd0e30db0" }); // deposit()
await depositTx.wait();
}
},
};
export const createStylusDeployerCommand = {
command: "create-stylus-deployer",
describe: "deploys the stylus deployer contract",
builder: {
deployer: { string: true, describe: "account (see general help)" },
l3: { boolean: false, describe: "deploy on L3, otherwise deploy on L2" },
},
handler: async (argv: any) => {
console.log("create-stylus-deployer");
const provider = new ethers.providers.WebSocketProvider(argv.l3 ? argv.l3url : argv.l2url);
const deployerWallet = namedAccount(argv.deployer).connect(provider);
const stylusDeployerAddress = await createStylusDeployer(deployerWallet);
if (argv.l3) {
console.log("Stylus deployer deployed at L3 address:", stylusDeployerAddress);
} else {
console.log("Stylus deployer deployed at L2 address:", stylusDeployerAddress);
}
provider.destroy();
}
};
export const sendL1Command = {
command: "send-l1",
describe: "sends funds between l1 accounts",
builder: {
ethamount: {
string: true,
describe: "amount to transfer (in eth)",
default: "10",
},
from: {
string: true,
describe: "account (see general help)",
default: "funnel",
},
to: {
string: true,
describe: "address (see general help)",
default: "funnel",
},
wait: {
boolean: true,
describe: "wait for transaction to complete",
default: false,
},
data: { string: true, describe: "data" },
},
handler: async (argv: any) => {
argv.provider = new ethers.providers.WebSocketProvider(argv.l1url);
await runStress(argv, sendTransaction);
argv.provider.destroy();
},
};
export const sendL2Command = {
command: "send-l2",
describe: "sends funds between l2 accounts",
builder: {
ethamount: {
string: true,
describe: "amount to transfer (in eth)",
default: "10",
},
from: {
string: true,
describe: "account (see general help)",
default: "funnel",
},
to: {
string: true,
describe: "address (see general help)",
default: "funnel",
},
wait: {
boolean: true,
describe: "wait for transaction to complete",
default: false,
},
data: { string: true, describe: "data" },
},
handler: async (argv: any) => {
argv.provider = new ethers.providers.WebSocketProvider(argv.l2url);
await runStress(argv, sendTransaction);
argv.provider.destroy();
},
};
export const sendL3Command = {
command: "send-l3",
describe: "sends funds between l3 accounts",
builder: {
ethamount: {
string: true,
describe: "amount to transfer (in eth)",
default: "10",
},
from: {
string: true,
describe: "account (see general help)",
default: "funnel",
},
to: {
string: true,
describe: "address (see general help)",
default: "funnel",
},
wait: {
boolean: true,
describe: "wait for transaction to complete",
default: false,
},
data: { string: true, describe: "data" },
},
handler: async (argv: any) => {
argv.provider = new ethers.providers.WebSocketProvider(argv.l3url);
await runStress(argv, sendTransaction);
argv.provider.destroy();
},
};
export const sendRPCCommand = {
command: "send-rpc",
describe: "sends rpc command",
builder: {
method: { string: true, describe: "rpc method to call", default: "eth_syncing" },
url: { string: true, describe: "url to send rpc call", default: "http://sequencer:8547"},
params: { array : true, describe: "array of parameter name/values" },
},
handler: async (argv: any) => {
const rpcProvider = new ethers.providers.JsonRpcProvider(argv.url)
await rpcProvider.send(argv.method, argv.params)
}
}
export const setValidKeysetCommand = {
command: "set-valid-keyset",
describe: "sets the anytrust keyset",
handler: async (argv: any) => {
argv.provider = new ethers.providers.WebSocketProvider(argv.l1url);
const deploydata = JSON.parse(
fs
.readFileSync(path.join(consts.configpath, "deployment.json"))
.toString()
);
const sequencerInboxAddr = ethers.utils.hexlify(deploydata["sequencer-inbox"]);
const upgradeExecutorAddr = ethers.utils.hexlify(deploydata["upgrade-executor"]);
const keyset = fs
.readFileSync(path.join(consts.configpath, "l2_das_keyset.hex"))
.toString()
await setValidKeyset(argv, upgradeExecutorAddr, sequencerInboxAddr, keyset)
}
};
export const waitForSyncCommand = {
command: "wait-for-sync",
describe: "wait for rpc to sync",
builder: {
url: { string: true, describe: "url to send rpc call", default: "http://sequencer:8547"},
},
handler: async (argv: any) => {
const rpcProvider = new ethers.providers.JsonRpcProvider(argv.url)
let syncStatus;
do {
syncStatus = await rpcProvider.send("eth_syncing", [])
if (syncStatus !== false) {
// Wait for a short interval before checking again
await new Promise(resolve => setTimeout(resolve, 5000))
}
} while (syncStatus !== false)
},
};
export const grantFiltererRoleCommand = {
command: "grant-filterer-role",
describe: "grants TransactionFilterer role to the filterer account",
handler: async (argv: any) => {
argv.provider = new ethers.providers.WebSocketProvider(argv.l2url);
const arbOwnerIface = new ethers.utils.Interface([
"function addTransactionFilterer(address filterer) external"
]);
argv.data = arbOwnerIface.encodeFunctionData("addTransactionFilterer", [namedAddress("filterer")]);
argv.from = "l2owner";
argv.to = "address_" + ARB_OWNER;
argv.ethamount = "0";
await runStress(argv, sendTransaction);
argv.provider.destroy();
}
};