Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
82282f2
wip
StanislavBreadless Oct 28, 2025
7daa177
starting the servers + shutdown works
StanislavBreadless Oct 29, 2025
808a959
wip
StanislavBreadless Oct 31, 2025
8e0a008
rm migrate token balances step (will be tested elsewhere)
zkzoomer Dec 5, 2025
d7c0231
wait for all batches to be executed
zkzoomer Dec 5, 2025
958cffe
rm redundant step
zkzoomer Dec 5, 2025
79e413e
unpause deposits
zkzoomer Dec 5, 2025
edd70de
rm unpause deposits step
zkzoomer Dec 6, 2025
0654bd4
bump contracts
zkzoomer Dec 7, 2025
7c0f75f
test only migration from gateway
zkzoomer Dec 7, 2025
f01cbee
negative test for repeated migrations
zkzoomer Dec 7, 2025
eb74bf6
bump contracts
zkzoomer Dec 7, 2025
2137744
try fix
zkzoomer Dec 7, 2025
fb1e3e3
try fix II
zkzoomer Dec 7, 2025
198f37f
try await api
zkzoomer Dec 7, 2025
b94ec43
debug logs
zkzoomer Dec 7, 2025
55b1c2d
try fix III
zkzoomer Dec 7, 2025
6d86b1f
try fix IV
zkzoomer Dec 7, 2025
c9025c5
bump contracts
zkzoomer Dec 12, 2025
91ae43e
Merge remote-tracking branch 'origin/sma/refactor-tests' into sb-at-m…
zkzoomer Dec 12, 2025
58dbac8
fix protocol version
zkzoomer Dec 12, 2025
9b6cc3f
rm fee call starters
zkzoomer Dec 12, 2025
f6a9f62
fix function name
zkzoomer Dec 15, 2025
58ea26a
REVERT THIS--temp fix
zkzoomer Dec 15, 2025
de83838
Merge pull request #4564 from matter-labs/sb-at-migration-tests
zkzoomer Dec 15, 2025
bd17443
bump contracts
zkzoomer Dec 15, 2025
3516b3f
lint
zkzoomer Dec 15, 2025
6bda156
Revert "REVERT THIS--temp fix"
zkzoomer Dec 15, 2025
60fb324
revert changes
zkzoomer Dec 15, 2025
22363f6
restore en integration tests
zkzoomer Dec 15, 2025
c4568f7
restore tests
zkzoomer Dec 15, 2025
ba386eb
lint
zkzoomer Dec 15, 2025
04e1cf9
lint
zkzoomer Dec 16, 2025
7d0dc5c
rename to token-balance-migration-test
zkzoomer Dec 16, 2025
eac86ad
add token balance migration tests
zkzoomer Dec 16, 2025
ec0cb97
lints
zkzoomer Dec 16, 2025
a2e0dea
update dependencies
zkzoomer Dec 16, 2025
a71eaa6
fix gateway chain id
zkzoomer Dec 16, 2025
d6b69f0
Merge remote-tracking branch 'origin/kl/medium-interop-support' into …
zkzoomer Dec 17, 2025
e867f0a
refactor interop tests
zkzoomer Dec 18, 2025
bc1664a
Merge remote-tracking branch 'origin/kl/medium-interop-support' into …
zkzoomer Dec 18, 2025
7809d1d
revert changes
zkzoomer Dec 18, 2025
b520152
Revert "restore en integration tests"
zkzoomer Dec 18, 2025
cb9479a
minor changes
zkzoomer Dec 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/lib/basic_types/src/protocol_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ impl ProtocolVersionId {
}

pub fn is_pre_medium_interop(&self) -> bool {
self < &Self::Version30
self < &Self::Version31
}

pub fn is_1_4_0(&self) -> bool {
Expand Down
4 changes: 2 additions & 2 deletions core/lib/multivm/src/pubdata_builders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ fn test_full_pubdata_building() {
));

