Summary
When users bridge NFTs from L2 to L1, they can provide the use_withdraw_auto
flag. However, if use_withdraw_auto
is set to true
, their withdrawTokens
calls on L1 will always revert, causing the NFTs to get stuck on L1.
Vulnerability Details
When users bridge nft from L2 to L1, they allowed to provide use_withdraw_auto
flag, this flag will be encoded inside the header and propagated to L1 alongside with other Request
data.
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/starknet/src/bridge.cairo#L242-L291
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,
) {
ensure_is_enabled(@self);
assert(!self.bridge_l1_address.read().is_zero(), 'Bridge is not open');
let collection_l1 = self.l2_to_l1_addresses.read(collection_l2);
let req = Request {
>>> header: compute_request_header_v1(ctype, use_deposit_burn_auto, use_withdraw_auto),
hash: compute_request_hash(salt, collection_l2, owner_l1, token_ids),
collection_l1,
collection_l2,
owner_l1,
owner_l2: from,
name,
symbol,
base_uri,
ids: token_ids,
values: array![].span(),
uris,
new_owners: array![].span(),
};
}
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/starknet/src/request.cairo#L112-L136
fn compute_request_header_v1(
ctype: CollectionType,
use_deposit_burn_auto: bool,
use_withdraw_auto: bool,
) -> felt252 {
let mut header: u256 = HEADER_V1;
if (ctype == CollectionType::ERC721) {
header = header | ERC721_TYPE;
} else if (ctype == CollectionType::ERC1155) {
header = header | ERC1155_TYPE;
} else {
panic_with_felt252('Invalid collection type');
}
if (use_deposit_burn_auto) {
header = header | DEPOSIT_AUTO_BURN;
}
>>> if (use_withdraw_auto) {
header = header | WITHDRAW_AUTO;
}
header.try_into().expect('Invalid header V1')
}
However, on L1, when users call withdrawTokens
to retrieve the token, the transaction will revert because withdraw auto feature is currently disabled on L1.
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/ethereum/src/Bridge.sol#L169-L173
function withdrawTokens(
uint256[] calldata request
)
external
payable
returns (address)
{
if (!_enabled) {
revert BridgeNotEnabledError();
}
uint256 header = request[0];
>>> if (Protocol.canUseWithdrawAuto(header)) {
>>> revert NotSupportedYetError();
} else {
_consumeMessageStarknet(_starknetCoreAddress, _starklaneL2Address, request);
}
}
This will cause users call to retrieve the tokens will always revert.
Impact
Users can't retrieve their NFT on L1, and there is no way to recover the NFT on L2.
Tools Used
Manual review
Recommendations
If users set use_withdraw_auto
to true
, revert the bridging request on L2. Additionally, consider implementing a recover NFT functionality on L2 in case another unexpected issue arises.