-
Notifications
You must be signed in to change notification settings - Fork 292
/
Copy pathexec.js
187 lines (167 loc) · 7.71 KB
/
exec.js
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
const { ethers } = require('hardhat');
const { BigNumber, providers, Wallet } = require('ethers');
const { getArbitrumNetwork, ParentToChildMessageStatus, Erc20Bridger } = require('@arbitrum/sdk');
const {
arbLog,
requireEnvVariables,
addCustomNetworkFromFile,
} = require('arb-shared-dependencies');
const { expect } = require('chai');
require('dotenv').config();
requireEnvVariables(['PRIVATE_KEY', 'CHAIN_RPC', 'PARENT_CHAIN_RPC']);
/**
* Set up: instantiate wallets connected to providers
*/
const walletPrivateKey = process.env.PRIVATE_KEY;
const parentChainProvider = new providers.JsonRpcProvider(process.env.PARENT_CHAIN_RPC);
const childChainProvider = new providers.JsonRpcProvider(process.env.CHAIN_RPC);
const parentChainWallet = new Wallet(walletPrivateKey, parentChainProvider);
const childChainWallet = new Wallet(walletPrivateKey, childChainProvider);
/**
* Set the amount of token to be transferred to the child chain and then withdrawn
*/
const tokenAmount = BigNumber.from(50);
const tokenAmountToWithdraw = BigNumber.from(20);
const main = async () => {
await arbLog('Withdraw tokens using Arbitrum SDK');
/**
* Add the custom network configuration to the SDK if present
*/
addCustomNetworkFromFile();
/**
* For the purpose of our tests, here we deploy an standard ERC-20 token (DappToken) to the parent chain
* It sends its deployer (us) the initial supply of 1000
*/
console.log('Deploying the test DappToken to the parent chain:');
const DappToken = (await ethers.getContractFactory('DappToken')).connect(parentChainWallet);
const dappToken = await DappToken.deploy(1000);
await dappToken.deployed();
console.log(`DappToken is deployed to the parent chain at ${dappToken.address}`);
const tokenAddress = dappToken.address;
/**
* Use childChainNetwork to create an Arbitrum SDK Erc20Bridger instance
* We'll use Erc20Bridger for its convenience methods around transferring token to the child chain
*/
const childChainNetwork = await getArbitrumNetwork(childChainProvider);
const erc20Bridger = new Erc20Bridger(childChainNetwork);
/**
* Because the token might have decimals, we update the amounts to deposit and withdraw taking into account those decimals
*/
const tokenDecimals = await dappToken.decimals();
const tokenDepositAmount = tokenAmount.mul(BigNumber.from(10).pow(tokenDecimals));
const tokenWithdrawAmount = tokenAmountToWithdraw.mul(BigNumber.from(10).pow(tokenDecimals));
/**
* The StandardGateway contract will ultimately be making the token transfer call; thus, that's the contract we need to approve.
* erc20Bridger.approveToken handles this approval
* Arguments required are:
* (1) parentSigner: address of the account on the parent chain transferring tokens to the child chain
* (2) erc20ParentAddress: address on the parent chain of the ERC-20 token to be depositted to the child chain
*/
console.log('Approving:');
const approveTransaction = await erc20Bridger.approveToken({
parentSigner: parentChainWallet,
erc20ParentAddress: tokenAddress,
});
const approveTransactionReceipt = await approveTransaction.wait();
console.log(
`You successfully allowed the Arbitrum Bridge to spend DappToken ${approveTransactionReceipt.transactionHash}`,
);
/**
* The next function initiates the deposit of DappToken to the child chain using erc20Bridger.
* This will escrow funds in the gateway contract on the parent chain, and send a message to mint tokens on the child chain.
*
* The erc20Bridge.deposit method handles computing the necessary fees for automatic-execution of retryable tickets — maxSubmission cost and (gas price * gas)
* and will automatically forward the fees to the child chain as callvalue.
*
* Also note that since this is the first DappToken deposit onto the child chain, a standard Arb ERC-20 contract will automatically be deployed.
* Arguments required are:
* (1) amount: The amount of tokens to be transferred to the child chain
* (2) erc20ParentAddress: address on the parent chain of the ERC-20 token to be depositted to the child chain
* (3) parentSigner: address of the account on the parent chain transferring tokens to the child chain
* (4) childProvider: A provider for the child chain
*/
console.log('Transferring DappToken to the child chain:');
const depositTransaction = await erc20Bridger.deposit({
amount: tokenDepositAmount,
erc20ParentAddress: tokenAddress,
parentSigner: parentChainWallet,
childProvider: childChainProvider,
});
/**
* Now we wait for both the parent-chain and child-chain sides of transactions to be confirmed
*/
console.log(
`Deposit initiated: waiting for execution of the retryable ticket on the child chain (takes 10-15 minutes; current time: ${new Date().toTimeString()}) `,
);
const depositTransactionReceipt = await depositTransaction.wait();
const childTransactionReceipt = await depositTransactionReceipt.waitForChildTransactionReceipt(
childChainProvider,
);
/**
* The `complete` boolean tells us if the parent-to-child message was successful
*/
if (childTransactionReceipt.complete) {
console.log(
`Message was successfully executed on the child chain: status: ${
ParentToChildMessageStatus[childTransactionReceipt.status]
}`,
);
} else {
throw new Error(
`Message failed to be executed on the child chain: status ${
ParentToChildMessageStatus[childTransactionReceipt.status]
}`,
);
}
console.log(`Setup complete`);
/**
* Now we begin withdrawing DappToken from the child chain.
* To withdraw, we'll use Erc20Bridger helper method withdraw which will call the GatewayRouter of the child chain
* to initiate a withdrawal via the Standard ERC-20 gateway.
* This transaction is constructed and paid for like any other transaction (it just happens to (ultimately) make a call to ArbSys.sendTxToL1)
*
* Arguments required are:
* (1) amount: The amount of tokens to be transferred to the parent chain
* (2) erc20ParentAddress: address on the parent chain of the ERC-20 token to be depositted to the parent chain
* (3) childSigner: address of the account on the child chain transferring tokens to the parent chain
*/
console.log('Withdrawing...');
const withdrawTransaction = await erc20Bridger.withdraw({
amount: tokenWithdrawAmount,
destinationAddress: childChainWallet.address,
erc20ParentAddress: tokenAddress,
childSigner: childChainWallet,
});
const withdrawTransactionReceipt = await withdrawTransaction.wait();
console.log(`Token withdrawal initiated! 🥳 ${withdrawTransactionReceipt.transactionHash}`);
/**
* And with that, our withdrawal is initiated! No additional time-sensitive actions are required.
* Any time after the transaction's assertion is confirmed (around 7 days by default),
* funds can be transferred out of the bridge via the outbox contract
* We'll check our wallet's DappToken balance here:
*/
const childChainTokenAddress = await erc20Bridger.getChildErc20Address(
tokenAddress,
parentChainProvider,
);
const childChainToken = erc20Bridger.getChildTokenContract(
childChainProvider,
childChainTokenAddress,
);
const testWalletBalanceOnChildChain = (
await childChainToken.functions.balanceOf(childChainWallet.address)
)[0];
expect(
testWalletBalanceOnChildChain.add(tokenWithdrawAmount).eq(tokenDepositAmount),
'token withdraw balance not deducted',
).to.be.true;
console.log(
`To to claim funds (after dispute period), see outbox-execute repo and use this transaction hash: ${withdrawTransactionReceipt.transactionHash}`,
);
};
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});