When the first token of a collection is going to be bridged from L1 to L2, the address of the L2 collection is not known on L1 yet. So, req.collectionL2 = 0
. On L2 side, during withdrawal, L2 collection will be deployed and the mappings l1_to_l2_addresses
and l2_to_l1_addresses
would be updated accordingly. So far so good.
When this NFT of the L2 collection is going to be bridged from L2 to L1, an issue happens that reverts the transaction. Because, during withdrawal on L1 side, the mapping _l2ToL1Addresses[collectionL2Req]
returns address(0)
(because the L1 collection associated with the L2 collection is not known on L1). As a result, since l1Mapping != l1Req
, it reverts by InvalidCollectionL1Address
.
Suppose the first token of a collection is going to be bridged from L1 to L2.
During depositing the NFT on L1, since the associated L2 collection is not yet deployed on L2, req.collectionL2
would be 0
.
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/ethereum/src/Bridge.sol#L115
On L2 side, when the function withdraw_auto_from_l1
is invoked, it ensures the L2 collection deployment by calling the internal function ensure_erc721_deployment
.
In this function, since L2 collection associated with L1 collection is not available, i.e. l2_req = 0
and l2_bridge = 0
, collection_L2
would be zero. As a result, it will be deployed.
After deployment, the mappings l1_to_l2_addresses
and l2_to_l1_addresses
would be updated:
So far everything is working properly.
Now suppose, this collection on L2 is going to be bridged to L1.
When the function deposit_tokens
is called, the mapping l2_to_l1_addresses
is used to get the associated collection on L1. Then, the fields collection_l1
and collection_l2
in the struct Request
would be populated with nonzero values.
On L1 side, when calling the function withdrawTokens
, it calls the internal function _verifyRequestAddresses
to calculate the address of the associated collection on L1.
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/ethereum/src/Bridge.sol#L179
In the function _verifyRequestAddresses
, since l1Req > address(0)
and l2Req > 0
(both of these values are forwarded from L2), the body of first if-clause will not be executed.
Then, the body of second if-clause will be executed.
The issue happens here. Since address l1Mapping = _l2ToL1Addresses[collectionL2Req];
, the value of l1Mapping
is address(0)
, because the mapping _l2ToL1Addresses
is not aware of the L1 collection associated with L2 collection. So, the body of the first inner if-clause will be executed, and reverts the transaction.
This leads to a situation that bridging the collection from L2 to L1 would be impossible and leads to stuck of fund. The only solution is that the Ark owner sets the associated collections by calling setL1L2CollectionMapping
, then retrying the manual withdrawal on L1 again.
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/ethereum/src/Bridge.sol#L368
Please note that:
This finding is almost similar to the report with title "Issue with bridging back an L2-native NFT from L1 to L2", but the root cause and fix are different. In other words, this finding explains the issue when a collection is native to L1, then bridged to L2, and later bridged back to L1. But, the other report explains an issue when a collection is native to L2, then bridged to L1, and later bridged back to L2.
Stuck of NFT. Because the NFT is escrowed on L2, but it is not released on L1 due to the revert during withdrawal.
It should be modified as follows:
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.