Skip to content

Commit de83838

Browse files
authored
Merge pull request #4564 from matter-labs/sb-at-migration-tests
test: add token balance migration tests
2 parents 58ea26a + 91ae43e commit de83838

File tree

8 files changed

+1713
-3
lines changed

8 files changed

+1713
-3
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "asset-tracker-chain-migration-test",
3+
"version": "1.0.0",
4+
"license": "MIT",
5+
"mocha": {
6+
"timeout": 240000,
7+
"exit": true,
8+
"color": false,
9+
"slow": 0,
10+
"require": [
11+
"ts-node/register",
12+
"mocha-steps"
13+
]
14+
},
15+
"scripts": {
16+
"migration-test": "mocha tests/migration.test.ts"
17+
},
18+
"devDependencies": {
19+
"@types/chai": "^4.2.21",
20+
"@types/mocha": "^8.2.3",
21+
"@types/mocha-steps": "^1.3.0",
22+
"@types/node": "^18.19.15",
23+
"@types/node-fetch": "^2.5.7",
24+
"chai": "^4.3.4",
25+
"chai-as-promised": "^7.1.1",
26+
"ethers": "^6.13.5",
27+
"mocha": "^9.0.2",
28+
"mocha-steps": "^1.3.0",
29+
"node-fetch": "^2.6.1",
30+
"ts-node": "^10.1.0",
31+
"typescript": "^4.3.5",
32+
"zksync-ethers": "https://github.com/zksync-sdk/zksync-ethers#di/avoid-zks-estimate-fee-build"
33+
},
34+
"dependencies": {
35+
"prettier": "^2.3.2"
36+
}
37+
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import * as utils from 'utils';
2+
import { DEFAULT_LARGE_AMOUNT, L1ERC20Handler, L2ERC20Handler, Tester, WithdrawalHandler } from './tester';
3+
import * as zksync from 'zksync-ethers';
4+
import * as ethers from 'ethers';
5+
import { expect } from 'chai';
6+
import fs from 'node:fs/promises';
7+
import { existsSync, readFileSync } from 'node:fs';
8+
import { BytesLike } from '@ethersproject/bytes';
9+
import { BigNumberish } from 'ethers';
10+
import { loadConfig, shouldLoadConfigFromFile } from 'utils/build/file-configs';
11+
import path from 'path';
12+
import { CONTRACT_DEPLOYER, CONTRACT_DEPLOYER_ADDRESS, hashBytecode, ZKSYNC_MAIN_ABI } from 'zksync-ethers/build/utils';
13+
import { utils as zksync_utils } from 'zksync-ethers';
14+
import { logsTestPath } from 'utils/build/logs';
15+
import { waitForNewL1Batch } from 'utils';
16+
import { getMainWalletPk } from 'highlevel-test-tools/src/wallets';
17+
18+
19+
20+
import {ChainHandler} from "./tester";
21+
22+
23+
async function logsPath(name: string): Promise<string> {
24+
return await logsTestPath(fileConfig.chain, 'logs/upgrade/', name);
25+
}
26+
27+
const L2_BRIDGEHUB_ADDRESS = '0x0000000000000000000000000000000000010002';
28+
const pathToHome = path.join(__dirname, '../../../..');
29+
const fileConfig = shouldLoadConfigFromFile();
30+
31+
// const contracts: Contracts = initContracts(pathToHome, fileConfig.loadFromFile);
32+
33+
const ZK_CHAIN_INTERFACE = JSON.parse(
34+
readFileSync(pathToHome + '/contracts/l1-contracts/out/IZKChain.sol/IZKChain.json').toString()
35+
).abi;
36+
37+
const depositAmount = ethers.parseEther('0.001');
38+
39+
interface GatewayInfo {
40+
gatewayChainId: string;
41+
gatewayProvider: zksync.Provider;
42+
gatewayCTM: string;
43+
l2ChainAdmin: string;
44+
l2DiamondProxyAddress: string;
45+
}
46+
47+
interface Call {
48+
target: string;
49+
value: BigNumberish;
50+
data: BytesLike;
51+
}
52+
53+
// This test requires interop and so it requires Gateway chain.
54+
// This is the name of the chain.
55+
const GATEWAY_CHAIN_NAME = 'gateway';
56+
57+
// Returns a wallet that is both rich on L1 and on GW
58+
async function prepareRichWallet(): Promise<zksync.Wallet> {
59+
const generalConfig = loadConfig({
60+
pathToHome,
61+
chain: GATEWAY_CHAIN_NAME,
62+
config: 'general.yaml'
63+
});
64+
const contractsConfig = loadConfig({
65+
pathToHome,
66+
chain: GATEWAY_CHAIN_NAME,
67+
config: 'contracts.yaml'
68+
});
69+
const secretsConfig = loadConfig({
70+
pathToHome,
71+
chain: GATEWAY_CHAIN_NAME,
72+
config: 'secrets.yaml'
73+
});
74+
const ethProviderAddress = secretsConfig.l1.l1_rpc_url;
75+
const web3JsonRpc = generalConfig.api.web3_json_rpc.http_url;
76+
77+
const richWallet = new zksync.Wallet(getMainWalletPk('gateway'), new zksync.Provider(web3JsonRpc), new ethers.JsonRpcProvider(ethProviderAddress));
78+
79+
// We assume that Gateway has "ETH" as the base token.
80+
// We deposit funds to ensure that the wallet is rich
81+
await (await richWallet.deposit({
82+
token: zksync.utils.ETH_ADDRESS_IN_CONTRACTS,
83+
amount: ethers.parseEther('10.0')
84+
})).wait();
85+
86+
return richWallet;
87+
}
88+
89+
/// There are the following kinds of tokens' states that we test:
90+
/// At the moment of migration the token can be:
91+
/// - Native to chain, already present on L1/other L2s.
92+
/// - Native to chain, not present on L1 at all (can have unfinalized withdrawal).
93+
/// - Native to L1, never been on the chain.
94+
/// - Native to L1, already present on the chain.
95+
/// - Native to another L2, never present on the chain.
96+
/// - Native to another L2, already present on the chain.
97+
/// After the chain migrates to GW, we can classify the states of the tokens the following way:
98+
/// - Migrated the balance to GW. May be done after the token already received some deposits.
99+
/// - Never migrated the balance to GW (but the token is known to the chain). May be done after the token.
100+
/// - Never migrated the balance to GW (but the token is bridged for the first time). No migration should be needed at all.
101+
/// After the chain migrates from GW, we need to test that all the tokens can be withdrawn in sufficient amounts to move
102+
/// the entire balance to L1. It should not be possible to finalize all old interops.
103+
104+
describe('Asset tracker migration test', function () {
105+
let ethChainHandler: ChainHandler;
106+
let erc20ChainHandler: ChainHandler;
107+
108+
let l1RichWallet: ethers.Wallet;
109+
let gwRichWallet: zksync.Wallet;
110+
111+
let ethChainTokenPreBridged: L2ERC20Handler;
112+
let ethChainTokenNotPreBridged: L2ERC20Handler;
113+
let ethChainTokenUnfinalizedWithdrawalHandler: WithdrawalHandler;
114+
let l1NativeToken: L1ERC20Handler;
115+
let l1NativeTokenPreBridged: L1ERC20Handler;
116+
let l1NativeToken2: L1ERC20Handler;
117+
118+
let erc20ChainTokenPreBridged: L2ERC20Handler;
119+
let erc20ChainTokenNotPreBridged: L2ERC20Handler;
120+
121+
before('Setup the system', async function () {
122+
console.log('Initializing rich wallet...');
123+
gwRichWallet = await prepareRichWallet();
124+
l1RichWallet = gwRichWallet.ethWallet();
125+
126+
console.log('Creating a new chain 1...');
127+
ethChainHandler = await ChainHandler.createNewChain('era');
128+
// FIXME: not erc20
129+
console.log('Creating a new chain 2...');
130+
erc20ChainHandler = await ChainHandler.createNewChain('era');
131+
});
132+
133+
step('TMP spawn tokens', async function() {
134+
ethChainTokenPreBridged = await ethChainHandler.deployNativeToken();
135+
ethChainTokenNotPreBridged = await ethChainHandler.deployNativeToken();
136+
137+
l1NativeToken = await L1ERC20Handler.deployToken(l1RichWallet);
138+
l1NativeTokenPreBridged = await L1ERC20Handler.deployToken(l1RichWallet);
139+
l1NativeToken2 = await L1ERC20Handler.deployToken(l1RichWallet);
140+
141+
erc20ChainTokenPreBridged = await erc20ChainHandler.deployNativeToken();
142+
erc20ChainTokenNotPreBridged = await erc20ChainHandler.deployNativeToken();
143+
})
144+
145+
step('Bridge tokens', async function () {
146+
const withdrawalHandler1 = await ethChainTokenPreBridged.withdraw();
147+
148+
// For now it will be unfinalized, we'll use it later.
149+
ethChainTokenUnfinalizedWithdrawalHandler = await ethChainTokenNotPreBridged.withdraw();
150+
151+
await l1NativeTokenPreBridged.deposit(ethChainHandler, DEFAULT_LARGE_AMOUNT);
152+
await l1NativeTokenPreBridged.deposit(erc20ChainHandler, DEFAULT_LARGE_AMOUNT);
153+
154+
await withdrawalHandler1.finalizeWithdrawal(l1RichWallet);
155+
})
156+
157+
step('Migration of balances to GW', async function () {
158+
await Promise.all([
159+
ethChainHandler.migrateToGateway(),
160+
// erc20ChainHandler.migrateToGateway()
161+
]);
162+
163+
// const l2VersionTokenPreBridged = await l1NativeTokenPreBridged.atL2SameWallet(ethChainHandler);
164+
165+
// const l1Native
166+
167+
// Each of the below should Fail
168+
ethChainTokenPreBridged.withdraw();
169+
ethChainTokenNotPreBridged.withdraw();
170+
171+
172+
// // should fail
173+
// l2VersionTokenPreBridged.withdraw();
174+
// // should also fail
175+
// ethChainTokenUnfinalizedWithdrawalHandler.finalizeWithdrawal(l1RichWallet);
176+
177+
// // We migrate the tokens two times. This is to
178+
// // demonstrate that it is possible to call the migration again
179+
// // and the handlers will still work.
180+
// const migrationHandlers1 = [
181+
// await ethChainTokenPreBridged.migrateBalanceL2ToGW(),
182+
// await ethChainTokenNotPreBridged.migrateBalanceL2ToGW(),
183+
// await l2VersionTokenPreBridged.migrateBalanceL2ToGW()
184+
// ];
185+
// const migrationHandlers2 = [
186+
// await ethChainTokenPreBridged.migrateBalanceL2ToGW(),
187+
// await ethChainTokenNotPreBridged.migrateBalanceL2ToGW(),
188+
// await l2VersionTokenPreBridged.migrateBalanceL2ToGW()
189+
// ];
190+
191+
// // Sometimes we use migrationHandlers1, sometimes migrationHandlers 2,
192+
// // these should be equivalent.
193+
// // TODO: maybe check for actual equivalence of messages.
194+
// await migrationHandlers1[0].finalizeMigration(l1RichWallet);
195+
// await migrationHandlers2[1].finalizeMigration(l1RichWallet);
196+
// await migrationHandlers1[0].finalizeMigration(l1RichWallet);
197+
198+
// // Now all the below should succeed:
199+
// // TODO: actually check that these withdrawals will finalize fine.
200+
// // We should also spawn a withdrawal to be finalized after the chain has moved away from GW.
201+
// await ethChainTokenPreBridged.withdraw();
202+
// await ethChainTokenNotPreBridged.withdraw();
203+
// await l2VersionTokenPreBridged.withdraw();
204+
// ethChainTokenUnfinalizedWithdrawalHandler.finalizeWithdrawal(l1RichWallet);
205+
});
206+
207+
// step('Test receiving interop for migrated assets', async function () {
208+
// // TODO
209+
// })
210+
211+
// step('Test automatic registration', async function () {
212+
// await l1NativeToken.deposit(ethChainHandler);
213+
// // We dont withdraw it yet, we'll withdraw it after we migrate to L1.
214+
// await l1NativeToken2.deposit(ethChainHandler);
215+
// const l2Repr = await l1NativeToken.atL2SameWallet(ethChainHandler);
216+
217+
// // should succeed
218+
// const withdrawHandle = await l2Repr.withdraw();
219+
// await withdrawHandle.finalizeWithdrawal(l1RichWallet);
220+
221+
// // TODO: dont forget to check asset migrtion number.
222+
// });
223+
224+
// step('Migrate back to L1', async function () {
225+
// await ethChainHandler.migrateFromGateway();
226+
227+
// const l2Token = await l1NativeToken2.atL2SameWallet(ethChainHandler);
228+
// const withdrawHandler = await l2Token.withdraw();
229+
230+
// // should fail, since the chain has not balance.
231+
// await withdrawHandler.finalizeWithdrawal(l1RichWallet);
232+
233+
// await l2Token.migrateBalanceGWtoL1(gwRichWallet);
234+
235+
// // Should succeed
236+
// await withdrawHandler.finalizeWithdrawal(l1RichWallet);
237+
238+
// // todo: test the ability to migrate all of the tokens' balances to the chain on L1.
239+
240+
// // todo: test that all of the withdrawn tokens can be withdrawn and finalized.
241+
// })
242+
243+
after('shutdown', async function () {
244+
console.log('Tearing down chains...');
245+
if (ethChainHandler) {
246+
await ethChainHandler.stopServer();
247+
}
248+
if (erc20ChainHandler) {
249+
await erc20ChainHandler.stopServer();
250+
}
251+
console.log('Complete');
252+
});
253+
});

0 commit comments

Comments
 (0)