Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

Current implementation does not allow all items within a collection to be sold

Description and impact

Organizer execute FestivalPass.createMemorabiliaCollection and set maxSupply as 3

Users will execute FestivalPass.redeemMemorabilia to buy the items within the collection but only the 2/3 items can be bought
Whent the 3rd user tries to buy, it will revert with Collection sold out even if there is 1 more item to available in the collection

Recommended mitigation

There are 2 different modifications that can solve this problem .

( 1 )

Within createMemorabiliaCollection

collections[collectionId] = MemorabiliaCollection({
name: name,
baseUri: baseUri,
priceInBeat: priceInBeat,
maxSupply: maxSupply,
currentItemId: 1, // Start item IDs at 1
isActive: activateNow
});

to

collections[collectionId] = MemorabiliaCollection({
name: name,
baseUri: baseUri,
priceInBeat: priceInBeat,
maxSupply: maxSupply,
currentItemId: 0,
isActive: activateNow
});

( 2 )

Within redeemMemorabilia

require(collection.currentItemId < collection.maxSupply, "Collection sold out");

to

require(collection.currentItemId <= collection.maxSupply, "Collection sold out");

PoC

How to execute the PoC ?

  • Create a Foundry project with everything needed .

  • Add the PoC in test/TEST.t.sol .

  • Execute the PoC using the command forge test --match-path test/TEST.t.sol --match-test test____ -vv .

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Test} from "lib/forge-std/src/Test.sol";
import {console2} from "lib/forge-std/src/console2.sol";
import {BeatToken} from "src/BeatToken.sol";
import {FestivalPass} from "src/FestivalPass.sol";
contract Tests is Test {
struct Smart_Contracts {
BeatToken BeatToken;
FestivalPass FestivalPass;
}
Smart_Contracts SC;
struct User {
address A;
uint256 PK;
}
struct Users {
User O;
User U1;
User U2;
User U3;
User U4;
User U5;
}
Users U;
mapping(address => string) private name;
function test____() external {
CREATE_USER(U.O, "O");
CREATE_USER(U.U1, "U1");
CREATE_USER(U.U2, "U2");
CREATE_USER(U.U3, "U3");
CREATE_USER(U.U4, "U4");
CREATE_USER(U.U5, "U5");
// BeatToken ` constructor ` //
{
vm.startPrank(U.O.A);
SC.BeatToken = new BeatToken();
vm.stopPrank();
}
SET_NAME_FOR_ADDRESS(
address(SC.BeatToken),
"BeatToken"
);
// FestivalPass ` constructor ` //
{
vm.startPrank(U.O.A);
SC.FestivalPass = new FestivalPass(
address(SC.BeatToken),
U.O.A
);
vm.stopPrank();
}
SET_NAME_FOR_ADDRESS(
address(SC.FestivalPass),
"FestivalPass"
);
// BeatToken.setFestivalContract //
{
vm.startPrank(U.O.A);
SC.BeatToken.setFestivalContract(
address(SC.FestivalPass)
);
vm.stopPrank();
}
// FestivalPass.configurePass //
{
vm.startPrank(U.O.A);
SC.FestivalPass.configurePass(
1,
0.1 ether,
1000
);
vm.stopPrank();
}
// FestivalPass.configurePass //
{
vm.startPrank(U.O.A);
SC.FestivalPass.configurePass(
2,
0.5 ether,
100
);
vm.stopPrank();
}
// FestivalPass.configurePass //
{
vm.startPrank(U.O.A);
SC.FestivalPass.configurePass(
3,
1 ether,
10
);
vm.stopPrank();
}
{
AIRDROP_ETH(
U.U1.A,
1 ether
);
}
// FestivalPass.buyPass //
{
vm.startPrank(U.U1.A);
SC.FestivalPass.buyPass{value: 1 ether}(
3
);
vm.stopPrank();
}
// FestivalPass.createMemorabiliaCollection //
{
vm.startPrank(U.O.A);
SC.FestivalPass.createMemorabiliaCollection(
"Limited Edition Poster",
"https://example.com",
1 * 10 ** I____ERC20_decimals(address(SC.BeatToken)).decimals(),
3,
true
);
vm.stopPrank();
}
// FestivalPass.redeemMemorabilia //
{
vm.startPrank(U.U1.A);
SC.FestivalPass.redeemMemorabilia(
100
);
vm.stopPrank();
}
// FestivalPass.redeemMemorabilia //
{
vm.startPrank(U.U1.A);
SC.FestivalPass.redeemMemorabilia(
100
);
vm.stopPrank();
}
// FestivalPass.redeemMemorabilia //
{
vm.startPrank(U.U1.A);
SC.FestivalPass.redeemMemorabilia(
100
);
vm.stopPrank();
}
}
function AIRDROP_ETH(address a, uint256 b) private {
deal(a, b);
}
function CREATE_USER(User storage a, string memory b) private {
(a.A, a.PK) = makeAddrAndKey(b);
SET_NAME_FOR_ADDRESS(a.A, b);
}
function SET_NAME_FOR_ADDRESS(address a, string memory b) private {
name[a] = b;
}
function GET_NAME_FOR_ADDRESS(address a) private view returns (string memory) {
if (bytes(name[a]).length == 0) {
return vm.toString(a);
}
return name[a];
}
function SPACE() private pure {
console2.log("");
}
}
interface I____ERC20_decimals {
function decimals() external view returns (uint8);
}
Updates

Lead Judging Commences

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

Off by one error in redeemMemorabilia

Support

FAQs

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