Era

ZKsync
FoundryLayer 2
500,000 USDC
View results
Submission Details
Severity: medium
Valid

`L2AssetRouter.withdrawToken` reverts due to invalid asset id returned by `_ensureTokenRegisteredWithNTV`

Summary

In the withdrawToken function of the L2AssetRouter contract, the _ensureTokenRegisteredWithNTV function fails to properly return the assetId, causing the subsequent _withdrawSender function to fail.

Vulnerability Details

L2AssetRouter contract defines a function called withdrawToken that is used to withdraw l2NativeToken for a user.

function withdrawToken(address _l2NativeToken, bytes memory _assetData) public returns (bytes32) {
bytes32 recordedAssetId = INativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).assetId(_l2NativeToken);
uint256 recordedOriginChainId = INativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).originChainId(recordedAssetId);
if (recordedOriginChainId == L1_CHAIN_ID) {
revert AssetIdNotSupported(recordedAssetId);
}
>> bytes32 assetId = _ensureTokenRegisteredWithNTV(_l2NativeToken);
return _withdrawSender(assetId, _assetData, msg.sender, true);
}

However, there's a flaw in _ensureTokenRegisteredWithNTV function that it doesn't return a valid assetId, which then subsequently is used by _withdrawSender function. It will retain its initial value of bytes32(0). As an invalid assetId is provided, _withdrawSender will inevitably fail, preventing the msg.sender from receiving the expected token amount.

function _ensureTokenRegisteredWithNTV(address _token) internal override returns (bytes32 assetId) {
IL2NativeTokenVault nativeTokenVault = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR);
nativeTokenVault.ensureTokenIsRegistered(_token);
}

Below is an example scenario demonstrating the unfair revert:

Bob attempts to withdraw TokenA from the L2AssetRouter, but since TokenA hasn’t been registered with the NativeTokenVault (NTV), he needs to call the withdrawToken function to register the token and complete the withdrawal.
(Note: TokenA must not be WETH, as it is not supported by NTV. Also, if TokenA was already registered, it'd fail too.)
However, the _ensureTokenRegisteredWithNTV function fails to return a valid assetId registered with NTV, causing the withdrawal process to ultimately fail.

Proof of Concept

Modify the interface IL2AssetRouter to include the definition of the public function withdrawToken.

function withdrawToken(address _l2NativeToken, bytes memory _assetData) public returns (bytes32);

Put the test below inside L2Erc20TestAbstract.t.sol and run test yarn test:foundry --match-test test_withdrawTokenL2AR -vvv.

function test_withdrawTokenL2AR() public {
// user
address user = makeAddr("user");
// l2 ntv
IL2NativeTokenVault l2_ntv = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR);
IL2AssetRouter l2_ar = L2AssetRouter(L2_ASSET_ROUTER_ADDR);
// register L2 native token
ERC20 token = new ERC20("L2N", "L2N");
l2_ntv.registerToken(address(token));
deal(address(token), user, 1000);
vm.prank(user);
token.approve(address(l2_ntv), 1000);
// withdraw
vm.prank(user);
// reverts because assetId is not set in the _ensureTokenRegisteredWithNTV
vm.expectRevert();
l2_ar.withdrawToken(address(token), abi.encode(1000, user));
}

Impact

With invalid assetId provided, particularly in cases where _l2NativeToken has not yet been registered with the NativeTokenVault, _withdrawSender will fail entirely. This prevents the msg.sender from receiving the expected token amount, effectively halting the withdrawal process. Such scenarios could lead to disrupted user operations, potential loss of funds, and diminished trust in the system's reliability.

Tools Used

Manual Review, Foundry

Recommendations

Update _ensureTokenRegisteredWithNTV function to make sure the assetId is correctly assigned.

Updates

Lead Judging Commences

inallhonesty Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`L2AssetRouter._ensureTokenRegisteredWithNTV` `assetId` return value is never assigned, which will cause `withdrawToken` to fail

Appeal created

inallhonesty Lead Judge
5 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`L2AssetRouter._ensureTokenRegisteredWithNTV` `assetId` return value is never assigned, which will cause `withdrawToken` to fail

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.