Summary
The smart contract allows users to deposit NFTs into an escrow and initiate a cross-chain transfer to StarkNet (L2) with an arbitrary gas fee. The lack of a meaningful minimum gas fee requirement can result in NFTs being locked in the escrow contract if the L2 message is never processed due to insufficient gas.
[Link](https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/ethereum/src/Bridge.sol#L84)
function depositTokens(
uint256 salt,
address collectionL1,
snaddress ownerL2,
uint256[] calldata ids,
bool useAutoBurn
)
external
payable
{
if (!Cairo.isFelt252(snaddress.unwrap(ownerL2))) {
revert CairoWrapError();
}
if (!_enabled) {
revert BridgeNotEnabledError();
}
CollectionType ctype = TokenUtil.detectInterface(collectionL1);
if (ctype == CollectionType.ERC1155) {
revert NotSupportedYetError();
}
if (!_isWhiteListed(collectionL1)) {
revert NotWhiteListedError();
}
Request memory req;
req.header = Protocol.requestHeaderV1(ctype, useAutoBurn, false);
req.hash = Protocol.requestHash(salt, collectionL1, ownerL2, ids);
req.collectionL1 = collectionL1;
req.collectionL2 = _l1ToL2Addresses[collectionL1];
req.ownerL1 = msg.sender;
req.ownerL2 = ownerL2;
if (ctype == CollectionType.ERC721) {
(req.name, req.symbol, req.uri, req.tokenURIs) = TokenUtil.erc721Metadata(
collectionL1,
ids
);
} else {
(req.uri) = TokenUtil.erc1155Metadata(collectionL1);
}
_depositIntoEscrow(ctype, collectionL1, ids);
req.tokenIds = ids;
uint256[] memory payload = Protocol.requestSerialize(req);
if (payload.length >= MAX_PAYLOAD_LENGTH) {
revert TooManyTokensError();
}
IStarknetMessaging(_starknetCoreAddress).sendMessageToL2{value: msg.value}(
snaddress.unwrap(_starklaneL2Address),
felt252.unwrap(_starklaneL2Selector),
payload
);
emit DepositRequestInitiated(req.hash, block.timestamp, payload);
}
function sendMessageToL2(
uint256 toAddress,
uint256 selector,
uint256[] calldata payload
) external payable override returns (bytes32, uint256) {
require(msg.value > 0, "L1_MSG_FEE_MUST_BE_GREATER_THAN_0");
require(msg.value <= getMaxL1MsgFee(), "MAX_L1_MSG_FEE_EXCEEDED");
uint256 nonce = l1ToL2MessageNonce();
NamedStorage.setUintValue(L1L2_MESSAGE_NONCE_TAG, nonce + 1);
emit LogMessageToL2(msg.sender, toAddress, selector, payload, nonce, msg.value);
bytes32 msgHash = getL1ToL2MsgHash(toAddress, selector, payload, nonce);
l1ToL2Messages()[msgHash] = msg.value + 1;
return (msgHash, nonce);
}
Vulnerability Details
Users can deposit their NFTs into the escrow contract with a very low gas fee (e.g., 1-10 wei).
The L2 message might never be processed due to the low gas fee, as nodes are unlikely to pick up such transactions.
The NFTs become stuck in the escrow contract, as the L2 side never receives the message to complete the bridge process.
There's no apparent mechanism to retrieve the NFTs if the L2 message fails.
Impact
Users can potentially lose access to their NFTs indefinitely if they provide an insufficient gas fee.
Tools Used
Manual Review
Recommendations
1.) Implement an higher minimum gas fee
2.) Add a reclaim mechanism: Implement a function that allows users to reclaim their NFTs after a certain period if the L2 transaction hasn't been processed.
uint256 constant MIN_GAS_FEE = 1e15;
require(msg.value >= MIN_GAS_FEE, "Insufficient gas fee");