First Flight #11: Snek-Raffle

Beginner FriendlyFoundryNFT
100 EXP
Ended
Submission Details
Severity: medium
Valid

LEGEND_SNEK_URI lacks metadata and the `image` attribute points to the wrong SNEK's image URI. Can limit their value, marketability, and overall appeal.

https://github.com/Cyfrin/2024-03-snek-raffle/blob/6b12b719f5d8587f9bbbf3b029954f23a5a38704/contracts/snek_raffle.vy#L56

Summary

LEGEND_SNEK_URI (Cosmic) lacks essential metadata, such as description, attributes, and value. The only available metadata is the image uri, which unfortunately points to the wrong snek image URI (RARE aka JUNGLE SNEK). This oversight raises significant concerns regarding its accuracy and potential impact, potentially diminishing its value and credibility within the market.

Vulnerability Details

Please review Line 56, and copy the URI provided. Then, proceed to your web browser, paste the URI into the address bar, and press Enter.

.
.
.
## Snek NFT Stats
COMMON_SNEK_URI: public(constant(String[53])) = "ipfs://QmSQcYNrMGo5ZuGm1PqYtktvg1tWKGR7PJ9hQosKqMz2nD"
RARE_SNEK_URI: public(constant(String[53])) = "ipfs://QmZit9nbdhJsRTt3JBQN458dfZ1i6LR3iPGxGQwq34Li4a"
## Actually pointing to `RARE_SNEK_IMAGE_URI`
LEGEND_SNEK_URI: public(constant(String[53])) = "ipfs://QmRujARrkux8nsUG8BzXJa8TiDyz5sDJnVKDqrk3LLsKLX"
---------------------------------------------------------------^
.
.
.

Impact

  • Although the ownership of the NFT remains intact, it is concerning that it still points to the wrong SNEK's image URI. This misuse also constitutes an abuse of the Protocol's RARITY mechanism. Winners may mistakenly believe they have acquired a RARE aka Jungle SNEK's NFT.

  • While NFTs can technically be created without metadata, doing so can severely limit their value, marketability, and overall appeal. Metadata plays a pivotal role in providing context, enriching user experience, and ensuring the legal and ethical dimensions of NFT ownership. Both creators and collectors should prioritize the inclusion of comprehensive metadata when creating or acquiring NFTs.

Tools Used

Manual Review, Pytest.

Recommendations

  1. Update LEGEND_SNEK_URI: The simplest solution is to update the LEGEND_SNEK_URI constant variable with the correct URI pointing to its accurate JSON metadata.

  2. Upload and Pin JSON Metadata File: Create a JSON metadata file and upload it to any IPFS URI Pinning Service Provider's chain such as PINATA, Filecoin, NFT.Storage, Arweave, IPFS Cluster, Fleek, etc. For visual guidance on how to upload and pin NFT URIs to these services, you can onboard to Cyfrin Updraft.

  3. Implement Vyper Mimic: Mimic Cyfrin's HH & Foundry NFT course by implementing it in Vyper. This method offloads heavy computation related to NFT URI conversion by processing the URI off-chain. You can then arrive at a final base64 encoded URI and set it back to the LEGEND_SNEK_URI. Remember if you wanna trade/sell your NFT then you would need to pin/upload (recognize NFT tokenID) your final NFT URI to an NFT Trading Platform.

    • contracts/snek_raffle.vy

    .
    .
    .

    ## Snek NFT Stats
    COMMON_SNEK_URI: public(constant(String[53])) = "ipfs://QmSQcYNrMGo5ZuGm1PqYtktvg1tWKGR7PJ9hQosKqMz2nD"
    RARE_SNEK_URI: public(constant(String[53])) = "ipfs://QmZit9nbdhJsRTt3JBQN458dfZ1i6LR3iPGxGQwq34Li4a"
    - LEGEND_SNEK_URI: public(constant(String[53])) = "ipfs://QmRujARrkux8nsUG8BzXJa8TiDyz5sDJnVKDqrk3LLsKLX"
    COMMON_RARITY: public(constant(uint256)) = 70

    .
    .
    .

    - rarityToTokenURI: public(HashMap[uint256, String[53]])
    + rarityToTokenURI: public(HashMap[uint256, String[400]])
    tokenIdToRarity: public(HashMap[uint256, uint256])

    .
    .
    .

    # Constructor
    @deploy
    @payable
    def __init__(
    subscription_id: uint64,
    gas_lane: bytes32, # keyHash
    entrance_fee: uint256,
    vrf_coordinator_v2: address,
    + _legend_snek_uri: String[400]
    ):
    ERC721.__init__("Snek Raffle", "SNEK", "", "snek raffle", "v0.0.1")
    SUBSCRIPTION_ID = subscription_id
    GAS_LANE = gas_lane
    ENTRANCE_FEE = entrance_fee
  • After Updating snek_raffle.vy Just config and test configs like below and execute the test given below...

    • tests/conftest.py

    import boa
    import pytest
    + import base64

    VRF_COORDINATOR_LOCATION = "./contracts/test/VRFCoordinatorV2Mock.vy"
    RAFFLE_LOCATION = "./contracts/snek_raffle.vy"
    BASE_FEE = 1_00000000000000000 # 0.1
    GAS_PRICE_LINK = 1_000000000 # Some value calculated depending on the Layer 1 cost and Link. This is 1e9
    RAFFLE_ENTRANCE_FEE = 1_000_000_000_000_000_000
    + BASE_URI = "data:application/json;base64," # Base URI for Legend JSON URI

    .
    .
    .

    + @pytest.fixture
    + def legend_uri_json():
    + """Loads legend uri json data and converts to base64 encoded uri faciliated by `base64` standard library, string <-Conversion-> bytes methods `encode` & `decode`."""
    + # read file with alias file
    + with open("./img/snake-uris/legend-snek.json", "r") as file:
    + # storing file data in a container/variable called `RAW_FILE`
    + RAW_FILE = file.read()

    + # Strings Concatenation using `+` operator.
    + BASE_64_URI = BASE_URI + base64.b64encode(RAW_FILE.encode("utf-8")).decode("utf-8")

    + # closing file aka stop read.
    + file.close()

    + # returns converted data (off-chain computed Legend JSON URI)
    + return BASE_64_URI

    .
    .
    .

