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

Bridge::depositToken fails to properly lock tokens in Escrow contract, could lead to inconsistencies between the bridge's state and the actual token ownership

Summary

The Bridge::depositToken function in the Ark protocol fails to properly lock tokens during the deposit process. This vulnerability could lead to inconsistencies between the bridge's state and the actual token ownership, potentially disrupting the bridge's normal operation and compromising its integrity.

Vulnerability Details

The Bridge::depositToken function is intended to deposit tokens into the bridge for transfer to the L2 network. However, the function does not effectively lock the tokens in the Escrow contract as expected.

The issue stems from the following sequence of events through the test function below:

  1. The bridge contract is given approval to transfer all tokens of a specific collection (e.g., erc721C1) from the user (e.g., Alice).

  2. The Bridge::depositToken function internally calls Escrow::_depositIntoEscrow.

  3. Escrow::_depositIntoEscrow attempts to transfer the tokens using IERC721(collection).transferFrom(msg.sender, address(this), id).

The problem arises because msg.sender in the context of _depositIntoEscrow is the bridge contract, not the original token owner. Although the bridge has approval to transfer tokens, it is not the owner, which may cause the transferFrom call to fail silently without reverting the transaction.

This is highlighted by the failing test testDeposit2TokensERC721WhitelistedAreNotEscrowed(), where the assertions checking if the tokens are escrowed return false instead of the expected true.

Assuming you have the right for Ark project and using Foundry, follow these steps:

In Escrow.sol contract, add this function at the end of the contract:

function tokenIsEscrowed(
address collection,
uint256 id
)
public
view
returns (bool)
{
return _isEscrowed(collection, id);
}

In Bridge.t.sol import Escrow contract:

import "../src/Escrow.sol";

Declare contract instance:

StarklaneEscrow escrow;

In setUp function add the following:

escrow = new StarklaneEscrow();

Copy and past the following test function:

function testDeposit2TokensERC721WhitelistedAreNotEscrowed() public {
IERC721MintRangeFree(erc721C1).mintRangeFree(alice, 0, 10);
uint256[] memory ids = new uint256[](2);
ids[0] = 5;
ids[1] = 8;
uint256 salt = 0x1;
snaddress to = Cairo.snaddressWrap(0x1);
// Enable whitelist
IStarklane(bridge).enableWhiteList(true);
// Whitelist the collection
IStarklane(bridge).whiteList(erc721C1, true);
// Deposit the tokens
vm.startPrank(alice);
IERC721(erc721C1).setApprovalForAll(address(bridge), true);
IStarklane(bridge).depositTokens{value: 30000}(
salt,
address(erc721C1),
to,
ids,
false
);
vm.stopPrank();
// Check if the tokens are in escrow
assertEq(escrow.tokenIsEscrowed(erc721C1, 5), true);
assertEq(escrow.tokenIsEscrowed(erc721C1, 8), true);
}

Run this command: forge test --mt testDeposit2TokensERC721WhitelistedAreNotEscrowed -vv
You should get this output:

Ran 1 test for test/Bridge.t.sol:BridgeTest
[FAIL. Reason: assertion failed] testDeposit2TokensERC721WhitelistedAreNotEscrowed() (gas: 678491)
Logs:
Error: a == b not satisfied [bool]
Left: false
Right: true
Error: a == b not satisfied [bool]
Left: false
Right: true
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 56.82ms (4.16ms CPU time)

Impact

The impact of this vulnerability is severe:

  • Inconsistent State: The bridge may believe it has locked tokens that are still under the control of the original owner, leading to a discrepancy between the bridge's state and the actual token ownership.

  • Bridge Integrity: The core functionality of the bridge is compromised, as it cannot guarantee that tokens deposited for transfer to L2 are securely held.

Tools Used

Manual review - Foundry

Recommendations

Consider the following recommendation:
Direct Transfer: Instead of relying on transferFrom, implement a direct safeTransferFrom call in the Bridge::depositToken function:

IERC721(collection).safeTransferFrom(msg.sender, address(this), id);
Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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