let actual =
full_pubdata_builder.settlement_layer_pubdata(&input, ProtocolVersionId::Version30);
full_pubdata_builder.settlement_layer_pubdata(&input, ProtocolVersionId::Version31);
let expected = "00000001000000000000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000100000004deadbeef0000000100000060bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0100002a040001000000000000000000000000000000000000000000000000000000000000007e090e0000000c0901";
assert_eq!(
&hex::encode(actual),
Expand Down Expand Up @@ -141,7 +141,7 @@ fn test_hashed_pubdata_building() {
L2DACommitmentScheme::BlobsAndPubdataKeccak256,
));
let actual =
hashed_pubdata_builder.settlement_layer_pubdata(&input, ProtocolVersionId::Version30);
hashed_pubdata_builder.settlement_layer_pubdata(&input, ProtocolVersionId::Version31);
let expected = "fa96e2436e6fb4d668f5a06681a7c53fcb199b2747ee624ee52a13e85aac5f1e";
assert_eq!(
&hex::encode(actual),
Expand Down
3 changes: 2 additions & 1 deletion core/tests/highlevel-test-tools/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export {
genesisRecoveryTest,
snapshotsRecoveryTest,
gatewayMigrationToGatewayTest,
gatewayMigrationFromGatewayTest
gatewayMigrationFromGatewayTest,
tokenBalanceMigrationTest
} from './run-integration-tests';
export { generateLoad } from './generate-load';
export { getRpcUrl, queryJsonRpc, getL1BatchNumber, getL1BatchDetails } from './rpc-utils';
Expand Down
5 changes: 5 additions & 0 deletions core/tests/highlevel-test-tools/src/run-integration-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,8 @@ export async function gatewayMigrationFromGatewayTest(chainName: string): Promis
await initTestWallet(chainName);
await runTest('gateway-migration', chainName, undefined, ['--from-gateway']);
}

