NFTBridge
60,000 USDC
View results
Submission Details
Severity: low
Valid

Failure to Clear `Escrow` State in `Starknet` Bridge Contract's `withdraw_auto_from_l1` Function

Summary

The Starknet bridge contract's withdraw_auto_from_l1 function fails to update the escrow state after transferring tokens, leading to potential issues with token withdrawals

Vulnerability Details

In the Starknet bridge contract, the escrow mapping tracks the ownership of tokens that are temporarily held by the bridge during cross-chain operations.

escrow: LegacyMap::<(ContractAddress, u256), ContractAddress>,

During a token deposit on L2, the deposit_tokens function is called:

fn deposit_tokens(
ref self: ContractState,
salt: felt252,
collection_l2: ContractAddress,
owner_l1: EthAddress,
token_ids: Span<u256>,
use_withdraw_auto: bool,
use_deposit_burn_auto: bool,
) {
...snip...
escrow_deposit_tokens(ref self, collection_l2, from, token_ids);
...snip...

which in turn calls escrow_deposit_tokens to update the escrow mapping:

fn escrow_deposit_tokens(
ref self: ContractState,
contract_address: ContractAddress,
from: ContractAddress,
token_ids: Span<u256>,
) {
...snip...
self.escrow.write((contract_address, token_id), from);
};
...snip...
}

However, when a token withdrawal is initiated via l1_handler_transaction through the withdraw_auto_from_l1 function, the contract checks if the token is escrowed by verifying if the escrow mapping contains a non-zero address. If it is escrowed, it transfers the token back to th user on L2:

#[l1_handler]
fn withdraw_auto_from_l1(
ref self: ContractState,
from_address: felt252,
req: Request
) {
...snip...
let is_escrowed = !self.escrow.read((collection_l2, token_id)).is_zero();
if is_escrowed {
IERC721Dispatcher { contract_address: collection_l2 }
.transfer_from(from, to, token_id);
}
...snip...

The issue is, after this transfer, there's no code to update the escrow state. The escrow mapping is not cleared or updated after the token is transferred out of escrow.

This means the token's escrow entry remains in the mapping, indicating that the token is still held by the bridge, even though it has already been transferred back to the user.

Thus, escrow state gets out-of-sync with the actual state of tokens in the bridge.

Impact

If a token that was previously withdrawn is deposited again, subsequent legitimate withdrawal attempts will still detect the token as being escrowed. This might cause the contract to attempt another transfer instead of minting new tokens. If the bridge does not have possession of the token at that point, the withdrawal process could fail, effectively blocking the user from retrieving their tokens.

Tools Used

VSCode

Recommendations

Add the following line to the withdraw_auto_from_l1 function

if is_escrowed {
IERC721Dispatcher { contract_address: collection_l2 }
.transfer_from(from, to, token_id);
// Clear the escrow state
+ self.escrow.write((collection_l2, token_id), ContractAddress::zero());
}
Updates

Lead Judging Commences

n0kto Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

finding-L2-withdrawing-do-not-clean-escrow-mapping

Impact: Incorrect state without any other impact, which deserves a Low according to CodeHawks documentation.

Appeal created

n0kto Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-L2-withdrawing-do-not-clean-escrow-mapping

Impact: Incorrect state without any other impact, which deserves a Low according to CodeHawks documentation.

Support

FAQs

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