Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

The art paintings are not random distributed in `MondrianWallet::tokenURI`

Summary

The ModrianWallet::tokenURI function returns one of 4 random Mondrian art paintings. Each painting should be random distributed, but this is not implemented correctly.

Vulnerability Details

The ModrianWallet::tokenURI function distributes four art URIs. In the documentation is written:

You'll see the tokenURI function returns one of 4 random Mondrian art paintings. Each should have equal distribution and be random.

But the function doesn't distribute in a random way these 4 Mondrian art paintings:

function tokenURI(uint256 tokenId) public view override returns (string memory) {
if (ownerOf(tokenId) == address(0)) {
revert MondrainWallet__InvalidTokenId();
}
@> uint256 modNumber = tokenId % 10;
if (modNumber == 0) {
return ART_ONE;
} else if (modNumber == 1) {
return ART_TWO;
} else if (modNumber == 2) {
return ART_THREE;
} else {
return ART_FOUR;
}
}

The function distributes the tokenURI based on modNumber. The modNumber is calculated by tokenId % 10. This means the distribution is based on the input argument tokenId. If the user calls twice the function, the user will receive the same tokenURI. If the distribution should be random, that means by every call to the function, the function should return different result (tokenURI).

Impact

The following test function shows that Bob calls twice the MondrianWallet::tokenURI function with the same tokenURI. In that case the function should return two different results, but due to the incorrect implementation, the function returns the same result in the both calls.

it("call tokenURI twice", async function () {
let uri1
let uri2
const bob = "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199"
await mondrianWallet.connect(await ethers.getSigner(bob)).mintTo(bob, 0)
uri1 = await mondrianWallet.connect(await ethers.getSigner(bob)).tokenURI(0)
uri2 = await mondrianWallet.connect(await ethers.getSigner(bob)).tokenURI(0)
expect(uri1).to.equal(uri2)
});

Tools Used

Manual Review, Hardhat

Recommendations

The most appropriate solution for random numbers is Chainlink VRF. But Chainlink does not provide its VRF v2 service on the zkSync rollup: https://docs.chain.link/vrf/v2/subscription/supported-networks.
Therefore, I suggest to generate random number off-chain and then use it in the MondrianWallet::tokenURI function instead of tokenId.

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

NFTs are not random

Support

FAQs

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