Skip to content

Commit acf08fd

Browse files
upd how migration from gw works
1 parent 7e1624d commit acf08fd

File tree

8 files changed

+106
-149
lines changed

8 files changed

+106
-149
lines changed

l1-contracts/contracts/bridge/asset-tracker/AssetTrackerBase.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ abstract contract AssetTrackerBase is
5959
/// @notice Denotes whether a token is registered or not.
6060
/// - On L1AssetTracker, it means that all the chains have correct chainBalance and preV31Total supply set for this token.
6161
/// - On L2AssetTracker, it means that the token's chainBalance is set correctly and its `totalPreV31TotalSupply` is tracked correctly.
62+
/// @dev Once we know that all legacy tokens have been registered (and all new ones have the corresponding logic performed automatically),
63+
/// we can remove the mapping. So DONT RELY ON IT!
6264
mapping(bytes32 assetId => bool isTokenRegistered) public isTokenRegistered;
6365

6466
function _nativeTokenVault() internal view virtual returns (INativeTokenVaultBase);

l1-contracts/contracts/bridge/asset-tracker/GWAssetTracker.sol

Lines changed: 23 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,6 @@ contract GWAssetTracker is AssetTrackerBase, IGWAssetTracker {
6666
/// empty messageRoot calculated for specific chain.
6767
mapping(uint256 chainId => bytes32 emptyMessageRoot) internal emptyMessageRoot;
6868

69-
/// @notice We save the chainBalance which equals the chains totalSupply before the first GW->L1 migration so that it can be replayed.
70-
/// @dev Note, that the balance is only saved for even migration numbers, i.e. when the chain did not settle on top of Gateway.
71-
/// This is needed so that in the future after e.g. a chain migrated with number N (odd number), we would remember how much funds did it
72-
/// have right before the migration (when N - 1 was the migration number).
73-
mapping(uint256 chainId => mapping(uint256 migrationNumber => mapping(bytes32 assetId => SavedTotalSupply savedChainBalance)))
74-
internal savedChainBalance;
75-
7669
modifier onlyUpgrader() {
7770
if (msg.sender != L2_COMPLEX_UPGRADER_ADDR) {
7871
revert Unauthorized(msg.sender);
@@ -170,8 +163,6 @@ contract GWAssetTracker is AssetTrackerBase, IGWAssetTracker {
170163
bytes32 _canonicalTxHash,
171164
BalanceChange calldata _balanceChange
172165
) external onlyL2InteropCenter {
173-
uint256 chainMigrationNumber = _getChainMigrationNumber(_chainId);
174-
175166
if (_tokenCanSkipMigrationOnSettlementLayer(_chainId, _balanceChange.assetId)) {
176167
_forceSetAssetMigrationNumber(_chainId, _balanceChange.assetId);
177168
}
@@ -180,12 +171,11 @@ contract GWAssetTracker is AssetTrackerBase, IGWAssetTracker {
180171
}
181172

182173
/// Note we don't decrease L1ChainBalance here, since we don't track L1 chainBalance on Gateway.
183-
_increaseAndSaveChainBalance(_chainId, _balanceChange.assetId, _balanceChange.amount, chainMigrationNumber);
174+
_increaseAndSaveChainBalance(_chainId, _balanceChange.assetId, _balanceChange.amount);
184175
_increaseAndSaveChainBalance(
185176
_chainId,
186177
_balanceChange.baseTokenAssetId,
187-
_balanceChange.baseTokenAmount,
188-
chainMigrationNumber
178+
_balanceChange.baseTokenAmount
189179
);
190180

191181
_registerToken(_balanceChange.assetId, _balanceChange.originToken, _balanceChange.tokenOriginChainId);
@@ -379,8 +369,7 @@ contract GWAssetTracker is AssetTrackerBase, IGWAssetTracker {
379369
_increaseAndSaveChainBalance(
380370
interopBundle.destinationChainId,
381371
destinationChainBaseTokenAssetId,
382-
totalBaseTokenAmount,
383-
_getChainMigrationNumber(interopBundle.destinationChainId)
372+
totalBaseTokenAmount
384373
);
385374
}
386375

@@ -453,8 +442,7 @@ contract GWAssetTracker is AssetTrackerBase, IGWAssetTracker {
453442
_decreaseChainBalance(_sourceChainId, _assetId, _amount);
454443
}
455444
if (_destinationChainId != L1_CHAIN_ID) {
456-
uint256 chainMigrationNumber = _getChainMigrationNumber(_destinationChainId);
457-
_increaseAndSaveChainBalance(_destinationChainId, _assetId, _amount, chainMigrationNumber);
445+
_increaseAndSaveChainBalance(_destinationChainId, _assetId, _amount);
458446
}
459447
}
460448
}
@@ -521,18 +509,19 @@ contract GWAssetTracker is AssetTrackerBase, IGWAssetTracker {
521509
}
522510

523511
/// @notice Migrates the token balance from Gateway to L1.
524-
/// @dev This function can be called multiple times on the Gateway as it saves the chainBalance on the first call.
525-
/// @dev This function is permissionless.
512+
/// @dev This function is intended to be permissionless so that a chain that has moved out
513+
/// of Gateway has an easy way to migrate its balance out of the system.
526514
function initiateGatewayToL1MigrationOnGateway(uint256 _chainId, bytes32 _assetId) external {
527515
address zkChain = L2_BRIDGEHUB.getZKChain(_chainId);
528516
require(zkChain != address(0), ChainIdNotRegistered(_chainId));
529517

530518
// If the chain already migrated back to GW, then we need the previous migration number.
531519
uint256 chainMigrationNumber = _calculatePreviousChainMigrationNumber(_chainId);
532520
require(assetMigrationNumber[_chainId][_assetId] < chainMigrationNumber, InvalidAssetMigrationNumber());
533-
// We don't save chainBalance here since it might not be the final chainBalance for this value of the chainMigrationNumber.
534-
uint256 amount = _getOrSaveChainBalance(_chainId, _assetId, chainMigrationNumber);
535521

522+
require(chainMigrationNumber == 2, "Chain must have migrated back to L1 before withdraing balance");
523+
524+
uint256 amount = chainBalance[_chainId][_assetId];
536525
GatewayToL1TokenBalanceMigrationData memory tokenBalanceMigrationData = GatewayToL1TokenBalanceMigrationData({
537526
version: TOKEN_BALANCE_MIGRATION_DATA_VERSION,
538527
originToken: originToken[_assetId],
@@ -545,6 +534,10 @@ contract GWAssetTracker is AssetTrackerBase, IGWAssetTracker {
545534
});
546535
_sendGatewayToL1MigrationDataToL1(tokenBalanceMigrationData);
547536

537+
// We assign chain balance to the 0 and bump asset migration number for replay protection
538+
chainBalance[_chainId][_assetId] = 0;
539+
assetMigrationNumber[_chainId][_assetId] = chainMigrationNumber;
540+
548541
emit GatewayToL1MigrationInitiated(_assetId, _chainId, amount);
549542
}
550543

@@ -558,61 +551,22 @@ contract GWAssetTracker is AssetTrackerBase, IGWAssetTracker {
558551
return chainMigrationNumber;
559552
}
560553

561-
/// @notice Gets the chain balance for migration, saving it if this is the first time it's accessed.
562-
/// @dev This function implements a "snapshot and clear" pattern for chain balances during migration.
563-
/// @dev On first access, it saves the current chainBalance and sets it to 0 to prevent double-spending.
564-
/// @dev Subsequent accesses return the saved value without modifying the current chainBalance.
565-
/// @param _chainId The chain ID whose balance is being queried.
566-
/// @param _assetId The asset ID of the token.
567-
/// @param _migrationNumber The migration number for this operation.
568-
/// @return The saved chain balance for this migration.
569-
function _getOrSaveChainBalance(
570-
uint256 _chainId,
571-
bytes32 _assetId,
572-
uint256 _migrationNumber
573-
) internal returns (uint256) {
574-
// Check if we've already saved the balance for this migration
575-
SavedTotalSupply memory tokenSavedTotalSupply = savedChainBalance[_chainId][_migrationNumber][_assetId];
576-
if (!tokenSavedTotalSupply.isSaved) {
577-
// First time accessing this balance for this migration number
578-
tokenSavedTotalSupply.amount = chainBalance[_chainId][_assetId];
579-
// Persist the saved balance for this specific migration
580-
savedChainBalance[_chainId][_migrationNumber][_assetId] = SavedTotalSupply({
581-
isSaved: true,
582-
amount: tokenSavedTotalSupply.amount
583-
});
584-
}
585-
586-
// Return the balance that was available at the time of this migration
587-
return tokenSavedTotalSupply.amount;
588-
}
589-
590554
/// @notice Confirms a migration operation has been completed and updates the asset migration number.
591555
/// @param _data The migration confirmation data containing chain ID, asset ID, and migration number.
592556
function confirmMigrationOnGateway(MigrationConfirmationData calldata _data) external onlyServiceTransactionSender {
593-
_confirmMigrationOnGateway(_data);
594-
}
595-
596-
function _confirmMigrationOnGateway(MigrationConfirmationData memory _data) internal {
597-
assetMigrationNumber[_data.chainId][_data.assetId] = _data.assetMigrationNumber;
598-
// Register the token if it wasn't already
599-
_registerToken(_data.assetId, _data.originToken, _data.tokenOriginChainId);
600557
if (_data.isL1ToGateway) {
558+
assetMigrationNumber[_data.chainId][_data.assetId] = _data.assetMigrationNumber;
559+
// Register the token if it wasn't already
560+
_registerToken(_data.assetId, _data.originToken, _data.tokenOriginChainId);
561+
601562
/// In this case the balance might never have been migrated back to L1.
602563
chainBalance[_data.chainId][_data.assetId] += _data.amount;
603-
} else {
604-
_decreaseChainBalance(_data.chainId, _data.assetId, _data.amount);
605-
606-
uint256 chainMigrationNumber = _calculatePreviousChainMigrationNumber(_data.chainId);
607-
SavedTotalSupply memory savedBalance = savedChainBalance[_data.chainId][chainMigrationNumber][
608-
_data.assetId
609-
];
610-
if (savedBalance.isSaved) {
611-
savedChainBalance[_data.chainId][chainMigrationNumber][_data.assetId].amount =
612-
savedBalance.amount -
613-
_data.amount;
614-
}
615564
}
565+
566+
// For migrations from GW, the chainBalance and assetMigrationNumber are updated at the initiation of the migration.
567+
// Additionally, all the tokens are expected to be registered the first time they are deposited via the GWAssetTracker, i.e.
568+
// the amount migrated can not be more than 0 if the token was not registered.
569+
616570
}
617571

618572
/*//////////////////////////////////////////////////////////////
@@ -622,14 +576,8 @@ contract GWAssetTracker is AssetTrackerBase, IGWAssetTracker {
622576
function _increaseAndSaveChainBalance(
623577
uint256 _chainId,
624578
bytes32 _assetId,
625-
uint256 _amount,
626-
uint256 _chainMigrationNumber
579+
uint256 _amount
627580
) internal {
628-
// We save the chainBalance for the previous migration number so that the chain balance can be migrated back to GW in case it was not migrated.
629-
// Note, that for this logic to be correct, we need to ensure that `_chainMigrationNumber` is odd, i.e. the chain actually
630-
// actively settles on top of Gateway.
631-
_getOrSaveChainBalance(_chainId, _assetId, _chainMigrationNumber - 1);
632-
// we increase the chain balance of the token.
633581
if (_amount > 0) {
634582
chainBalance[_chainId][_assetId] += _amount;
635583
}

l1-contracts/contracts/bridge/asset-tracker/IL1AssetTracker.sol

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,27 @@ import {FinalizeL1DepositParams} from "../../common/Messaging.sol";
66
import {IBridgehubBase} from "../../core/bridgehub/IBridgehubBase.sol";
77

88
interface IL1AssetTracker {
9+
/// @notice Per-(chainId, assetId) migration accounting stored on L1.
10+
/// @param preV31ChainBalance Chain balance right before the v31 migration.
11+
/// - For non-native tokens it is exactly equal to chainBalance before the *ecosystem* upgraded to v31 (0 for new tokens).
12+
/// - For tokens native to the chain, we imagine that it received 2^256-1 token deposit at the inception point and so
13+
/// all the balances that are not present on the chain are from claimed withdrawals, i.e. for a token that was bridged
14+
/// before v31 it is equal to `2^256-1 - <sum of other chainBalances, including l1>`. For new tokens it is exactly `2^256-1`.
15+
/// @param totalDepositedFromL1 Total amount deposited from L1 to the chain since v31 accounting started. Note, that it is not
16+
/// just about any L1->L2 deposit, but only those that debited the chainBalance on L1 directly and it is assumed that every such
17+
/// deposit will be processed while the chain is still settling on L1. It is the responsibility of the chain admin to ensure that.
18+
/// @param totalClaimedOnL1 Total amount claimed on L1 (withdrawals and failed deposits) since v31 accounting started. Note, that it is not just
19+
/// about any claim, but claims that affect `chainBalance` of the chain (i.e. the respective failed deposits or withdrawals were submitted
20+
/// while the chain was settling on L1)).
21+
/// @dev It is the responsibility of the *chain* to ensure that all deposits are processed before the migration to Gateway is complete
22+
/// and vice versa, i.e. all deposits are either fully processed on L1 or fully processed while it settles on ZK Gateway. In case the chain violates
23+
/// this rule, invalid migration amount can be migrated, but it must only affect the chain and its users.
24+
struct InteropL1Info {
25+
uint256 preV31ChainBalance;
26+
uint256 totalDepositedFromL1;
27+
uint256 totalClaimedOnL1;
28+
}
29+
930
event PauseDepositsForChainRequested(uint256 indexed chainId, uint256 indexed settlementLayer);
1031

1132
function BRIDGE_HUB() external view returns (IBridgehubBase);

0 commit comments

Comments
 (0)