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

Bad Distribution and Randomness

Summary

The MondrianWallet contract suffers from a vulnerability related to the distribution and randomness of the non-fungible tokens (NFTs) it mints. The randomness is compromised because the token ID is used to assign the art, which is not random and allows users to predict which art they will receive based on the token ID. Additionally, the distribution is flawed because, despite having only four art options, the contract uses the %10 operation, leading to an uneven distribution among the art options.

Vulnerability Details

The vulnerability arises from the tokenURIfunction, which determines the art of the NFT based on the modulo operation tokenId % 10 (each user based on tokenId can predict the ART they will receive, and due to the use of operation %10 the distribution of NFTs will be bad because there are only four types of ART). This operation results in a non-random and bad distribution of NFTs, where distribution is predictable and 30% of tokens are associated with each of the first three art options (ART_ONE, ART_TWO, and ART_THREE each 10%), while 70% of tokens are associated with ART_FOUR.

PoC

To prove the vulnerability in the MondrianWallet contract regarding the poor distribution and randomness of NFTs, we first need to introduce a dummy mint function. This function, defined as mint, will allow minting of NFTs by incrementing a token ID counter. The function will look like this:

uint256 i;
function mint() external {
_mint(msg.sender, i);
i++;
}

Next, we write a test to mint a series of NFTs and verify the assigned art URIs. The test, written in JavaScript using the Hardhat framework, will demonstrate the predictable and uneven distribution of the NFTs. By minting 10 tokens, we will be able to observe that the distribution is not random. Specifically, we can predict which art will be assigned to each token based on the token ID modulo 10. The code for the test is as follows:

it("Distribution and randomness is bad", async function () {
let user1 = await ethers.getSigners();
const numIterations = 10;
for (let i = 0; i < numIterations; i++) {
await mondrianWallet.mint();
}
// Assert the token URIs for the first 10 tokens
assert.equal(await mondrianWallet.tokenURI(0), "ar://jMRC4pksxwYIgi6vIBsMKXh3Sq0dfFFghSEqrchd_nQ");
assert.equal(await mondrianWallet.tokenURI(1), "ar://8NI8_fZSi2JyiqSTkIBDVWRGmHCwqHT0qn4QwF9hnPU");
assert.equal(await mondrianWallet.tokenURI(2), "ar://AVwp_mWsxZO7yZ6Sf3nrsoJhVnJppN02-cbXbFpdOME");
assert.equal(await mondrianWallet.tokenURI(3), "ar://n17SzjtRkcbHWzcPnm0UU6w1Af5N1p0LAcRUMNP-LiM");
assert.equal(await mondrianWallet.tokenURI(4), "ar://n17SzjtRkcbHWzcPnm0UU6w1Af5N1p0LAcRUMNP-LiM");
assert.equal(await mondrianWallet.tokenURI(5), "ar://n17SzjtRkcbHWzcPnm0UU6w1Af5N1p0LAcRUMNP-LiM");
assert.equal(await mondrianWallet.tokenURI(6), "ar://n17SzjtRkcbHWzcPnm0UU6w1Af5N1p0LAcRUMNP-LiM");
assert.equal(await mondrianWallet.tokenURI(7), "ar://n17SzjtRkcbHWzcPnm0UU6w1Af5N1p0LAcRUMNP-LiM");
assert.equal(await mondrianWallet.tokenURI(8), "ar://n17SzjtRkcbHWzcPnm0UU6w1Af5N1p0LAcRUMNP-LiM");
assert.equal(await mondrianWallet.tokenURI(9), "ar://n17SzjtRkcbHWzcPnm0UU6w1Af5N1p0LAcRUMNP-LiM");
});

The analysis of the test results demonstrates that the distribution of art among the NFTs is bad and not random. Based on the modulo arithmetic, tokens that have IDs ending with 3 through 9 are always assigned the same art URI (ART_FOUR), showing a 70% concentration. Meanwhile, the other art options are assigned only 30% of the time collectively (ART_ONE, ART_TWO and ART_THREE each 10%). This predictable and uneven distribution proves that the current implementation of the tokenURI function does not provide a fair or random assignment of art to the NFTs.

Impact

The impact of this vulnerability is significant in terms of user trust and the perceived value of the NFTs. The highly predictable and uneven distribution results in most users receiving the same art option (ART_FOUR), diminishing the rarity and desirability of the NFTs. This unfair assignment could lead to user dissatisfaction and a loss of trust in the protocol, as the NFTs minted do not reflect a fair or random allocation of art, which is essential for maintaining the integrity and appeal of the NFT collection.

Tools Used

  1. Manual code review

  2. Hardhat

Recommendations

To solve the identified vulnerabilities, consider using RANDOMIZER.AI, which provides robust and secure randomness. This solution works on both Ethereum and ZkSync, ensuring a consistent and fair distribution of art assignments. By integrating RANDOMIZER.AI, the assignment of art pieces to tokens can be reliably randomized at the time of minting, improving the overall security and functionality of the NFT distribution process.
To interact with RANDOMIZER.AI, we will add the following interface:

interface IRandomizer {
function request(uint256 callbackGasLimit) external returns (uint256);
function request(uint256 callbackGasLimit, uint256 confirmations) external returns (uint256);
function clientWithdrawTo(address _to, uint256 _amount) external;
}

To improve the distribution and randomness of the assigned art the following code should be added to the contract. The _assignArt function should be called at the time of minting to assign art to the token, ensuring each token receives an art piece immediately upon creation. The assigned art should be stored in a mapping(uint256 => string) called IdAndItsArt, guaranteeing consistent and retrievable art for each token ID. The tokenURI function should be updated to retrieve the assigned art from this mapping, making it deterministic and ensuring it always returns the correct art.

constructor(address entryPoint, address randomizer) Ownable(msg.sender) ERC721("MondrianWallet", "MW") {
i_entryPoint = IEntryPoint(entryPoint);
// The address for randomizer will depend on the network we want to work on.
i_randomizer = IRandomizer(randomizer);
}
IRandomizer private immutable i_randomizer;
mapping(uint256=>string) public IdAndItsArt;
uint256 random;
function _assignArt(uint256 tokenId) internal{
// Request a random value from RANDOMIZER.AI
i_randomizer.request(50000);
// Based on the generated random value, assign an art piece to the tokenId.
if (random == 0) {
IdAndItsArt[tokenId]= ART_ONE;
} else if (random == 1) {
IdAndItsArt[tokenId]= ART_TWO;
} else if (random == 2) {
IdAndItsArt[tokenId]= ART_THREE;
} else {
IdAndItsArt[tokenId]= ART_FOUR;
}
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
if (ownerOf(tokenId) == address(0)) {
revert MondrainWallet__InvalidTokenId();
}
// Return the art assigned to the tokenId.
return IdAndItsArt[tokenId];
}
function randomizerCallback(uint256 _id, bytes32 _value) external {
//Callback can only be called by randomizer
require(msg.sender == address(i_randomizer), "Caller not Randomizer");
// Calculate the random value using modulo 4 to ensure fair distribution
random = uint256(_value) % 4;
}
// Withdraw funds deposited with RANDOMIZER.AI
function randomizerWithdraw(uint256 amount) external onlyOwner {
i_randomizer.clientWithdrawTo(msg.sender, amount);
}

By generating a random value and performing an operation %4 on the random value, we solved the issue of bad randomness and uneven distribution of art among NFTs. This ensures a more secure, fair, and predictable assignment of art to each minted token.

Updates

Lead Judging Commences

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

NFT's should have equal distribution

NFTs are not random

Support

FAQs

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