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

Incomplete ERC1155 Metadata Retrieval and Base URI Handling Vulnerability

Summary

The TokenUtil library has incomplete functionality for retrieving metadata from ERC1155 tokens and potential issues in handling base URI retrieval using low-level assembly code. These vulnerabilities can lead to missing or incorrect metadata, impacting the utility and reliability of the library.

Vulnerability Details

1.Incomplete ERC1155 Metadata Retrieval:

  • Description: The erc1155Metadata function in the TokenUtil library always returns an empty string, failing to retrieve any metadata for ERC1155 tokens.

2.Base URI Handling in _callBaseUri:

  • Description: The _callBaseUri function uses low-level assembly to call _baseUri or baseUri functions. If the return value is not handled correctly, it may incorrectly assume that the base URI is not present.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import "openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import "openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol";
import "openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import "../src/token/TokenUtil.sol";
contract TokenUtilTest is Test {
address erc721Contract;
address erc1155Contract;
function setUp() public {
// Deploy mock contracts for ERC721 and ERC1155
erc721Contract = address(new MockERC721());
erc1155Contract = address(new MockERC1155());
}
function testERC1155Metadata() public {
// Scenario Incomplete ERC1155 Metadata Functionality
string memory metadata = TokenUtil.erc1155Metadata(erc1155Contract);
assertEq(metadata, "", "Metadata should be empty");
}
function testCallBaseUriReturnValue() public {
// Scenario Handling Return Values ​​in _callBaseUri
(bool success, string memory baseUri) = TokenUtil._callBaseUri(address(new MockERC721WithValidBaseUri()));
assertTrue(success, "Base URI call should succeed");
assertEq(baseUri, "https://example.com/", "Base URI should be correctly returned");
}
}
contract MockERC721 is IERC721, IERC721Metadata {
function name() external pure override returns (string memory) {
return "MockERC721";
}
function symbol() external pure override returns (string memory) {
return "M721";
}
function tokenURI(uint256 tokenId) external pure override returns (string memory) {
return string(abi.encodePacked("https://example.com/", uint2str(tokenId)));
}
function balanceOf(address /*owner*/) external pure override returns (uint256) {
return 1;
}
function ownerOf(uint256 /*tokenId*/) external pure override returns (address) {
return address(0);
}
function approve(address /*to*/, uint256 /*tokenId*/) external pure override {}
function getApproved(uint256 /*tokenId*/) external pure override returns (address) {
return address(0);
}
function setApprovalForAll(address /*operator*/, bool /*approved*/) external pure override {}
function isApprovedForAll(address /*owner*/, address /*operator*/) external pure override returns (bool) {
return false;
}
function transferFrom(address /*from*/, address /*to*/, uint256 /*tokenId*/) external pure override {}
function safeTransferFrom(address /*from*/, address /*to*/, uint256 /*tokenId*/) external pure override {}
function safeTransferFrom(address /*from*/, address /*to*/, uint256 /*tokenId*/, bytes calldata /*data*/) external pure override {}
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId;
}
function uint2str(uint256 _i) internal pure returns (string memory) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint256 k = len;
while (_i != 0) {
k = k - 1;
uint8 temp = (48 + uint8(_i - _i / 10 * 10));
bytes1 b1 = bytes1(temp);
bstr[k] = b1;
_i /= 10;
}
return string(bstr);
}
}
contract MockERC1155 is IERC1155, IERC1155MetadataURI {
function uri(uint256 /*id*/) external pure override returns (string memory) {
return "";
}
function balanceOf(address /*account*/, uint256 /*id*/) external pure override returns (uint256) {
return 1;
}
function balanceOfBatch(address[] calldata accounts, uint256[] calldata /*ids*/) external pure override returns (uint256[] memory) {
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = 1;
}
return batchBalances;
}
function setApprovalForAll(address /*operator*/, bool /*approved*/) external pure override {}
function isApprovedForAll(address /*account*/, address /*operator*/) external pure override returns (bool) {
return false;
}
function safeTransferFrom(address /*from*/, address /*to*/, uint256 /*id*/, uint256 /*amount*/, bytes calldata /*data*/) external pure override {}
function safeBatchTransferFrom(address /*from*/, address /*to*/, uint256[] calldata /*ids*/, uint256[] calldata /*amounts*/, bytes calldata /*data*/) external pure override {}
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return interfaceId == type(IERC1155).interfaceId || interfaceId == type(IERC1155MetadataURI).interfaceId;
}
}
contract MockERC721WithDifferentBaseUri {
function baseURI() external pure returns (string memory) {
return "https://different.com/";
}
}
contract MockERC721WithValidBaseUri {
function _baseUri() external pure returns (string memory) {
return "https://example.com/";
}
}

forge test --match-path test/TokenUtilTest.t.sol -vvvv
[⠊] Compiling...
No files changed, compilation skipped

Ran 2 tests for test/TokenUtilTest.t.sol:TokenUtilTest
[PASS] testCallBaseUriReturnValue() (gas: 82245)
Traces:
[82245] TokenUtilTest::testCallBaseUriReturnValue()
├─ [47299] → new MockERC721WithValidBaseUri@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a
│ └─ ← [Return] 236 bytes of code
├─ [439] MockERC721WithValidBaseUri::_baseUri() [staticcall]
│ └─ ← [Return] "https://example.com/"
└─ ← [Stop]

[PASS] testERC1155Metadata() (gas: 941)
Traces:
[941] TokenUtilTest::testERC1155Metadata()
└─ ← [Stop]

Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 971.60µs (283.10µs CPU time)

Ran 1 test suite in 891.69ms (971.60µs CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

Impact

  • Users cannot retrieve metadata for ERC1155 tokens, limiting the functionality and utility of the library. This can affect applications relying on this metadata for display or processing purposes.

  • Incorrect handling of the base URI can lead to missing or incorrect metadata. This can cause issues in applications that depend on accurate metadata for ERC721 tokens.

Tools Used

  • Manual review

  • Foundry

Recommendations

  • Implement the erc1155Metadata function to properly retrieve and return metadata for ERC1155 tokens. Consider using the IERC1155MetadataURI interface to fetch the URI for a sample token and extract the base URI.

function erc1155Metadata(address collection) internal view returns (string memory) {
bool supportsMetadata = ERC165Checker.supportsInterface(
collection,
type(IERC1155MetadataURI).interfaceId
);
if (!supportsMetadata) {
return "";
} else {
// Fetch URI for a sample token ID (e.g., 1) and extract base URI
return IERC1155MetadataURI(collection).uri(1);
}
}
  • Ensure that the return value is correctly decoded and handled. Avoid relying solely on the return size and value checks.

function _callBaseUri(addresscollection) internal view returns (bool, string memory) {
bool success;
bytes memory ret;
bytes[2] memory encodedSignatures = [
abi.encodeWithSignature("_baseUri()"),
abi.encodeWithSignature("baseUri()")
];
for (uint256 i = 0; i < 2; i++) {
bytes memory encodedParams = encodedSignatures[i];
(success, ret) = collection.staticcall(encodedParams);
if (success && ret.length > 0) {
return (true, abi.decode(ret, (string)));
}
}
return (false, "");
}
Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
Assigned finding tags:

invalid-ERC1155-not-in-scope

```compatibilities: Blockchains: - Ethereum/Starknet Tokens: - [ERC721](www.tokenstandard.com) ``` ``` 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(); } … } ```

Support

FAQs

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