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 about 1 year 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.

Give us feedback!