|
| 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