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

Withdraw logic error for bridged tokens

Summary

Withdrawing a bridged token from StarkNet to Ethereum or vice versa is not possible without an admin setting the collection addresses, despite a previous audit ([C-02]) claiming the issue was fixed.

Vulnerability Details

The issue marked as [C-02] in the previous audit was claimed to be resolved, but it is not possible to withdraw a bridged token without admin intervention.

if we follow a user that bridge new collection to starknet following steps would happen.

  1. User deposits a collection from Ethereum

    The request will include collectionL1 address and collectionL2 as 0. At this point, the _l1ToL2Addresses and _l2ToL1Addresses mappings are not updated.

    req.collectionL1 = collectionL1;
    req.collectionL2 = _l1ToL2Addresses[collectionL1]; // 0 for a new collectio2n
  2. withdraw_auto_from_l1 is called on starknet

    On the StarkNet side, since collection_l2 is 0, a new collection will be deployed, and mappings will be filled accordingly.

    self.l1_to_l2_addresses.write(l1_req, l2_addr_from_deploy);
    self.l2_to_l1_addresses.write(l2_addr_from_deploy, l1_req);

    https://github.com/Cyfrin/2024-07-ark-project/blob/273b7b94986d3914d5ee737c99a59ec8728b1517/apps/blockchain/starknet/src/bridge.cairo#L458

  3. User deposit bridged tokens to withdraw from starknet to eth

    Since starknet collections mappings are already filled request will include correct addresses

    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,// filled with correct address
    collection_l2,// filled with correct address
    ...
  4. User try to withdraw tokens from ethereum

    On the Ethereum side, the _verifyRequestAddresses function will verify the collection addresses. Inside the Solidity contract, if the admin has not set the mappings, they will remain 0. Since mapping and request address doesn't match user cant withdraw .

    address l1Req = collectionL1Req; // filled with correct address
    uint256 l2Req = snaddress.unwrap(collectionL2Req);// filled with correct address
    address l1Mapping = _l2ToL1Addresses[collectionL2Req]; //empty
    uint256 l2Mapping = snaddress.unwrap(_l1ToL2Addresses[l1Req]);//empty
    // L2 address is present, and L1 address too.
    if (l2Req > 0 && l1Req > address(0)) {
    if (l1Mapping != l1Req) {
    revert InvalidCollectionL1Address();

    https://github.com/Cyfrin/2024-07-ark-project/blob/273b7b94986d3914d5ee737c99a59ec8728b1517/apps/blockchain/ethereum/src/token/CollectionManager.sol#L138

    Test:

    function test_depositWithdrawTokensFail() public {
    // on starknet new collection will be created wth address 0x1111
    test_depositTokenERC721();
    felt252 header = Protocol.requestHeaderV1(
    CollectionType.ERC721,
    false,
    false
    );
    // simulate user wihdraw from starknet to eth
    uint256[] memory ids = new uint256[](1);
    ids[0] = 9;
    // Starknet mapping l2_to_l1_addresses l1_to_l2_addresses both filled
    // request collectionL1 and collectionL2 are non zero
    Request memory req = Request({
    header: header,
    hash: 0x1,
    collectionL1: erc721C1,
    collectionL2: Cairo.snaddressWrap(0x1111),
    ownerL1: bob,
    ownerL2: Cairo.snaddressWrap(0x123),
    name: "",
    symbol: "",
    uri: "ABCD",
    tokenIds: ids,
    tokenValues: new uint256[](0),
    tokenURIs: new string[](0),
    newOwners: new uint256[](0)
    });
    uint256[] memory reqSerialized = Protocol.requestSerialize(req);
    bytes32 msgHash = computeMessageHashFromL2(reqSerialized);
    // on L1 and pushed to starknet core.
    uint256[] memory hashes = new uint256[](1);
    hashes[0] = uint256(msgHash);
    IStarknetMessagingLocal(snCore).addMessageHashesFromL2(hashes);
    address collection = IStarklane(bridge).withdrawTokens(reqSerialized);
    }

This issue also present for starknet to ethereum bridged tokens with similar logic.

Impact

This issue prevents users from withdrawing bridged tokens until admin intervention.

Tools Used

manual

Recommendations

Implement different logic to update mappings or remove verification between request addresses and mappings

Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-first-bridgeof-a-collection-L1<->L2-do-not-sync-addresses

Likelyhood: High, any collections bridged, without bridge owner action, will be unable to bridge back. Impact: High, L2 -> L1 tokens will be stuck in the bridge. L1 -> L2 will need to ask for a cancellation.

Support

FAQs

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