Mitigation PoC:

tests/snek_raffle_test.py: Put the following code at very bottom

<details>

<summary>Poc Off-chain URI Computation</summary>

def test_checks_rewarded_nft_(raffle_boa, vrf_coordinator_boa, entrance_fee):
boa.env.set_balance(USER, STARTING_BALANCE)
with boa.env.prank(USER):
raffle_boa.enter_raffle(value=entrance_fee)

boa.env.time_travel(seconds=INTERVAL + 1)

raffle_boa.request_raffle_winner()

# Normally we need to get the requestID, but our mock ignores that
vrf_coordinator_boa.fulfillRandomWords(0, raffle_boa.address)

recent_winner = raffle_boa.get_recent_winner()
winner_balance = boa.env.get_balance(recent_winner)

print("Recent Winner: ", recent_winner)
print("Winner Balance: ", winner_balance)
print("Winner NFT: ", raffle_boa.tokenURI(0))
  • Now Open your bash terminal and execute following command... (Make sure you're on your chosen virtual environment(Vyper))

pytest -v tests/snek_raffle_test.py::test_checks_rewarded_nft_ -s
  • Output:

==================================================================== test session starts =====================================================================
platform linux -- Python 3.10.12, pytest-8.0.2, pluggy-1.4.0 -- /home/theirrationalone/vyperenv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase(PosixPath('/home/theirrationalone/first-flights/2024-03-snek-raffle/.hypothesis/examples'))
rootdir: /home/theirrationalone/first-flights/2024-03-snek-raffle
plugins: titanoboa-0.1.8, cov-4.1.0, hypothesis-6.98.17
collected 1 item

tests/snek_raffle_test.py::test_checks_rewarded_nft_ Recent Winner: 0xd13f0Bd22AFF8176761AEFBfC052a7490bDe268E
Winner Balance: 1000000000000000000
Winner NFT(URI): data:application/json;base64,ewogICAgIm5hbWUiOiAiQ29zbWljIFNuZWsiLAogICAgImRlc2NyaXB0aW9uIjogIkFuIGFkb3JhYmxlIGxlZ2VuZGFyeSBjb3NtaWMgc25layEiLAogICAgImltYWdlIjogImlwZnM6Ly9RbVF1b1M3eTRja003eHZnMWM3aGUxcEVvellmYXBSREpwdVhkWkdaS0t4Z3JNIiwKICAgICJhdHRyaWJ1dGVzIjogWwogICAgICAgIHsKICAgICAgICAgICAgInRyYWl0X3R5cGUiOiAiY3V0ZW5lc3MiLAogICAgICAgICAgICAidmFsdWUiOiAxMDAKICAgICAgICB9CiAgICBdCn0=
PASSED

===================================================================== 1 passed in 1.56s ======================================================================
  • You can conveniently copy the NFT URI of the winner from the logs and paste it into your browser to view the associated metadata. Ensure that you have IPFS installed; otherwise, your browser may prompt you to suggest its installation. Simply follow the browser's instructions to proceed.

</details>

Updates

Lead Judging Commences

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

LEGEND_SNEK_URI is wrong

Support

FAQs

Can’t find an answer? Join our Discord or follow us on Twitter.

Cyfrin
Updraft
CodeHawks
Solodit
Resources