export async function tokenBalanceMigrationTest(chainName: string): Promise<void> {
await initTestWallet(chainName);
await runTest('token-balance-migration', chainName, undefined);
}
2 changes: 1 addition & 1 deletion core/tests/highlevel-test-tools/src/start-external-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class TestExternalNode {
markAsExpectingErrorCode(errorCode: number): void {
this.expectedErrorCode = errorCode;
console.log(`📝 Marked external node ${this.chainName} as expecting error code: ${errorCode}`);
removeErrorListeners(this.process);
removeErrorListeners(this.process!);
if (!this.process) {
throw new Error('External node process is not available!');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, it } from 'vitest';
import { createChainAndStartServer, TESTED_CHAIN_TYPE, tokenBalanceMigrationTest } from '../src';

const useGatewayChain = process.env.USE_GATEWAY_CHAIN;
const shouldSkip = useGatewayChain !== 'WITH_GATEWAY';

if (shouldSkip) {
console.log(
`⏭️ Skipping asset migration test for ${TESTED_CHAIN_TYPE} chain (USE_GATEWAY_CHAIN=${useGatewayChain})`
);
}

(shouldSkip ? describe.skip : describe)('Asset Migration Test', () => {
it(`for ${TESTED_CHAIN_TYPE} chain`, async () => {
const testChain = await createChainAndStartServer(TESTED_CHAIN_TYPE, 'Asset Migration Test');

await testChain.generateRealisticLoad();

await testChain.waitForAllBatchesToBeExecuted();

await tokenBalanceMigrationTest(testChain.chainName);
});
});
37 changes: 37 additions & 0 deletions core/tests/token-balance-migration-test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "asset-tracker-chain-migration-test",
"version": "1.0.0",
"license": "MIT",
"mocha": {
"timeout": 240000,
"exit": true,
"color": false,
"slow": 0,
"require": [
"ts-node/register",
"mocha-steps"
]
},
"scripts": {
"migration-test": "mocha tests/migration.test.ts"
},
"devDependencies": {
"@types/chai": "^4.2.21",
"@types/mocha": "^8.2.3",
"@types/mocha-steps": "^1.3.0",
"@types/node": "^18.19.15",
"@types/node-fetch": "^2.5.7",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"ethers": "^6.13.5",
"mocha": "^9.0.2",
"mocha-steps": "^1.3.0",
"node-fetch": "^2.6.1",
"ts-node": "^10.1.0",
"typescript": "^4.3.5",
"zksync-ethers": "https://github.com/zksync-sdk/zksync-ethers#di/avoid-zks-estimate-fee-build"
},
"dependencies": {
"prettier": "^2.3.2"
}
}
254 changes: 254 additions & 0 deletions core/tests/token-balance-migration-test/tests/migration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import * as utils from 'utils';
import { DEFAULT_LARGE_AMOUNT, L1ERC20Handler, L2ERC20Handler, Tester, WithdrawalHandler } from './tester';
import * as zksync from 'zksync-ethers';
import * as ethers from 'ethers';
import { expect } from 'chai';
import fs from 'node:fs/promises';
import { existsSync, readFileSync } from 'node:fs';
import { BytesLike } from '@ethersproject/bytes';
import { BigNumberish } from 'ethers';
import { loadConfig, shouldLoadConfigFromFile } from 'utils/build/file-configs';
import path from 'path';
import { CONTRACT_DEPLOYER, CONTRACT_DEPLOYER_ADDRESS, hashBytecode, ZKSYNC_MAIN_ABI } from 'zksync-ethers/build/utils';
import { utils as zksync_utils } from 'zksync-ethers';
import { logsTestPath } from 'utils/build/logs';
import { waitForNewL1Batch } from 'utils';
import { getMainWalletPk } from 'highlevel-test-tools/src/wallets';

import { ChainHandler } from './tester';

async function logsPath(name: string): Promise<string> {
return await logsTestPath(fileConfig.chain, 'logs/upgrade/', name);
}

const L2_BRIDGEHUB_ADDRESS = '0x0000000000000000000000000000000000010002';
const pathToHome = path.join(__dirname, '../../../..');
const fileConfig = shouldLoadConfigFromFile();

// const contracts: Contracts = initContracts(pathToHome, fileConfig.loadFromFile);

const ZK_CHAIN_INTERFACE = JSON.parse(
readFileSync(pathToHome + '/contracts/l1-contracts/out/IZKChain.sol/IZKChain.json').toString()
).abi;

const depositAmount = ethers.parseEther('0.001');

interface GatewayInfo {
gatewayChainId: string;
gatewayProvider: zksync.Provider;
gatewayCTM: string;
l2ChainAdmin: string;
l2DiamondProxyAddress: string;
}

interface Call {
target: string;
value: BigNumberish;
data: BytesLike;
}

// This test requires interop and so it requires Gateway chain.
// This is the name of the chain.
const GATEWAY_CHAIN_NAME = 'gateway';

// Returns a wallet that is both rich on L1 and on GW
async function prepareRichWallet(): Promise<zksync.Wallet> {
const generalConfig = loadConfig({
pathToHome,
chain: GATEWAY_CHAIN_NAME,
config: 'general.yaml'
});
const contractsConfig = loadConfig({
pathToHome,
chain: GATEWAY_CHAIN_NAME,
config: 'contracts.yaml'
});
const secretsConfig = loadConfig({
pathToHome,
chain: GATEWAY_CHAIN_NAME,
config: 'secrets.yaml'
});
const ethProviderAddress = secretsConfig.l1.l1_rpc_url;
const web3JsonRpc = generalConfig.api.web3_json_rpc.http_url;

const richWallet = new zksync.Wallet(
getMainWalletPk('gateway'),
new zksync.Provider(web3JsonRpc),
new ethers.JsonRpcProvider(ethProviderAddress)
);

// We assume that Gateway has "ETH" as the base token.
// We deposit funds to ensure that the wallet is rich
await (
await richWallet.deposit({
token: zksync.utils.ETH_ADDRESS_IN_CONTRACTS,
amount: ethers.parseEther('10.0')
})
).wait();

return richWallet;
}

/// There are the following kinds of tokens' states that we test:
/// At the moment of migration the token can be:
/// - Native to chain, already present on L1/other L2s.
/// - Native to chain, not present on L1 at all (can have unfinalized withdrawal).
/// - Native to L1, never been on the chain.
/// - Native to L1, already present on the chain.
/// - Native to another L2, never present on the chain.
/// - Native to another L2, already present on the chain.
/// After the chain migrates to GW, we can classify the states of the tokens the following way:
/// - Migrated the balance to GW. May be done after the token already received some deposits.
/// - Never migrated the balance to GW (but the token is known to the chain). May be done after the token.
/// - Never migrated the balance to GW (but the token is bridged for the first time). No migration should be needed at all.
/// After the chain migrates from GW, we need to test that all the tokens can be withdrawn in sufficient amounts to move
/// the entire balance to L1. It should not be possible to finalize all old interops.

describe('Asset tracker migration test', function () {
let ethChainHandler: ChainHandler;
let erc20ChainHandler: ChainHandler;

let l1RichWallet: ethers.Wallet;
let gwRichWallet: zksync.Wallet;

let ethChainTokenPreBridged: L2ERC20Handler;
let ethChainTokenNotPreBridged: L2ERC20Handler;
let ethChainTokenUnfinalizedWithdrawalHandler: WithdrawalHandler;
let l1NativeToken: L1ERC20Handler;
let l1NativeTokenPreBridged: L1ERC20Handler;
let l1NativeToken2: L1ERC20Handler;

let erc20ChainTokenPreBridged: L2ERC20Handler;
let erc20ChainTokenNotPreBridged: L2ERC20Handler;

before('Setup the system', async function () {
console.log('Initializing rich wallet...');
gwRichWallet = await prepareRichWallet();
l1RichWallet = gwRichWallet.ethWallet();

console.log('Creating a new chain with ETH as the base token...');
ethChainHandler = await ChainHandler.createNewChain('era');
console.log('Creating a new chain with a custom token as the base token...');
erc20ChainHandler = await ChainHandler.createNewChain('custom_token');
});

step('TMP spawn tokens', async function () {
ethChainTokenPreBridged = await ethChainHandler.deployNativeToken();
ethChainTokenNotPreBridged = await ethChainHandler.deployNativeToken();

l1NativeToken = await L1ERC20Handler.deployToken(l1RichWallet);
l1NativeTokenPreBridged = await L1ERC20Handler.deployToken(l1RichWallet);
l1NativeToken2 = await L1ERC20Handler.deployToken(l1RichWallet);

erc20ChainTokenPreBridged = await erc20ChainHandler.deployNativeToken();
erc20ChainTokenNotPreBridged = await erc20ChainHandler.deployNativeToken();
});

step('Bridge tokens', async function () {
const withdrawalHandler1 = await ethChainTokenPreBridged.withdraw();

// For now it will be unfinalized, we'll use it later.
ethChainTokenUnfinalizedWithdrawalHandler = await ethChainTokenNotPreBridged.withdraw();

await l1NativeTokenPreBridged.deposit(ethChainHandler, DEFAULT_LARGE_AMOUNT);
await l1NativeTokenPreBridged.deposit(erc20ChainHandler, DEFAULT_LARGE_AMOUNT);

await withdrawalHandler1.finalizeWithdrawal(l1RichWallet);
});

step('Migration of balances to GW', async function () {
await Promise.all([
ethChainHandler.migrateToGateway()
// erc20ChainHandler.migrateToGateway()
]);

// const l2VersionTokenPreBridged = await l1NativeTokenPreBridged.atL2SameWallet(ethChainHandler);

// const l1Native

// Each of the below should Fail
ethChainTokenPreBridged.withdraw();
ethChainTokenNotPreBridged.withdraw();

// // should fail
// l2VersionTokenPreBridged.withdraw();
// // should also fail
// ethChainTokenUnfinalizedWithdrawalHandler.finalizeWithdrawal(l1RichWallet);

// // We migrate the tokens two times. This is to
// // demonstrate that it is possible to call the migration again
// // and the handlers will still work.
// const migrationHandlers1 = [
// await ethChainTokenPreBridged.migrateBalanceL2ToGW(),
// await ethChainTokenNotPreBridged.migrateBalanceL2ToGW(),
// await l2VersionTokenPreBridged.migrateBalanceL2ToGW()
// ];
// const migrationHandlers2 = [
// await ethChainTokenPreBridged.migrateBalanceL2ToGW(),
// await ethChainTokenNotPreBridged.migrateBalanceL2ToGW(),
// await l2VersionTokenPreBridged.migrateBalanceL2ToGW()
// ];

// // Sometimes we use migrationHandlers1, sometimes migrationHandlers 2,
// // these should be equivalent.
// // TODO: maybe check for actual equivalence of messages.
// await migrationHandlers1[0].finalizeMigration(l1RichWallet);
// await migrationHandlers2[1].finalizeMigration(l1RichWallet);
// await migrationHandlers1[0].finalizeMigration(l1RichWallet);

// // Now all the below should succeed:
// // TODO: actually check that these withdrawals will finalize fine.
// // We should also spawn a withdrawal to be finalized after the chain has moved away from GW.
// await ethChainTokenPreBridged.withdraw();
// await ethChainTokenNotPreBridged.withdraw();
// await l2VersionTokenPreBridged.withdraw();
// ethChainTokenUnfinalizedWithdrawalHandler.finalizeWithdrawal(l1RichWallet);
});

// step('Test receiving interop for migrated assets', async function () {
// // TODO
// })

// step('Test automatic registration', async function () {
// await l1NativeToken.deposit(ethChainHandler);
// // We dont withdraw it yet, we'll withdraw it after we migrate to L1.
// await l1NativeToken2.deposit(ethChainHandler);
// const l2Repr = await l1NativeToken.atL2SameWallet(ethChainHandler);

// // should succeed
// const withdrawHandle = await l2Repr.withdraw();
// await withdrawHandle.finalizeWithdrawal(l1RichWallet);

// // TODO: dont forget to check asset migrtion number.
// });

// step('Migrate back to L1', async function () {
// await ethChainHandler.migrateFromGateway();

// const l2Token = await l1NativeToken2.atL2SameWallet(ethChainHandler);
// const withdrawHandler = await l2Token.withdraw();

// // should fail, since the chain has not balance.
// await withdrawHandler.finalizeWithdrawal(l1RichWallet);

// await l2Token.migrateBalanceGWtoL1(gwRichWallet);

// // Should succeed
// await withdrawHandler.finalizeWithdrawal(l1RichWallet);

// // todo: test the ability to migrate all of the tokens' balances to the chain on L1.

// // todo: test that all of the withdrawn tokens can be withdrawn and finalized.
// })

after('shutdown', async function () {
console.log('Tearing down chains...');
if (ethChainHandler) {
await ethChainHandler.stopServer();
}
if (erc20ChainHandler) {
await erc20ChainHandler.stopServer();
}
console.log('Complete');
});
});
Loading
Loading