From 4e773aeb4da63d65a42bb14fa07bfc4ca9d465ff Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Mon, 22 May 2023 12:10:14 +0100 Subject: [PATCH 01/54] BrainPass Nft Contract Added --- src/BrainPass/BrainPass.sol | 257 ++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 src/BrainPass/BrainPass.sol diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol new file mode 100644 index 0000000..28f2133 --- /dev/null +++ b/src/BrainPass/BrainPass.sol @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-contracts/contracts/utils/Counters.sol"; +import "openzeppelin-contracts/contracts/access/Ownable.sol"; +import "openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; + + +/// @title BRAIN Pass NFT +/// @author Oleanji +/// @notice A pass for IQ Wiki Editors + +contract BrainPassCollectibles is ERC721, Ownable { + /// ----------------------------------------------------------------------- + /// Errors + /// ----------------------------------------------------------------------- + + error MintingPaymentFailed(); + + /// ----------------------------------------------------------------------- + /// Inheritances + /// ----------------------------------------------------------------------- + using SafeMath for uint256; + using Counters for Counters.Counter; + + /// ----------------------------------------------------------------------- + /// Structs + /// ----------------------------------------------------------------------- + struct UserPassItem { + uint256 _tokenId; + uint256 _passId; + uint256 _startTimestamp; + uint256 _endTimestamp; + } + + struct PassType { + uint256 _passId; + string _passSlug; + uint256 _pricePerDays; + string _tokenURI; + uint256 _maxTokens; + uint256 _discount; + uint256 _lastTokenIdMinted; + } + + /// ----------------------------------------------------------------------- + /// Mappings + /// ----------------------------------------------------------------------- + mapping(uint256 => PassType) public passTypes; + mapping(address => mapping(uint256 => UserPassItem)) + public addressToNFTPass; + + mapping(address => mapping(uint256 => bool)) public addressToPassId; + + /// ----------------------------------------------------------------------- + /// Constant + /// ----------------------------------------------------------------------- + IERC20 public IqToken; + + /// ----------------------------------------------------------------------- + /// Variables + /// ----------------------------------------------------------------------- + string public baseTokenURI; + Counters.Counter private _tokenIds; + + /// ----------------------------------------------------------------------- + /// Constructor + /// ----------------------------------------------------------------------- + constructor(address _IqAddr) ERC721("BRAINY EDITOR PASS", "BEP") { + IqToken = IERC20(_IqAddr); + } + + function _baseURI() internal view virtual override returns (string memory) { + return baseTokenURI; + } + + function setBaseURI(string memory _baseTokenURI) public onlyOwner { + baseTokenURI = _baseTokenURI; + } + + function addPassType( + uint256 _passId, + uint256 _pricePerDays, + string memory _tokenURI, + string memory _passSlug, + uint256 _maxTokens, + uint256 _discount + ) public onlyOwner { + require(bytes(_tokenURI).length > 0, "Invalid token URI"); + require(_maxTokens > 0, "Invalid max tokens"); + + passTypes[_passId] = PassType( + _passId, + _passSlug, + _pricePerDays, + _tokenURI, + _maxTokens, + _discount, + 0 + ); + + emit NewPassAdded(_passId, _passSlug, _maxTokens, _pricePerDays); + } + + function mintNFT( + uint256 _passIdNum, + uint256 _startTimestamp, + uint256 _endTimestamp + ) public payable { + require( + addressToPassId[msg.sender][_passIdNum] != true, + "Max NFTs per address reached" + ); + + PassType storage passType = passTypes[_passIdNum]; + + require(passType._maxTokens != 0, "Pass type not found"); + + require( + passType._lastTokenIdMinted.add(1) <= passType._maxTokens, + "Max supply reached" + ); + + uint256 price = calculatePrice( + _passIdNum, + _startTimestamp, + _endTimestamp + ); + require(msg.value >= price, "Not enough payment token"); + + uint256 tokenId = passType._lastTokenIdMinted; + bool success = IqToken.transfer(owner(), price); + if (!success) revert MintingPaymentFailed(); + + setBaseURI(passType._tokenURI); + _safeMint(msg.sender, tokenId); + + UserPassItem memory purchase = UserPassItem( + tokenId, + _passIdNum, + _startTimestamp, + _endTimestamp + ); + addressToPassId[msg.sender][_passIdNum] = true; + addressToNFTPass[msg.sender][tokenId] = purchase; + passType._lastTokenIdMinted = tokenId += 1; + + emit BrainPassBought( + msg.sender, + tokenId, + _startTimestamp, + _endTimestamp + ); + } + + function calculatePrice( + uint256 _passIdNum, + uint256 _startTimestamp, + uint256 _endTimestamp + ) public view returns (uint256) { + PassType memory passType = passTypes[_passIdNum]; + uint256 duration = _endTimestamp.sub(_startTimestamp); + uint256 totalPrice = duration.mul(passType._pricePerDays); + if (passType._discount > 0) { + uint256 discountAmount = totalPrice.mul(passType._discount).div( + 100 + ); + totalPrice = totalPrice.sub(discountAmount); + } + + return totalPrice; + } + + function increasePassTime( + uint256 tokenId, + uint256 _passIdNum, + uint _newStartTime, + uint256 _newEndTime + ) public payable { + require( + msg.sender == ownerOf(tokenId), + "You cannot increase the time for an NFT you don't own" + ); + + UserPassItem storage pass = addressToNFTPass[ownerOf(tokenId)][tokenId]; + uint256 price = calculatePrice(_passIdNum, _newStartTime, _newEndTime); + require(msg.value >= price, "Not enough payment token"); + + pass._startTimestamp = _newStartTime; + pass._endTimestamp = _newEndTime; + + emit TimeIncreased( + msg.sender, + tokenId, + pass._startTimestamp, + pass._endTimestamp + ); + } + + function getUserNFTs( + address _user, + uint _passIdNum + ) public view returns (UserPassItem[] memory) { + uint256 userTokenCount = balanceOf(_user); + PassType memory passType = passTypes[_passIdNum]; + UserPassItem[] memory userTokens = new UserPassItem[](userTokenCount); + uint256 counter = 0; + for (uint256 i = 0; i < passType._maxTokens; i++) { + if (ownerOf(i) == _user) { + userTokens[counter] = addressToNFTPass[msg.sender][i]; + counter++; + } + } + return userTokens; + } + + function getPassType( + uint256 _passId + ) public view returns (PassType memory) { + PassType memory passType = passTypes[_passId]; + return (passType); + } + + function withdraw() public payable onlyOwner { + uint256 balance = address(this).balance; + require(balance > 0, "No ether left to withdraw"); + (bool success, ) = (msg.sender).call{value: balance}(""); + require(success, "Transfer failed."); + } + + /// ----------------------------------------------------------------------- + /// Events + /// ----------------------------------------------------------------------- + + event BrainPassBought( + address indexed _owner, + uint256 _tokenId, + uint256 _startTimestamp, + uint256 _endTimestamp + ); + + event TimeIncreased( + address indexed _owner, + uint256 _tokenId, + uint256 _startTimestamp, + uint256 _newEndTimestamp + ); + + event NewPassAdded( + uint256 indexed _passId, + string _passSlug, + uint256 _maxtokens, + uint256 _pricePerDays + ); +} From c4aec5a54399866be182aa78f21c0f2c6a4137fe Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Mon, 22 May 2023 12:25:49 +0100 Subject: [PATCH 02/54] addedexpalainable comments to functions --- src/BrainPass/BrainPass.sol | 191 ++++++++++++++++++------------------ 1 file changed, 98 insertions(+), 93 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 28f2133..1b7c63d 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -2,17 +2,19 @@ pragma solidity ^0.8.13; import "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; -import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-contracts/contracts/utils/Counters.sol"; -import "openzeppelin-contracts/contracts/access/Ownable.sol"; -import "openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; +import {Counters} from "openzeppelin-contracts/contracts/utils/Counters.sol"; +import {Owned} from "solmate/auth/Owned.sol"; +import {SafeMath} from "openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; +interface IERC20 { + function transfer(address to, uint256 amount) external returns (bool); +} /// @title BRAIN Pass NFT /// @author Oleanji /// @notice A pass for IQ Wiki Editors -contract BrainPassCollectibles is ERC721, Ownable { +contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Errors /// ----------------------------------------------------------------------- @@ -29,20 +31,20 @@ contract BrainPassCollectibles is ERC721, Ownable { /// Structs /// ----------------------------------------------------------------------- struct UserPassItem { - uint256 _tokenId; - uint256 _passId; - uint256 _startTimestamp; - uint256 _endTimestamp; + uint256 tokenId; + uint256 passId; + uint256 startTimestamp; + uint256 endTimestamp; } struct PassType { - uint256 _passId; - string _passSlug; - uint256 _pricePerDays; - string _tokenURI; - uint256 _maxTokens; - uint256 _discount; - uint256 _lastTokenIdMinted; + uint256 passId; + string passSlug; + uint256 pricePerDays; + string tokenURI; + uint256 maxTokens; + uint256 discount; + uint256 lastTokenIdMinted; } /// ----------------------------------------------------------------------- @@ -51,7 +53,6 @@ contract BrainPassCollectibles is ERC721, Ownable { mapping(uint256 => PassType) public passTypes; mapping(address => mapping(uint256 => UserPassItem)) public addressToNFTPass; - mapping(address => mapping(uint256 => bool)) public addressToPassId; /// ----------------------------------------------------------------------- @@ -63,121 +64,120 @@ contract BrainPassCollectibles is ERC721, Ownable { /// Variables /// ----------------------------------------------------------------------- string public baseTokenURI; - Counters.Counter private _tokenIds; + Counters.Counter private tokenIds; /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- - constructor(address _IqAddr) ERC721("BRAINY EDITOR PASS", "BEP") { - IqToken = IERC20(_IqAddr); + constructor( + address IqAddr + ) ERC721("BRAINY EDITOR PASS", "BEP") Owned(msg.sender) { + IqToken = IERC20(IqAddr); } - function _baseURI() internal view virtual override returns (string memory) { + function baseURI() internal view virtual returns (string memory) { return baseTokenURI; } - function setBaseURI(string memory _baseTokenURI) public onlyOwner { - baseTokenURI = _baseTokenURI; + function setBaseURI(string memory tokenURI) public onlyOwner { + baseTokenURI = tokenURI; } + /// @notice Add a new Pass Type + /// @param passId and others are the details needed for a passType function addPassType( - uint256 _passId, - uint256 _pricePerDays, - string memory _tokenURI, - string memory _passSlug, - uint256 _maxTokens, - uint256 _discount + uint256 passId, + uint256 pricePerDays, + string memory tokenURI, + string memory passSlug, + uint256 maxTokens, + uint256 discount ) public onlyOwner { - require(bytes(_tokenURI).length > 0, "Invalid token URI"); - require(_maxTokens > 0, "Invalid max tokens"); - - passTypes[_passId] = PassType( - _passId, - _passSlug, - _pricePerDays, - _tokenURI, - _maxTokens, - _discount, + require(bytes(tokenURI).length > 0, "Invalid token URI"); + require(maxTokens > 0, "Invalid max tokens"); + + passTypes[passId] = PassType( + passId, + passSlug, + pricePerDays, + tokenURI, + maxTokens, + discount, 0 ); - emit NewPassAdded(_passId, _passSlug, _maxTokens, _pricePerDays); + emit NewPassAdded(passId, passSlug, maxTokens, pricePerDays); } + /// @notice Mint and NFT of a particular passtype + /// @param passIdNum The id of the passtype to mint function mintNFT( - uint256 _passIdNum, - uint256 _startTimestamp, - uint256 _endTimestamp + uint256 passIdNum, + uint256 startTimestamp, + uint256 endTimestamp ) public payable { require( - addressToPassId[msg.sender][_passIdNum] != true, + addressToPassId[msg.sender][passIdNum] != true, "Max NFTs per address reached" ); - PassType storage passType = passTypes[_passIdNum]; + PassType storage passType = passTypes[passIdNum]; - require(passType._maxTokens != 0, "Pass type not found"); + require(passType.maxTokens != 0, "Pass type not found"); require( - passType._lastTokenIdMinted.add(1) <= passType._maxTokens, + passType.lastTokenIdMinted.add(1) <= passType.maxTokens, "Max supply reached" ); - uint256 price = calculatePrice( - _passIdNum, - _startTimestamp, - _endTimestamp - ); + uint256 price = calculatePrice(passIdNum, startTimestamp, endTimestamp); require(msg.value >= price, "Not enough payment token"); - uint256 tokenId = passType._lastTokenIdMinted; - bool success = IqToken.transfer(owner(), price); + uint256 tokenId = passType.lastTokenIdMinted; + bool success = IqToken.transfer(owner, price); if (!success) revert MintingPaymentFailed(); - setBaseURI(passType._tokenURI); + setBaseURI(passType.tokenURI); _safeMint(msg.sender, tokenId); UserPassItem memory purchase = UserPassItem( tokenId, - _passIdNum, - _startTimestamp, - _endTimestamp + passIdNum, + startTimestamp, + endTimestamp ); - addressToPassId[msg.sender][_passIdNum] = true; + addressToPassId[msg.sender][passIdNum] = true; addressToNFTPass[msg.sender][tokenId] = purchase; - passType._lastTokenIdMinted = tokenId += 1; + passType.lastTokenIdMinted = tokenId += 1; - emit BrainPassBought( - msg.sender, - tokenId, - _startTimestamp, - _endTimestamp - ); + emit BrainPassBought(msg.sender, tokenId, startTimestamp, endTimestamp); } + /// @notice Calculate the price of an Nft + /// @param startTimestamp and endTimestamp are used to calc the price to be paid function calculatePrice( - uint256 _passIdNum, - uint256 _startTimestamp, - uint256 _endTimestamp + uint256 passIdNum, + uint256 startTimestamp, + uint256 endTimestamp ) public view returns (uint256) { - PassType memory passType = passTypes[_passIdNum]; - uint256 duration = _endTimestamp.sub(_startTimestamp); - uint256 totalPrice = duration.mul(passType._pricePerDays); - if (passType._discount > 0) { - uint256 discountAmount = totalPrice.mul(passType._discount).div( - 100 - ); + PassType memory passType = passTypes[passIdNum]; + uint256 duration = endTimestamp.sub(startTimestamp); + uint256 totalPrice = duration.mul(passType.pricePerDays); + if (passType.discount > 0) { + uint256 discountAmount = totalPrice.mul(passType.discount).div(100); totalPrice = totalPrice.sub(discountAmount); } return totalPrice; } + /// @notice Increase the time to hold a PassNft + /// @param tokenId The Id of the NFT whose time is to be increased function increasePassTime( uint256 tokenId, - uint256 _passIdNum, - uint _newStartTime, - uint256 _newEndTime + uint256 passIdNum, + uint newStartTime, + uint256 newEndTime ) public payable { require( msg.sender == ownerOf(tokenId), @@ -185,30 +185,32 @@ contract BrainPassCollectibles is ERC721, Ownable { ); UserPassItem storage pass = addressToNFTPass[ownerOf(tokenId)][tokenId]; - uint256 price = calculatePrice(_passIdNum, _newStartTime, _newEndTime); + uint256 price = calculatePrice(passIdNum, newStartTime, newEndTime); require(msg.value >= price, "Not enough payment token"); - pass._startTimestamp = _newStartTime; - pass._endTimestamp = _newEndTime; + pass.startTimestamp = newStartTime; + pass.endTimestamp = newEndTime; emit TimeIncreased( msg.sender, tokenId, - pass._startTimestamp, - pass._endTimestamp + pass.startTimestamp, + pass.endTimestamp ); } + /// @notice Gets all the NFT owned by an address + /// @param user The address of the user function getUserNFTs( - address _user, - uint _passIdNum + address user, + uint passIdNum ) public view returns (UserPassItem[] memory) { - uint256 userTokenCount = balanceOf(_user); - PassType memory passType = passTypes[_passIdNum]; + uint256 userTokenCount = balanceOf(user); + PassType memory passType = passTypes[passIdNum]; UserPassItem[] memory userTokens = new UserPassItem[](userTokenCount); uint256 counter = 0; - for (uint256 i = 0; i < passType._maxTokens; i++) { - if (ownerOf(i) == _user) { + for (uint256 i = 0; i < passType.maxTokens; i++) { + if (ownerOf(i) == user) { userTokens[counter] = addressToNFTPass[msg.sender][i]; counter++; } @@ -216,13 +218,16 @@ contract BrainPassCollectibles is ERC721, Ownable { return userTokens; } - function getPassType( - uint256 _passId + /// @notice Gets all the details of a passtype + /// @param passId The id of the passtype + function getAllPassType( + uint256 passId ) public view returns (PassType memory) { - PassType memory passType = passTypes[_passId]; + PassType memory passType = passTypes[passId]; return (passType); } + /// @notice Withdraws any amount in the contract function withdraw() public payable onlyOwner { uint256 balance = address(this).balance; require(balance > 0, "No ether left to withdraw"); From c2f67f905293d84bfd1bdb59c38cb1c3ba78aebf Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Mon, 22 May 2023 16:12:27 +0100 Subject: [PATCH 03/54] refined the vars in the brainpass contract --- script/BrainPassMinter.sol | 17 ++++++++++++ src/BrainPass/BrainPass.sol | 53 ++++++++++++++++++++++--------------- test/BrainPass.t.sol | 0 3 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 script/BrainPassMinter.sol create mode 100644 test/BrainPass.t.sol diff --git a/script/BrainPassMinter.sol b/script/BrainPassMinter.sol new file mode 100644 index 0000000..e82dd83 --- /dev/null +++ b/script/BrainPassMinter.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "../lib/forge-std/src/Script.sol"; +import {console} from "../lib/forge-std/src/console.sol"; +import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; + +contract BrainPassMinter is Script { + address constant owner = address(0xaCa39B187352D9805DECEd6E73A3d72ABf86E7A0); + + function run() external { + vm.startBroadcast(); + + + vm.stopBroadcast(); + } +} diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 1b7c63d..e7f6d1c 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -39,8 +39,8 @@ contract BrainPassCollectibles is ERC721, Owned { struct PassType { uint256 passId; - string passSlug; - uint256 pricePerDays; + string name; + uint256 pricePerDay; string tokenURI; uint256 maxTokens; uint256 discount; @@ -87,9 +87,9 @@ contract BrainPassCollectibles is ERC721, Owned { /// @param passId and others are the details needed for a passType function addPassType( uint256 passId, - uint256 pricePerDays, + uint256 pricePerDay, string memory tokenURI, - string memory passSlug, + string memory name, uint256 maxTokens, uint256 discount ) public onlyOwner { @@ -98,30 +98,30 @@ contract BrainPassCollectibles is ERC721, Owned { passTypes[passId] = PassType( passId, - passSlug, - pricePerDays, + name, + pricePerDay, tokenURI, maxTokens, discount, 0 ); - emit NewPassAdded(passId, passSlug, maxTokens, pricePerDays); + emit NewPassAdded(passId, name, maxTokens, pricePerDay); } /// @notice Mint and NFT of a particular passtype - /// @param passIdNum The id of the passtype to mint + /// @param passId The id of the passtype to mint function mintNFT( - uint256 passIdNum, + uint256 passId, uint256 startTimestamp, uint256 endTimestamp ) public payable { require( - addressToPassId[msg.sender][passIdNum] != true, + addressToPassId[msg.sender][passId] != true, "Max NFTs per address reached" ); - PassType storage passType = passTypes[passIdNum]; + PassType storage passType = passTypes[passId]; require(passType.maxTokens != 0, "Pass type not found"); @@ -130,39 +130,47 @@ contract BrainPassCollectibles is ERC721, Owned { "Max supply reached" ); - uint256 price = calculatePrice(passIdNum, startTimestamp, endTimestamp); + uint256 price = calculatePrice(passId, startTimestamp, endTimestamp); require(msg.value >= price, "Not enough payment token"); - uint256 tokenId = passType.lastTokenIdMinted; + uint256 tokenId = passType.lastTokenIdMinted.add(1); bool success = IqToken.transfer(owner, price); if (!success) revert MintingPaymentFailed(); setBaseURI(passType.tokenURI); _safeMint(msg.sender, tokenId); + addressToPassId[msg.sender][passId] = true; + UserPassItem memory purchase = UserPassItem( tokenId, - passIdNum, + passId, startTimestamp, endTimestamp ); - addressToPassId[msg.sender][passIdNum] = true; addressToNFTPass[msg.sender][tokenId] = purchase; - passType.lastTokenIdMinted = tokenId += 1; + passType.lastTokenIdMinted = tokenId; - emit BrainPassBought(msg.sender, tokenId, startTimestamp, endTimestamp); + emit BrainPassBought( + msg.sender, + tokenId, + passId, + startTimestamp, + endTimestamp + ); } /// @notice Calculate the price of an Nft /// @param startTimestamp and endTimestamp are used to calc the price to be paid function calculatePrice( - uint256 passIdNum, + uint256 passId, uint256 startTimestamp, uint256 endTimestamp ) public view returns (uint256) { - PassType memory passType = passTypes[passIdNum]; + PassType memory passType = passTypes[passId]; + //duration in days uint256 duration = endTimestamp.sub(startTimestamp); - uint256 totalPrice = duration.mul(passType.pricePerDays); + uint256 totalPrice = duration.mul(passType.pricePerDay); if (passType.discount > 0) { uint256 discountAmount = totalPrice.mul(passType.discount).div(100); totalPrice = totalPrice.sub(discountAmount); @@ -241,6 +249,7 @@ contract BrainPassCollectibles is ERC721, Owned { event BrainPassBought( address indexed _owner, + uint256 _passId, uint256 _tokenId, uint256 _startTimestamp, uint256 _endTimestamp @@ -255,8 +264,8 @@ contract BrainPassCollectibles is ERC721, Owned { event NewPassAdded( uint256 indexed _passId, - string _passSlug, + string _name, uint256 _maxtokens, - uint256 _pricePerDays + uint256 _pricePerDay ); } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol new file mode 100644 index 0000000..e69de29 From bc3a1cc407aa92290a73f847f93cd1b42bcc81f6 Mon Sep 17 00:00:00 2001 From: Damilare Bakare Date: Mon, 22 May 2023 17:14:02 +0100 Subject: [PATCH 04/54] adds brainpass deployer script --- README.md | 2 +- script/BrainPassDeployer.s.sol | 22 ++++++++++++++++++++++ script/WikiWhitelistValidator.s.sol | 2 -- 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 script/BrainPassDeployer.s.sol diff --git a/README.md b/README.md index 023e9c5..d1aeff6 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,6 @@ Deploy and verify a contract source .env # To deploy and verify our contract -forge script script/WikiNoValidator.s.sol:WikiNoValidator --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_KEY -vvvv --gas-price 60 --legacy +forge script script/BrainPassDeployer.s.sol:BrainPassDeployer --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_KEY -vvvv --gas-price 60 --legacy ``` diff --git a/script/BrainPassDeployer.s.sol b/script/BrainPassDeployer.s.sol new file mode 100644 index 0000000..d897aa7 --- /dev/null +++ b/script/BrainPassDeployer.s.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "../lib/forge-std/src/Script.sol"; +import {console} from "../lib/forge-std/src/console.sol"; + +import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; + + +contract BrainPassDeployer is Script { + address constant owner = address(0xE161eB85f00eC6471E0de06bA1Cfc136C053fFfe); + + function run() external { + vm.startBroadcast(); + + console.log("Deploying Brainpass deployer...."); + BrainPassCollectibles validator = new BrainPassCollectibles(0x5E959c60f86D17fb7D764AB69B654227d464E820); + console.log("Brainpass Deployed", address(validator)); + + vm.stopBroadcast(); + } +} diff --git a/script/WikiWhitelistValidator.s.sol b/script/WikiWhitelistValidator.s.sol index 27ae7b2..11c37f7 100644 --- a/script/WikiWhitelistValidator.s.sol +++ b/script/WikiWhitelistValidator.s.sol @@ -22,10 +22,8 @@ contract WikiWhitelistValidator is Script { validator.whitelistEditor(address(0xF6d9467758C08d05571f1bFa0a03A2286cE1F043)); validator.whitelistEditor(address(0x2fE6aCD015384E1ee5138eF79fe1a434dA8FA12e)); validator.whitelistEditor(address(0xb029c0367CCFeEFBc6D00B4cc22fcbFd6A781F5c)); - validator.whitelistEditor(address(0x9fEAB70f3c4a944B97b7565BAc4991dF5B7A69ff)); validator.whitelistEditor(address(0x14B68b85E1037d1C75726b7794e99C20554f9CC3)); - validator.setOwner(owner); vm.stopBroadcast(); From 51cd49086a321370a3629ced40d1dfb73ad6bf34 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Mon, 22 May 2023 22:06:27 +0100 Subject: [PATCH 05/54] started testing --- src/BrainPass/BrainPass.sol | 31 ++++++++++++----- test/BrainPass.t.sol | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index e7f6d1c..b013189 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -7,7 +7,15 @@ import {Owned} from "solmate/auth/Owned.sol"; import {SafeMath} from "openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; interface IERC20 { + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); + function transfer(address to, uint256 amount) external returns (bool); + + function balanceOf(address account) external view returns (uint256); } /// @title BRAIN Pass NFT @@ -20,6 +28,8 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- error MintingPaymentFailed(); + error IncreseTimePaymentFailed(); + error UserBalanceNotEnough(); /// ----------------------------------------------------------------------- /// Inheritances @@ -58,7 +68,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Constant /// ----------------------------------------------------------------------- - IERC20 public IqToken; + address public IqToken; /// ----------------------------------------------------------------------- /// Variables @@ -72,14 +82,14 @@ contract BrainPassCollectibles is ERC721, Owned { constructor( address IqAddr ) ERC721("BRAINY EDITOR PASS", "BEP") Owned(msg.sender) { - IqToken = IERC20(IqAddr); + IqToken =IqAddr; } function baseURI() internal view virtual returns (string memory) { return baseTokenURI; } - function setBaseURI(string memory tokenURI) public onlyOwner { + function setBaseURI(string memory tokenURI) internal { baseTokenURI = tokenURI; } @@ -115,7 +125,7 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 passId, uint256 startTimestamp, uint256 endTimestamp - ) public payable { + ) public { require( addressToPassId[msg.sender][passId] != true, "Max NFTs per address reached" @@ -131,12 +141,14 @@ contract BrainPassCollectibles is ERC721, Owned { ); uint256 price = calculatePrice(passId, startTimestamp, endTimestamp); - require(msg.value >= price, "Not enough payment token"); - uint256 tokenId = passType.lastTokenIdMinted.add(1); - bool success = IqToken.transfer(owner, price); + if (IERC20(IqToken).balanceOf(msg.sender) < price) + revert UserBalanceNotEnough(); + + bool success = IERC20(IqToken).transferFrom(msg.sender, owner, 1e18); if (!success) revert MintingPaymentFailed(); + uint256 tokenId = passType.lastTokenIdMinted + 1; setBaseURI(passType.tokenURI); _safeMint(msg.sender, tokenId); @@ -186,7 +198,7 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 passIdNum, uint newStartTime, uint256 newEndTime - ) public payable { + ) public { require( msg.sender == ownerOf(tokenId), "You cannot increase the time for an NFT you don't own" @@ -194,7 +206,8 @@ contract BrainPassCollectibles is ERC721, Owned { UserPassItem storage pass = addressToNFTPass[ownerOf(tokenId)][tokenId]; uint256 price = calculatePrice(passIdNum, newStartTime, newEndTime); - require(msg.value >= price, "Not enough payment token"); + bool success = IERC20(IqToken).transferFrom(msg.sender, owner, price); + if (!success) revert IncreseTimePaymentFailed(); pass.startTimestamp = newStartTime; pass.endTimestamp = newEndTime; diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index e69de29..637b771 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.13; + +import {PRBTest} from "prb-test/PRBTest.sol"; +import {Cheats} from "forge-std/Cheats.sol"; +import {console} from "../lib/forge-std/src/console.sol"; +import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; +import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol"; +import {MockERC20} from "../lib/solmate/src/test/utils/mocks/MockERC20.sol"; + +contract TestEditor is PRBTest, Cheats { + BrainPassCollectibles internal brainPass; + address private alice = address(0x2); + address private doe = address(0x3); + address private babe = address(0x4); + MockERC20 private mockERC20; + + function setUp() public { + mockERC20 = new MockERC20("Mock Token", "MTN", 18); + brainPass = new BrainPassCollectibles(address(mockERC20)); + } + + function testAddPassType() public { + brainPass.addPassType(1, 15, "http://example.com/1", "Gold", 30, 0); + brainPass.getAllPassType(1); + assertEq(brainPass.getAllPassType(1).tokenURI, "http://example.com/1"); + } + + function testCalculatePrice() public { + brainPass.addPassType(1, 2, "http://example.com/1", "Gold", 30, 0); + brainPass.calculatePrice(1, 10, 20); + assertEq(brainPass.calculatePrice(1, 10, 20), 20); + } + + function testFailUserBalanceNotEnough() public { + brainPass.addPassType(1, 2, "http://example.com/1", "Gold", 20, 0); + vm.startPrank(alice); + brainPass.mintNFT(1, 10, 20); + vm.expectRevert("UserBalanceNotEnough"); + vm.stopPrank(); + } + + function testFailMintingPaymentFailed() public { + brainPass.addPassType(1, 2, "http://example.com/1", "Gold", 20, 0); + uint256 mintAmount = 1e18; + mockERC20.mint(alice, mintAmount); + vm.startPrank(alice); + brainPass.mintNFT(1, 10, 20); + vm.expectRevert("MintingPaymentFailed"); + vm.stopPrank(); + } + + function testmintNFT() public { + brainPass.addPassType(1, 2, "http://example.com/1", "Gold", 20, 0); + uint256 mintAmount = 10000e18; + mockERC20.mint(alice, mintAmount); + vm.startPrank(alice); + brainPass.mintNFT(1, 10, 20); + assertEq(brainPass.balanceOf(alice), 1); + bool hasMinted = brainPass.addressToPassId(alice, 1); + assertEq(hasMinted, true); + vm.stopPrank(); + } + + +} From 1c6e3fe690ffeabd7d7befba2ddee7e88b3d9aa0 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Tue, 23 May 2023 12:55:41 +0100 Subject: [PATCH 06/54] updated test file and improved contract --- src/BrainPass/BrainPass.sol | 58 +++++++++++++-------------- test/BrainPass.t.sol | 78 +++++++++++++++++++++++-------------- 2 files changed, 77 insertions(+), 59 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index b013189..2d03526 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -50,7 +50,7 @@ contract BrainPassCollectibles is ERC721, Owned { struct PassType { uint256 passId; string name; - uint256 pricePerDay; + uint256 pricePerMonth; string tokenURI; uint256 maxTokens; uint256 discount; @@ -69,6 +69,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// Constant /// ----------------------------------------------------------------------- address public IqToken; + uint256 MonthInSecs = 2592000; //30 days (2,592,000 seconds) /// ----------------------------------------------------------------------- /// Variables @@ -79,10 +80,8 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- - constructor( - address IqAddr - ) ERC721("BRAINY EDITOR PASS", "BEP") Owned(msg.sender) { - IqToken =IqAddr; + constructor(address IqAddr) ERC721("BAINPASS", "BEP") Owned(msg.sender) { + IqToken = IqAddr; } function baseURI() internal view virtual returns (string memory) { @@ -97,7 +96,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// @param passId and others are the details needed for a passType function addPassType( uint256 passId, - uint256 pricePerDay, + uint256 pricePerMonth, string memory tokenURI, string memory name, uint256 maxTokens, @@ -109,14 +108,14 @@ contract BrainPassCollectibles is ERC721, Owned { passTypes[passId] = PassType( passId, name, - pricePerDay, + pricePerMonth, tokenURI, maxTokens, discount, 0 ); - emit NewPassAdded(passId, name, maxTokens, pricePerDay); + emit NewPassAdded(passId, name, maxTokens, pricePerMonth); } /// @notice Mint and NFT of a particular passtype @@ -142,18 +141,12 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 price = calculatePrice(passId, startTimestamp, endTimestamp); - if (IERC20(IqToken).balanceOf(msg.sender) < price) - revert UserBalanceNotEnough(); - - bool success = IERC20(IqToken).transferFrom(msg.sender, owner, 1e18); + bool success = IERC20(IqToken).transferFrom(msg.sender, owner, price); if (!success) revert MintingPaymentFailed(); uint256 tokenId = passType.lastTokenIdMinted + 1; - setBaseURI(passType.tokenURI); - _safeMint(msg.sender, tokenId); addressToPassId[msg.sender][passId] = true; - UserPassItem memory purchase = UserPassItem( tokenId, passId, @@ -163,6 +156,9 @@ contract BrainPassCollectibles is ERC721, Owned { addressToNFTPass[msg.sender][tokenId] = purchase; passType.lastTokenIdMinted = tokenId; + setBaseURI(passType.tokenURI); + _safeMint(msg.sender, tokenId); + emit BrainPassBought( msg.sender, tokenId, @@ -180,9 +176,15 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 endTimestamp ) public view returns (uint256) { PassType memory passType = passTypes[passId]; - //duration in days uint256 duration = endTimestamp.sub(startTimestamp); - uint256 totalPrice = duration.mul(passType.pricePerDay); + + require(duration >= MonthInSecs, "Not up to a month plan chosen "); + + uint256 monthPeriods = duration.div(MonthInSecs); + + // Calculate the total price + uint256 totalPrice = monthPeriods.mul(passType.pricePerMonth); + if (passType.discount > 0) { uint256 discountAmount = totalPrice.mul(passType.discount).div(100); totalPrice = totalPrice.sub(discountAmount); @@ -195,7 +197,6 @@ contract BrainPassCollectibles is ERC721, Owned { /// @param tokenId The Id of the NFT whose time is to be increased function increasePassTime( uint256 tokenId, - uint256 passIdNum, uint newStartTime, uint256 newEndTime ) public { @@ -205,7 +206,7 @@ contract BrainPassCollectibles is ERC721, Owned { ); UserPassItem storage pass = addressToNFTPass[ownerOf(tokenId)][tokenId]; - uint256 price = calculatePrice(passIdNum, newStartTime, newEndTime); + uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); bool success = IERC20(IqToken).transferFrom(msg.sender, owner, price); if (!success) revert IncreseTimePaymentFailed(); @@ -222,21 +223,18 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Gets all the NFT owned by an address /// @param user The address of the user - function getUserNFTs( + function getUserPassDetails( address user, - uint passIdNum - ) public view returns (UserPassItem[] memory) { - uint256 userTokenCount = balanceOf(user); - PassType memory passType = passTypes[passIdNum]; - UserPassItem[] memory userTokens = new UserPassItem[](userTokenCount); - uint256 counter = 0; + uint passId + ) public view returns (UserPassItem memory) { + PassType memory passType = passTypes[passId]; + UserPassItem memory userToken; for (uint256 i = 0; i < passType.maxTokens; i++) { if (ownerOf(i) == user) { - userTokens[counter] = addressToNFTPass[msg.sender][i]; - counter++; + userToken = addressToNFTPass[msg.sender][i]; } } - return userTokens; + return userToken; } /// @notice Gets all the details of a passtype @@ -279,6 +277,6 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 indexed _passId, string _name, uint256 _maxtokens, - uint256 _pricePerDay + uint256 _pricePerMonth ); } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 637b771..90cf483 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.13; import {PRBTest} from "prb-test/PRBTest.sol"; import {Cheats} from "forge-std/Cheats.sol"; -import {console} from "../lib/forge-std/src/console.sol"; +import "forge-std/console.sol"; +import {stdError} from "forge-std/Errors.sol"; import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol"; import {MockERC20} from "../lib/solmate/src/test/utils/mocks/MockERC20.sol"; @@ -15,52 +16,71 @@ contract TestEditor is PRBTest, Cheats { address private babe = address(0x4); MockERC20 private mockERC20; + struct PassType { + uint256 passId; + string name; + uint256 pricePerMonth; + string tokenURI; + uint256 maxTokens; + uint256 discount; + uint256 lastTokenIdMinted; + } + + struct UserPassItem { + uint256 tokenId; + uint256 passId; + uint256 startTimestamp; + uint256 endTimestamp; + } + function setUp() public { mockERC20 = new MockERC20("Mock Token", "MTN", 18); brainPass = new BrainPassCollectibles(address(mockERC20)); + brainPass.addPassType(1, 15, "http://example.com/1", "Gold", 20, 0); } function testAddPassType() public { - brainPass.addPassType(1, 15, "http://example.com/1", "Gold", 30, 0); brainPass.getAllPassType(1); assertEq(brainPass.getAllPassType(1).tokenURI, "http://example.com/1"); } - function testCalculatePrice() public { - brainPass.addPassType(1, 2, "http://example.com/1", "Gold", 30, 0); - brainPass.calculatePrice(1, 10, 20); - assertEq(brainPass.calculatePrice(1, 10, 20), 20); - } + function testmintNFT() public { + mockERC20.mint(alice, 10); - function testFailUserBalanceNotEnough() public { - brainPass.addPassType(1, 2, "http://example.com/1", "Gold", 20, 0); vm.startPrank(alice); - brainPass.mintNFT(1, 10, 20); - vm.expectRevert("UserBalanceNotEnough"); - vm.stopPrank(); - } - function testFailMintingPaymentFailed() public { - brainPass.addPassType(1, 2, "http://example.com/1", "Gold", 20, 0); - uint256 mintAmount = 1e18; - mockERC20.mint(alice, mintAmount); - vm.startPrank(alice); - brainPass.mintNFT(1, 10, 20); - vm.expectRevert("MintingPaymentFailed"); - vm.stopPrank(); - } + mockERC20.approve(address(brainPass), 3e18); + vm.expectRevert(stdError.arithmeticError); + brainPass.mintNFT(1, 5184000, 18144000); - function testmintNFT() public { - brainPass.addPassType(1, 2, "http://example.com/1", "Gold", 20, 0); - uint256 mintAmount = 10000e18; - mockERC20.mint(alice, mintAmount); - vm.startPrank(alice); - brainPass.mintNFT(1, 10, 20); + mockERC20.mint(alice, 4e18); + brainPass.mintNFT(1, 5184000, 18144000); assertEq(brainPass.balanceOf(alice), 1); bool hasMinted = brainPass.addressToPassId(alice, 1); assertEq(hasMinted, true); vm.stopPrank(); } - + function testIncreaseTime() public { + mockERC20.mint(alice, 3e18); + + vm.startPrank(alice); + mockERC20.approve(address(brainPass), 5e18); + brainPass.mintNFT(1, 5184000, 18144000); + + // brainPass.getUserPassDetails(alice, 1); + // console.log(brainPass.getUserPassDetails(alice, 1).tokenId); + // brainPass.increasePassTime( + // brainPass.getUserPassDetails(alice, 1).tokenId, + // 18144000, + // 20736000 + // ); + + // brainPass.addressToNFTPass( + // alice, + // brainPass.getUserPassDetails(alice, 1).tokenId + // ); + // uint _satTime = brainPass.getUserPassDetails(alice, 1).startTimestamp; + // assertEq(_satTime, 30); + } } From d88b6e8e5da22c2cfb91186a36743fcbd7306cb8 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Tue, 23 May 2023 22:35:49 +0100 Subject: [PATCH 07/54] changes noticed fix, getAllPassType Func, --- src/BrainPass/BrainPass.sol | 53 +++++++++++++++------------ test/BrainPass.t.sol | 71 ++++++++++++++----------------------- 2 files changed, 57 insertions(+), 67 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 2d03526..a60beed 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -50,7 +50,7 @@ contract BrainPassCollectibles is ERC721, Owned { struct PassType { uint256 passId; string name; - uint256 pricePerMonth; + uint256 pricePerDay; string tokenURI; uint256 maxTokens; uint256 discount; @@ -69,13 +69,13 @@ contract BrainPassCollectibles is ERC721, Owned { /// Constant /// ----------------------------------------------------------------------- address public IqToken; - uint256 MonthInSecs = 2592000; //30 days (2,592,000 seconds) + uint256 SecondsInADay = 86400; /// ----------------------------------------------------------------------- /// Variables /// ----------------------------------------------------------------------- string public baseTokenURI; - Counters.Counter private tokenIds; + Counters.Counter private passIds; /// ----------------------------------------------------------------------- /// Constructor @@ -84,7 +84,7 @@ contract BrainPassCollectibles is ERC721, Owned { IqToken = IqAddr; } - function baseURI() internal view virtual returns (string memory) { + function _baseURI() internal view virtual override returns (string memory) { return baseTokenURI; } @@ -93,10 +93,9 @@ contract BrainPassCollectibles is ERC721, Owned { } /// @notice Add a new Pass Type - /// @param passId and others are the details needed for a passType + /// @param name and others are the details needed for a passType function addPassType( - uint256 passId, - uint256 pricePerMonth, + uint256 pricePerDay, string memory tokenURI, string memory name, uint256 maxTokens, @@ -104,18 +103,20 @@ contract BrainPassCollectibles is ERC721, Owned { ) public onlyOwner { require(bytes(tokenURI).length > 0, "Invalid token URI"); require(maxTokens > 0, "Invalid max tokens"); - + uint256 passId = passIds.current(); passTypes[passId] = PassType( passId, name, - pricePerMonth, + pricePerDay, tokenURI, maxTokens, discount, 0 ); - emit NewPassAdded(passId, name, maxTokens, pricePerMonth); + passIds.increment(); + + emit NewPassAdded(passId, name, maxTokens, pricePerDay); } /// @notice Mint and NFT of a particular passtype @@ -156,8 +157,8 @@ contract BrainPassCollectibles is ERC721, Owned { addressToNFTPass[msg.sender][tokenId] = purchase; passType.lastTokenIdMinted = tokenId; - setBaseURI(passType.tokenURI); _safeMint(msg.sender, tokenId); + setBaseURI(passType.tokenURI); emit BrainPassBought( msg.sender, @@ -178,19 +179,17 @@ contract BrainPassCollectibles is ERC721, Owned { PassType memory passType = passTypes[passId]; uint256 duration = endTimestamp.sub(startTimestamp); - require(duration >= MonthInSecs, "Not up to a month plan chosen "); - - uint256 monthPeriods = duration.div(MonthInSecs); + uint256 subscriptionPeriodInDays = duration.div(SecondsInADay); // Calculate the total price - uint256 totalPrice = monthPeriods.mul(passType.pricePerMonth); + uint256 totalPrice = subscriptionPeriodInDays.mul(passType.pricePerDay); if (passType.discount > 0) { uint256 discountAmount = totalPrice.mul(passType.discount).div(100); totalPrice = totalPrice.sub(discountAmount); } - return totalPrice; + return totalPrice * 1e18; } /// @notice Increase the time to hold a PassNft @@ -205,8 +204,9 @@ contract BrainPassCollectibles is ERC721, Owned { "You cannot increase the time for an NFT you don't own" ); - UserPassItem storage pass = addressToNFTPass[ownerOf(tokenId)][tokenId]; + UserPassItem storage pass = addressToNFTPass[msg.sender][tokenId]; uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); + bool success = IERC20(IqToken).transferFrom(msg.sender, owner, price); if (!success) revert IncreseTimePaymentFailed(); @@ -229,19 +229,28 @@ contract BrainPassCollectibles is ERC721, Owned { ) public view returns (UserPassItem memory) { PassType memory passType = passTypes[passId]; UserPassItem memory userToken; - for (uint256 i = 0; i < passType.maxTokens; i++) { + for (uint256 i = 1; i < passType.maxTokens; i++) { if (ownerOf(i) == user) { userToken = addressToNFTPass[msg.sender][i]; + break; } } return userToken; } + /// @notice Gets all the PassType created + function getAllPassType() public view returns (PassType[] memory) { + uint256 total = passIds.current(); + PassType[] memory passType = new PassType[](total); + for (uint256 i = 0; i < total; i++) { + passType[i] = passTypes[i]; + } + return passType; + } + /// @notice Gets all the details of a passtype /// @param passId The id of the passtype - function getAllPassType( - uint256 passId - ) public view returns (PassType memory) { + function getPassType(uint256 passId) public view returns (PassType memory) { PassType memory passType = passTypes[passId]; return (passType); } @@ -277,6 +286,6 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 indexed _passId, string _name, uint256 _maxtokens, - uint256 _pricePerMonth + uint256 _pricePerDay ); } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 90cf483..f938352 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -12,49 +12,33 @@ import {MockERC20} from "../lib/solmate/src/test/utils/mocks/MockERC20.sol"; contract TestEditor is PRBTest, Cheats { BrainPassCollectibles internal brainPass; address private alice = address(0x2); - address private doe = address(0x3); - address private babe = address(0x4); MockERC20 private mockERC20; - struct PassType { - uint256 passId; - string name; - uint256 pricePerMonth; - string tokenURI; - uint256 maxTokens; - uint256 discount; - uint256 lastTokenIdMinted; - } - - struct UserPassItem { - uint256 tokenId; - uint256 passId; - uint256 startTimestamp; - uint256 endTimestamp; - } - function setUp() public { - mockERC20 = new MockERC20("Mock Token", "MTN", 18); + mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token brainPass = new BrainPassCollectibles(address(mockERC20)); - brainPass.addPassType(1, 15, "http://example.com/1", "Gold", 20, 0); + brainPass.addPassType( 15, "http://example.com/1", "Gold", 200, 0); } function testAddPassType() public { - brainPass.getAllPassType(1); - assertEq(brainPass.getAllPassType(1).tokenURI, "http://example.com/1"); + brainPass.addPassType(15, "http://example.com/56", "Gold", 200, 0); + string memory _name = brainPass.getPassType(1).name; + assertEq(_name, "Gold"); } function testmintNFT() public { - mockERC20.mint(alice, 10); + mockERC20.mint(alice, 100e18); vm.startPrank(alice); - mockERC20.approve(address(brainPass), 3e18); + vm.expectRevert(stdError.arithmeticError); - brainPass.mintNFT(1, 5184000, 18144000); + brainPass.mintNFT(1, 172800, 518400); - mockERC20.mint(alice, 4e18); - brainPass.mintNFT(1, 5184000, 18144000); + + mockERC20.approve(address(brainPass), 60e18); + assertEq(brainPass.balanceOf(alice), 0); + brainPass.mintNFT(1, 172800, 518400); assertEq(brainPass.balanceOf(alice), 1); bool hasMinted = brainPass.addressToPassId(alice, 1); assertEq(hasMinted, true); @@ -62,25 +46,22 @@ contract TestEditor is PRBTest, Cheats { } function testIncreaseTime() public { - mockERC20.mint(alice, 3e18); + mockERC20.mint(alice, 200e18); vm.startPrank(alice); - mockERC20.approve(address(brainPass), 5e18); - brainPass.mintNFT(1, 5184000, 18144000); - - // brainPass.getUserPassDetails(alice, 1); - // console.log(brainPass.getUserPassDetails(alice, 1).tokenId); - // brainPass.increasePassTime( - // brainPass.getUserPassDetails(alice, 1).tokenId, - // 18144000, - // 20736000 - // ); + mockERC20.approve(address(brainPass), 120e18); + assertEq(brainPass.balanceOf(alice), 0); + brainPass.mintNFT(1, 172800, 518400); + assertEq(brainPass.balanceOf(alice), 1); + + uint256 _tokenId = brainPass.getUserPassDetails(alice, 1).tokenId; + assertEq(_tokenId, 1); + brainPass.increasePassTime(_tokenId, 518400, 864000); - // brainPass.addressToNFTPass( - // alice, - // brainPass.getUserPassDetails(alice, 1).tokenId - // ); - // uint _satTime = brainPass.getUserPassDetails(alice, 1).startTimestamp; - // assertEq(_satTime, 30); + brainPass.addressToNFTPass(alice, _tokenId); + uint _startTime = brainPass.getUserPassDetails(alice, 1).startTimestamp; + assertEq(_startTime, 518400); } + + //todo : upgrade /downgrade after subscription has ended, Errors } From 4482b93e8c3971ce412a1ddd32a5ad76dc93e8e2 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Tue, 23 May 2023 22:40:50 +0100 Subject: [PATCH 08/54] labelling functions --- src/BrainPass/BrainPass.sol | 97 +++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index a60beed..1b7ab74 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -12,10 +12,6 @@ interface IERC20 { address to, uint256 amount ) external returns (bool); - - function transfer(address to, uint256 amount) external returns (bool); - - function balanceOf(address account) external view returns (uint256); } /// @title BRAIN Pass NFT @@ -84,14 +80,14 @@ contract BrainPassCollectibles is ERC721, Owned { IqToken = IqAddr; } + /// ----------------------------------------------------------------------- + /// External functions + /// ----------------------------------------------------------------------- + function _baseURI() internal view virtual override returns (string memory) { return baseTokenURI; } - function setBaseURI(string memory tokenURI) internal { - baseTokenURI = tokenURI; - } - /// @notice Add a new Pass Type /// @param name and others are the details needed for a passType function addPassType( @@ -100,7 +96,7 @@ contract BrainPassCollectibles is ERC721, Owned { string memory name, uint256 maxTokens, uint256 discount - ) public onlyOwner { + ) external onlyOwner { require(bytes(tokenURI).length > 0, "Invalid token URI"); require(maxTokens > 0, "Invalid max tokens"); uint256 passId = passIds.current(); @@ -125,7 +121,7 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 passId, uint256 startTimestamp, uint256 endTimestamp - ) public { + ) external { require( addressToPassId[msg.sender][passId] != true, "Max NFTs per address reached" @@ -169,36 +165,13 @@ contract BrainPassCollectibles is ERC721, Owned { ); } - /// @notice Calculate the price of an Nft - /// @param startTimestamp and endTimestamp are used to calc the price to be paid - function calculatePrice( - uint256 passId, - uint256 startTimestamp, - uint256 endTimestamp - ) public view returns (uint256) { - PassType memory passType = passTypes[passId]; - uint256 duration = endTimestamp.sub(startTimestamp); - - uint256 subscriptionPeriodInDays = duration.div(SecondsInADay); - - // Calculate the total price - uint256 totalPrice = subscriptionPeriodInDays.mul(passType.pricePerDay); - - if (passType.discount > 0) { - uint256 discountAmount = totalPrice.mul(passType.discount).div(100); - totalPrice = totalPrice.sub(discountAmount); - } - - return totalPrice * 1e18; - } - /// @notice Increase the time to hold a PassNft /// @param tokenId The Id of the NFT whose time is to be increased function increasePassTime( uint256 tokenId, uint newStartTime, uint256 newEndTime - ) public { + ) external { require( msg.sender == ownerOf(tokenId), "You cannot increase the time for an NFT you don't own" @@ -221,6 +194,45 @@ contract BrainPassCollectibles is ERC721, Owned { ); } + /// @notice Withdraws any amount in the contract + function withdraw() external payable onlyOwner { + uint256 balance = address(this).balance; + require(balance > 0, "No ether left to withdraw"); + (bool success, ) = (msg.sender).call{value: balance}(""); + require(success, "Transfer failed."); + } + + /// ----------------------------------------------------------------------- + /// Internal Functions + /// ----------------------------------------------------------------------- + + /// @notice Calculate the price of an Nft + /// @param startTimestamp and endTimestamp are used to calc the price to be paid + function calculatePrice( + uint256 passId, + uint256 startTimestamp, + uint256 endTimestamp + ) internal view returns (uint256) { + PassType memory passType = passTypes[passId]; + uint256 duration = endTimestamp.sub(startTimestamp); + + uint256 subscriptionPeriodInDays = duration.div(SecondsInADay); + + // Calculate the total price + uint256 totalPrice = subscriptionPeriodInDays.mul(passType.pricePerDay); + + if (passType.discount > 0) { + uint256 discountAmount = totalPrice.mul(passType.discount).div(100); + totalPrice = totalPrice.sub(discountAmount); + } + + return totalPrice * 1e18; + } + + /// ----------------------------------------------------------------------- + /// Getters + /// ----------------------------------------------------------------------- + /// @notice Gets all the NFT owned by an address /// @param user The address of the user function getUserPassDetails( @@ -239,7 +251,7 @@ contract BrainPassCollectibles is ERC721, Owned { } /// @notice Gets all the PassType created - function getAllPassType() public view returns (PassType[] memory) { + function getAllPassType() external view returns (PassType[] memory) { uint256 total = passIds.current(); PassType[] memory passType = new PassType[](total); for (uint256 i = 0; i < total; i++) { @@ -250,17 +262,18 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Gets all the details of a passtype /// @param passId The id of the passtype - function getPassType(uint256 passId) public view returns (PassType memory) { + function getPassType( + uint256 passId + ) external view returns (PassType memory) { PassType memory passType = passTypes[passId]; return (passType); } - /// @notice Withdraws any amount in the contract - function withdraw() public payable onlyOwner { - uint256 balance = address(this).balance; - require(balance > 0, "No ether left to withdraw"); - (bool success, ) = (msg.sender).call{value: balance}(""); - require(success, "Transfer failed."); + /// ----------------------------------------------------------------------- + /// Setters + /// ----------------------------------------------------------------------- + function setBaseURI(string memory tokenURI) internal { + baseTokenURI = tokenURI; } /// ----------------------------------------------------------------------- From 2959b638f448466aecab4c1b3beaa96ebc1e403e Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 24 May 2023 11:33:42 +0100 Subject: [PATCH 09/54] updated contract and arranged the functions, used reverts instead of require, more tests -all passed --- src/BrainPass/BrainPass.sol | 30 ++++++++--------- test/BrainPass.t.sol | 64 +++++++++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 1b7ab74..ee71935 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -26,6 +26,9 @@ contract BrainPassCollectibles is ERC721, Owned { error MintingPaymentFailed(); error IncreseTimePaymentFailed(); error UserBalanceNotEnough(); + error MaxPassNFTsReached(); + error NotTheOwnerOfThisNft(); + error InvalidMaxTokensForAPass(); /// ----------------------------------------------------------------------- /// Inheritances @@ -84,10 +87,6 @@ contract BrainPassCollectibles is ERC721, Owned { /// External functions /// ----------------------------------------------------------------------- - function _baseURI() internal view virtual override returns (string memory) { - return baseTokenURI; - } - /// @notice Add a new Pass Type /// @param name and others are the details needed for a passType function addPassType( @@ -97,8 +96,8 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 maxTokens, uint256 discount ) external onlyOwner { - require(bytes(tokenURI).length > 0, "Invalid token URI"); - require(maxTokens > 0, "Invalid max tokens"); + if (maxTokens <= 0) revert InvalidMaxTokensForAPass(); + uint256 passId = passIds.current(); passTypes[passId] = PassType( passId, @@ -122,10 +121,8 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 startTimestamp, uint256 endTimestamp ) external { - require( - addressToPassId[msg.sender][passId] != true, - "Max NFTs per address reached" - ); + if (addressToPassId[msg.sender][passId] == true) + revert MaxPassNFTsReached(); PassType storage passType = passTypes[passId]; @@ -172,10 +169,7 @@ contract BrainPassCollectibles is ERC721, Owned { uint newStartTime, uint256 newEndTime ) external { - require( - msg.sender == ownerOf(tokenId), - "You cannot increase the time for an NFT you don't own" - ); + if (msg.sender != ownerOf(tokenId)) revert NotTheOwnerOfThisNft(); UserPassItem storage pass = addressToNFTPass[msg.sender][tokenId]; uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); @@ -233,6 +227,10 @@ contract BrainPassCollectibles is ERC721, Owned { /// Getters /// ----------------------------------------------------------------------- + function _baseURI() internal view virtual override returns (string memory) { + return baseTokenURI; + } + /// @notice Gets all the NFT owned by an address /// @param user The address of the user function getUserPassDetails( @@ -254,7 +252,7 @@ contract BrainPassCollectibles is ERC721, Owned { function getAllPassType() external view returns (PassType[] memory) { uint256 total = passIds.current(); PassType[] memory passType = new PassType[](total); - for (uint256 i = 0; i < total; i++) { + for (uint256 i = 1; i < total; i++) { passType[i] = passTypes[i]; } return passType; @@ -272,7 +270,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Setters /// ----------------------------------------------------------------------- - function setBaseURI(string memory tokenURI) internal { + function setBaseURI(string memory tokenURI) public { baseTokenURI = tokenURI; } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index f938352..607eb5a 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -12,12 +12,13 @@ import {MockERC20} from "../lib/solmate/src/test/utils/mocks/MockERC20.sol"; contract TestEditor is PRBTest, Cheats { BrainPassCollectibles internal brainPass; address private alice = address(0x2); + address private bob = address(0x3); MockERC20 private mockERC20; function setUp() public { mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token brainPass = new BrainPassCollectibles(address(mockERC20)); - brainPass.addPassType( 15, "http://example.com/1", "Gold", 200, 0); + brainPass.addPassType(15, "http://example.com/1", "Gold", 200, 0); } function testAddPassType() public { @@ -26,42 +27,77 @@ contract TestEditor is PRBTest, Cheats { assertEq(_name, "Gold"); } + function testFailMintNFT() public { + mockERC20.mint(alice, 300e18); + vm.startPrank(alice); + mockERC20.approve(address(brainPass), 120e18); + assertEq(brainPass.balanceOf(alice), 0); + brainPass.mintNFT(0, 172800, 518400); + assertEq(brainPass.balanceOf(alice), 1); + brainPass.mintNFT(0, 172800, 518400); + vm.expectRevert("MaxPassNFTsReached"); + } + function testmintNFT() public { mockERC20.mint(alice, 100e18); - vm.startPrank(alice); mockERC20.approve(address(brainPass), 3e18); - vm.expectRevert(stdError.arithmeticError); - brainPass.mintNFT(1, 172800, 518400); - - + brainPass.mintNFT(0, 172800, 518400); mockERC20.approve(address(brainPass), 60e18); assertEq(brainPass.balanceOf(alice), 0); - brainPass.mintNFT(1, 172800, 518400); + brainPass.mintNFT(0, 172800, 518400); assertEq(brainPass.balanceOf(alice), 1); - bool hasMinted = brainPass.addressToPassId(alice, 1); + bool hasMinted = brainPass.addressToPassId(alice, 0); assertEq(hasMinted, true); vm.stopPrank(); } + function testFailIncreaseTime() public { + mockERC20.mint(alice, 300e18); + vm.startPrank(alice); + mockERC20.approve(address(brainPass), 120e18); + brainPass.mintNFT(0, 172800, 518400); + uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; + brainPass.increasePassTime(_tokenId, 518400, 864000); + vm.startPrank(bob); + brainPass.increasePassTime(_tokenId, 518400, 864000); + vm.expectRevert("NotTheOwnerOfThisNft"); + } + function testIncreaseTime() public { mockERC20.mint(alice, 200e18); - vm.startPrank(alice); mockERC20.approve(address(brainPass), 120e18); assertEq(brainPass.balanceOf(alice), 0); - brainPass.mintNFT(1, 172800, 518400); + brainPass.mintNFT(0, 172800, 518400); assertEq(brainPass.balanceOf(alice), 1); - - uint256 _tokenId = brainPass.getUserPassDetails(alice, 1).tokenId; + uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; assertEq(_tokenId, 1); brainPass.increasePassTime(_tokenId, 518400, 864000); - brainPass.addressToNFTPass(alice, _tokenId); - uint _startTime = brainPass.getUserPassDetails(alice, 1).startTimestamp; + uint _startTime = brainPass.getUserPassDetails(alice, 0).startTimestamp; assertEq(_startTime, 518400); } + function testBaseTokenURI() public { + assertEq(brainPass.baseTokenURI(), ""); + brainPass.setBaseURI("http://example.org.com/565"); + assertEq(brainPass.baseTokenURI(), "http://example.org.com/565"); + } + + function testGetAllPassType() public { + assertEq(brainPass.getAllPassType().length, 1); + brainPass.addPassType( + 400, + "http://example.com/56", + "Platinum", + 3000, + 10 + ); + assertEq(brainPass.getPassType(1).name, "Platinum"); + assertEq(brainPass.getAllPassType().length, 2); + } + //todo : upgrade /downgrade after subscription has ended, Errors } From a3e0bd73048bdf41ddbfc9d14cdedffffeee26b6 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 24 May 2023 14:05:06 +0100 Subject: [PATCH 10/54] changed all require func to if..revert --- src/BrainPass/BrainPass.sol | 29 +++++++++++++++-------------- test/BrainPass.t.sol | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index ee71935..148cdab 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -29,6 +29,10 @@ contract BrainPassCollectibles is ERC721, Owned { error MaxPassNFTsReached(); error NotTheOwnerOfThisNft(); error InvalidMaxTokensForAPass(); + error PassTypeNotFound(); + error PassMaxSupplyReached(); + error NoEtherLeftToWithdraw(); + error TransferFailed(); /// ----------------------------------------------------------------------- /// Inheritances @@ -67,8 +71,8 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Constant /// ----------------------------------------------------------------------- - address public IqToken; - uint256 SecondsInADay = 86400; + address public immutable iqToken; + uint256 immutable SECONDS_IN_A_DAY = 86400; /// ----------------------------------------------------------------------- /// Variables @@ -80,7 +84,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// Constructor /// ----------------------------------------------------------------------- constructor(address IqAddr) ERC721("BAINPASS", "BEP") Owned(msg.sender) { - IqToken = IqAddr; + iqToken = IqAddr; } /// ----------------------------------------------------------------------- @@ -126,16 +130,13 @@ contract BrainPassCollectibles is ERC721, Owned { PassType storage passType = passTypes[passId]; - require(passType.maxTokens != 0, "Pass type not found"); - - require( - passType.lastTokenIdMinted.add(1) <= passType.maxTokens, - "Max supply reached" - ); + if (passType.maxTokens == 0) revert PassTypeNotFound(); + if (passType.lastTokenIdMinted.add(1) >= passType.maxTokens) + revert PassMaxSupplyReached(); uint256 price = calculatePrice(passId, startTimestamp, endTimestamp); - bool success = IERC20(IqToken).transferFrom(msg.sender, owner, price); + bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); if (!success) revert MintingPaymentFailed(); uint256 tokenId = passType.lastTokenIdMinted + 1; @@ -174,7 +175,7 @@ contract BrainPassCollectibles is ERC721, Owned { UserPassItem storage pass = addressToNFTPass[msg.sender][tokenId]; uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); - bool success = IERC20(IqToken).transferFrom(msg.sender, owner, price); + bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); if (!success) revert IncreseTimePaymentFailed(); pass.startTimestamp = newStartTime; @@ -191,9 +192,9 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Withdraws any amount in the contract function withdraw() external payable onlyOwner { uint256 balance = address(this).balance; - require(balance > 0, "No ether left to withdraw"); + if (balance <= 0) revert NoEtherLeftToWithdraw(); (bool success, ) = (msg.sender).call{value: balance}(""); - require(success, "Transfer failed."); + if (!success) revert TransferFailed(); } /// ----------------------------------------------------------------------- @@ -210,7 +211,7 @@ contract BrainPassCollectibles is ERC721, Owned { PassType memory passType = passTypes[passId]; uint256 duration = endTimestamp.sub(startTimestamp); - uint256 subscriptionPeriodInDays = duration.div(SecondsInADay); + uint256 subscriptionPeriodInDays = duration.div(SECONDS_IN_A_DAY); // Calculate the total price uint256 totalPrice = subscriptionPeriodInDays.mul(passType.pricePerDay); diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 607eb5a..7334326 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -99,5 +99,5 @@ contract TestEditor is PRBTest, Cheats { assertEq(brainPass.getAllPassType().length, 2); } - //todo : upgrade /downgrade after subscription has ended, Errors + //todo : upgrade /downgrade after subscription has ended } From 9e52d56b3cf082a7f91a24f2e710830b8f0573e6 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 24 May 2023 19:25:54 +0100 Subject: [PATCH 11/54] readme doc --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++ test/BrainPass.t.sol | 1 + 2 files changed, 66 insertions(+) diff --git a/README.md b/README.md index d1aeff6..3295bd6 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,68 @@ source .env forge script script/BrainPassDeployer.s.sol:BrainPassDeployer --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_KEY -vvvv --gas-price 60 --legacy ``` + +# Expalaining How BrainPassCollectibles Works + +BrainPassCollectibles is a Solidity smart contract that enables the creation and management of BrainPass NFTs. It allows users to buy passes to have some Wiki previledges on IQ Wiki and provides functionalities to mint NFTs, increase pass time, and manage pass types. + +## Usage + +### Contract Deployment + +Deploy the `BrainPassCollectibles` contract by providing the address of the IQ token contract as a constructor parameter. + +### Adding a Pass Type +This function allows the contract owner to add a new pass type. + +Call the `addPassType` function with the following parameters: +- `pricePerDay`: The price per day in IQ tokens for the pass. +- `tokenURI`: The base URI for the pass token metadata. +- `name`: The name of the pass type. +- `maxTokens`: The maximum number of tokens that can be minted for this pass type. +- `discount`: The discount percentage applied to the pass price (optional). + +### Minting an NFT Pass +This function allows users to mint a BrainPass NFT for a specific pass type and duration. +Call the `mintNFT` function with the following parameters: +- `passId`: The ID of the pass type to mint. +- `startTimestamp`: The start timestamp for the pass. +- `endTimestamp`: The end timestamp for the pass. +- the duration is calculated by endTimestamp - startTimestamp +- An address can only mint one passtype. +- The payment is in IQ Token + +### Increasing Pass Time + +Call the `increasePassTime` function with the following parameters: +- `tokenId`: The ID of the NFT whose time should be increased. +- `newStartTime`: The new start timestamp for the pass. +- `newEndTime`: The new end timestamp for the pass. + +### Get User Pass Details +This gets the details of the tokenId the user has in the particular passId +Call the `getUserPassDetails` function with the following parameters: +- `user`: The address of the user. +- `passId`: The ID of the pass type. + +### Get Pass Types +Call the `getAllPassType` function to get an array of all pass types. + +### Get Pass Type Details +This gets a single passtype details +Call the `getPassType` function with the following parameters: +- `passId`: The ID of the pass type. + +### Withdraw Ether + +Only the contract owner can call the `withdraw` function to withdraw any amount of Ether stored in the contract. + +## Events + +The contract emits the following events: + +- `BrainPassBought`: Emitted when a user buys a BrainPass NFT. +- `TimeIncreased`: Emitted when the time of a BrainPass NFT is increased. +- `NewPassAdded`: Emitted when a new pass type is added. + + diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 7334326..7f815a7 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -48,6 +48,7 @@ contract TestEditor is PRBTest, Cheats { assertEq(brainPass.balanceOf(alice), 0); brainPass.mintNFT(0, 172800, 518400); assertEq(brainPass.balanceOf(alice), 1); + assertEq(mockERC20.balanceOf(address(this)), 60e18); bool hasMinted = brainPass.addressToPassId(alice, 0); assertEq(hasMinted, true); vm.stopPrank(); From ff9f7dbbb38a1c13fe0351ec541aad1e32499111 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 24 May 2023 21:04:32 +0100 Subject: [PATCH 12/54] validate pass duration func --- README.md | 5 ++--- src/BrainPass/BrainPass.sol | 28 +++++++++++++++++++++++++--- test/BrainPass.t.sol | 1 + 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3295bd6..4c6d1ab 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Call the `mintNFT` function with the following parameters: - The payment is in IQ Token ### Increasing Pass Time - +This function is ued to increase the duration for which a pass is owned for. Call the `increasePassTime` function with the following parameters: - `tokenId`: The ID of the NFT whose time should be increased. - `newStartTime`: The new start timestamp for the pass. @@ -92,8 +92,7 @@ Call the `getPassType` function with the following parameters: - `passId`: The ID of the pass type. ### Withdraw Ether - -Only the contract owner can call the `withdraw` function to withdraw any amount of Ether stored in the contract. +Only the contract owner can call the `withdraw` function to withdraw any amount of Ether stored in the contract. ## Events diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 148cdab..310be0d 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -33,6 +33,7 @@ contract BrainPassCollectibles is ERC721, Owned { error PassMaxSupplyReached(); error NoEtherLeftToWithdraw(); error TransferFailed(); + error DurationNotInTimeFrame(); /// ----------------------------------------------------------------------- /// Inheritances @@ -73,6 +74,8 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- address public immutable iqToken; uint256 immutable SECONDS_IN_A_DAY = 86400; + uint256 immutable DAYS_MINT_LOWER_LIMIT = 28; + uint256 immutable DAYS_MINT_UPPER_LIMIT = 365; /// ----------------------------------------------------------------------- /// Variables @@ -134,6 +137,8 @@ contract BrainPassCollectibles is ERC721, Owned { if (passType.lastTokenIdMinted.add(1) >= passType.maxTokens) revert PassMaxSupplyReached(); + if (!validatePassDuration(startTimestamp, endTimestamp)) revert DurationNotInTimeFrame(); + uint256 price = calculatePrice(passId, startTimestamp, endTimestamp); bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); @@ -163,6 +168,7 @@ contract BrainPassCollectibles is ERC721, Owned { ); } + /// @notice Increase the time to hold a PassNft /// @param tokenId The Id of the NFT whose time is to be increased function increasePassTime( @@ -177,9 +183,13 @@ contract BrainPassCollectibles is ERC721, Owned { bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); if (!success) revert IncreseTimePaymentFailed(); - - pass.startTimestamp = newStartTime; - pass.endTimestamp = newEndTime; + + if(block.timestamp > pass.endTimestamp){ //not active + pass.startTimestamp = newStartTime; // new start time + pass.endTimestamp = newEndTime; //new end time + }else {//active + pass.endTimestamp = newEndTime; //new endtime + } emit TimeIncreased( msg.sender, @@ -224,6 +234,18 @@ contract BrainPassCollectibles is ERC721, Owned { return totalPrice * 1e18; } + /// @notice Validates the Timestamp Duration for minting Nft + /// @param startTimestamp and endTimestamp are used to check if the duration is within the subscription timeframe + function validatePassDuration(uint256 startTimestamp, uint256 endTimestamp) internal pure returns(bool){ + uint256 durationInDays = (endTimestamp.sub(startTimestamp)) / SECONDS_IN_A_DAY; + + if (durationInDays >= DAYS_MINT_LOWER_LIMIT && durationInDays<= DAYS_MINT_UPPER_LIMIT) { + return true; + } else { + return false; + } + } + /// ----------------------------------------------------------------------- /// Getters /// ----------------------------------------------------------------------- diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 7f815a7..19c554e 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -76,6 +76,7 @@ contract TestEditor is PRBTest, Cheats { uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; assertEq(_tokenId, 1); brainPass.increasePassTime(_tokenId, 518400, 864000); + assertEq(mockERC20.balanceOf(address(this)), 120e18); brainPass.addressToNFTPass(alice, _tokenId); uint _startTime = brainPass.getUserPassDetails(alice, 0).startTimestamp; assertEq(_startTime, 518400); From 347c98cba0e8e1d9d3101a2fc5b550fb264a856c Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 24 May 2023 21:16:05 +0100 Subject: [PATCH 13/54] test for the new contract update --- test/BrainPass.t.sol | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 19c554e..8d42fae 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -39,16 +39,16 @@ contract TestEditor is PRBTest, Cheats { } function testmintNFT() public { - mockERC20.mint(alice, 100e18); + mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(brainPass), 3e18); vm.expectRevert(stdError.arithmeticError); - brainPass.mintNFT(0, 172800, 518400); - mockERC20.approve(address(brainPass), 60e18); + brainPass.mintNFT(0, 172800, 5184000); + mockERC20.approve(address(brainPass), 9000e18); assertEq(brainPass.balanceOf(alice), 0); - brainPass.mintNFT(0, 172800, 518400); + brainPass.mintNFT(0, 172800, 5184000); assertEq(brainPass.balanceOf(alice), 1); - assertEq(mockERC20.balanceOf(address(this)), 60e18); + assertEq(mockERC20.balanceOf(address(this)), 870e18); bool hasMinted = brainPass.addressToPassId(alice, 0); assertEq(hasMinted, true); vm.stopPrank(); @@ -67,19 +67,20 @@ contract TestEditor is PRBTest, Cheats { } function testIncreaseTime() public { - mockERC20.mint(alice, 200e18); + mockERC20.mint(alice, 20000e18); vm.startPrank(alice); - mockERC20.approve(address(brainPass), 120e18); + mockERC20.approve(address(brainPass), 12000e18); assertEq(brainPass.balanceOf(alice), 0); - brainPass.mintNFT(0, 172800, 518400); + brainPass.mintNFT(0, 172800, 5184000); assertEq(brainPass.balanceOf(alice), 1); uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; assertEq(_tokenId, 1); - brainPass.increasePassTime(_tokenId, 518400, 864000); - assertEq(mockERC20.balanceOf(address(this)), 120e18); + brainPass.increasePassTime(_tokenId, 5184000, 8640000); + console.log(mockERC20.balanceOf(address(this))); + assertEq(mockERC20.balanceOf(address(this)), 1470e18); brainPass.addressToNFTPass(alice, _tokenId); uint _startTime = brainPass.getUserPassDetails(alice, 0).startTimestamp; - assertEq(_startTime, 518400); + // assertEq(_startTime, 172800); } function testBaseTokenURI() public { From a74cb886123b33a6545c3b3f1f2a83ef7dc649e7 Mon Sep 17 00:00:00 2001 From: Adebayo Olamilekan <75235148+OleanjiKingCode@users.noreply.github.com> Date: Wed, 24 May 2023 21:23:12 +0100 Subject: [PATCH 14/54] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4c6d1ab..ba93aff 100644 --- a/README.md +++ b/README.md @@ -102,4 +102,6 @@ The contract emits the following events: - `TimeIncreased`: Emitted when the time of a BrainPass NFT is increased. - `NewPassAdded`: Emitted when a new pass type is added. +## PICTORAL EXPLANATION +![image](https://github.com/EveripediaNetwork/ep-contract/assets/75235148/eee4d631-28d9-4ca4-bc0e-62e5a02998a2) From ae58340d6164d24dd86292ad3adc961d92a3d41f Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 24 May 2023 22:19:24 +0100 Subject: [PATCH 15/54] test for the new contract update --- src/BrainPass/BrainPass.sol | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 310be0d..f475527 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -179,18 +179,21 @@ contract BrainPassCollectibles is ERC721, Owned { if (msg.sender != ownerOf(tokenId)) revert NotTheOwnerOfThisNft(); UserPassItem storage pass = addressToNFTPass[msg.sender][tokenId]; + uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); if (!success) revert IncreseTimePaymentFailed(); - - if(block.timestamp > pass.endTimestamp){ //not active + + if(block.timestamp > pass.endTimestamp ){ //not active pass.startTimestamp = newStartTime; // new start time pass.endTimestamp = newEndTime; //new end time }else {//active pass.endTimestamp = newEndTime; //new endtime } + + emit TimeIncreased( msg.sender, tokenId, @@ -238,12 +241,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// @param startTimestamp and endTimestamp are used to check if the duration is within the subscription timeframe function validatePassDuration(uint256 startTimestamp, uint256 endTimestamp) internal pure returns(bool){ uint256 durationInDays = (endTimestamp.sub(startTimestamp)) / SECONDS_IN_A_DAY; - - if (durationInDays >= DAYS_MINT_LOWER_LIMIT && durationInDays<= DAYS_MINT_UPPER_LIMIT) { - return true; - } else { - return false; - } + return durationInDays >= DAYS_MINT_LOWER_LIMIT && durationInDays <= DAYS_MINT_UPPER_LIMIT; } /// ----------------------------------------------------------------------- @@ -262,7 +260,7 @@ contract BrainPassCollectibles is ERC721, Owned { ) public view returns (UserPassItem memory) { PassType memory passType = passTypes[passId]; UserPassItem memory userToken; - for (uint256 i = 1; i < passType.maxTokens; i++) { + for (uint256 i = 0; i < passType.maxTokens; i++) { if (ownerOf(i) == user) { userToken = addressToNFTPass[msg.sender][i]; break; @@ -275,7 +273,7 @@ contract BrainPassCollectibles is ERC721, Owned { function getAllPassType() external view returns (PassType[] memory) { uint256 total = passIds.current(); PassType[] memory passType = new PassType[](total); - for (uint256 i = 1; i < total; i++) { + for (uint256 i = 0; i < total; i++) { passType[i] = passTypes[i]; } return passType; @@ -323,3 +321,11 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 _pricePerDay ); } + + + +initial Strtime = Jan 1 +Intial endtime = Jan 20th + +newStartime = Feb 15th +newEndtime= Feb 18th \ No newline at end of file From ef3731a1789f75d88e724f891bb9e779e3c3691a Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 24 May 2023 22:42:20 +0100 Subject: [PATCH 16/54] IncresePassTime Func Refined --- src/BrainPass/BrainPass.sol | 32 ++++++++++++-------------------- test/BrainPass.t.sol | 8 ++++---- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index f475527..3a1412c 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -173,26 +173,26 @@ contract BrainPassCollectibles is ERC721, Owned { /// @param tokenId The Id of the NFT whose time is to be increased function increasePassTime( uint256 tokenId, - uint newStartTime, uint256 newEndTime ) external { if (msg.sender != ownerOf(tokenId)) revert NotTheOwnerOfThisNft(); UserPassItem storage pass = addressToNFTPass[msg.sender][tokenId]; - uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); + uint256 newStartTime; + if( pass.endTimestamp < block.timestamp ){ + newStartTime= block.timestamp; + } else { + newStartTime = pass.endTimestamp; + } + + if (!validatePassDuration(newStartTime, newEndTime)) revert DurationNotInTimeFrame(); + uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); if (!success) revert IncreseTimePaymentFailed(); - if(block.timestamp > pass.endTimestamp ){ //not active - pass.startTimestamp = newStartTime; // new start time - pass.endTimestamp = newEndTime; //new end time - }else {//active - pass.endTimestamp = newEndTime; //new endtime - } - - + pass.endTimestamp = newEndTime; emit TimeIncreased( msg.sender, @@ -240,7 +240,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Validates the Timestamp Duration for minting Nft /// @param startTimestamp and endTimestamp are used to check if the duration is within the subscription timeframe function validatePassDuration(uint256 startTimestamp, uint256 endTimestamp) internal pure returns(bool){ - uint256 durationInDays = (endTimestamp.sub(startTimestamp)) / SECONDS_IN_A_DAY; + uint256 durationInDays = (endTimestamp.sub(startTimestamp)) / SECONDS_IN_A_DAY; return durationInDays >= DAYS_MINT_LOWER_LIMIT && durationInDays <= DAYS_MINT_UPPER_LIMIT; } @@ -260,7 +260,7 @@ contract BrainPassCollectibles is ERC721, Owned { ) public view returns (UserPassItem memory) { PassType memory passType = passTypes[passId]; UserPassItem memory userToken; - for (uint256 i = 0; i < passType.maxTokens; i++) { + for (uint256 i = 1; i < passType.maxTokens; i++) { if (ownerOf(i) == user) { userToken = addressToNFTPass[msg.sender][i]; break; @@ -321,11 +321,3 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 _pricePerDay ); } - - - -initial Strtime = Jan 1 -Intial endtime = Jan 20th - -newStartime = Feb 15th -newEndtime= Feb 18th \ No newline at end of file diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 8d42fae..1dd216e 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -60,9 +60,9 @@ contract TestEditor is PRBTest, Cheats { mockERC20.approve(address(brainPass), 120e18); brainPass.mintNFT(0, 172800, 518400); uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; - brainPass.increasePassTime(_tokenId, 518400, 864000); + brainPass.increasePassTime(_tokenId, 864000); vm.startPrank(bob); - brainPass.increasePassTime(_tokenId, 518400, 864000); + brainPass.increasePassTime(_tokenId, 864000); vm.expectRevert("NotTheOwnerOfThisNft"); } @@ -75,11 +75,11 @@ contract TestEditor is PRBTest, Cheats { assertEq(brainPass.balanceOf(alice), 1); uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; assertEq(_tokenId, 1); - brainPass.increasePassTime(_tokenId, 5184000, 8640000); + brainPass.increasePassTime(_tokenId, 8640000); console.log(mockERC20.balanceOf(address(this))); assertEq(mockERC20.balanceOf(address(this)), 1470e18); brainPass.addressToNFTPass(alice, _tokenId); - uint _startTime = brainPass.getUserPassDetails(alice, 0).startTimestamp; + //uint _startTime = brainPass.getUserPassDetails(alice, 0).startTimestamp; // assertEq(_startTime, 172800); } From 5ef8ca1362d97a3f5cc294707504f98efff3629f Mon Sep 17 00:00:00 2001 From: Damilare Bakare Date: Thu, 25 May 2023 12:43:36 +0100 Subject: [PATCH 17/54] changes variable names --- src/BrainPass/BrainPass.sol | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 3a1412c..a6f2724 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -81,7 +81,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// Variables /// ----------------------------------------------------------------------- string public baseTokenURI; - Counters.Counter private passIds; + Counters.Counter private passIdTracker; /// ----------------------------------------------------------------------- /// Constructor @@ -101,11 +101,10 @@ contract BrainPassCollectibles is ERC721, Owned { string memory tokenURI, string memory name, uint256 maxTokens, - uint256 discount + uint256 discount ) external onlyOwner { if (maxTokens <= 0) revert InvalidMaxTokensForAPass(); - - uint256 passId = passIds.current(); + uint256 passId = passIdTracker.current(); passTypes[passId] = PassType( passId, name, @@ -116,7 +115,7 @@ contract BrainPassCollectibles is ERC721, Owned { 0 ); - passIds.increment(); + passIdTracker.increment(); emit NewPassAdded(passId, name, maxTokens, pricePerDay); } @@ -222,9 +221,9 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 endTimestamp ) internal view returns (uint256) { PassType memory passType = passTypes[passId]; - uint256 duration = endTimestamp.sub(startTimestamp); + uint256 subscriptionPeriodInSeconds = endTimestamp.sub(startTimestamp); - uint256 subscriptionPeriodInDays = duration.div(SECONDS_IN_A_DAY); + uint256 subscriptionPeriodInDays = subscriptionPeriodInSeconds.div(SECONDS_IN_A_DAY); // Calculate the total price uint256 totalPrice = subscriptionPeriodInDays.mul(passType.pricePerDay); @@ -260,7 +259,7 @@ contract BrainPassCollectibles is ERC721, Owned { ) public view returns (UserPassItem memory) { PassType memory passType = passTypes[passId]; UserPassItem memory userToken; - for (uint256 i = 1; i < passType.maxTokens; i++) { + for (uint256 i = 1; i < passType.maxTokens; i++) { // use mapping instead if (ownerOf(i) == user) { userToken = addressToNFTPass[msg.sender][i]; break; @@ -271,7 +270,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Gets all the PassType created function getAllPassType() external view returns (PassType[] memory) { - uint256 total = passIds.current(); + uint256 total = passIdTracker.current(); PassType[] memory passType = new PassType[](total); for (uint256 i = 0; i < total; i++) { passType[i] = passTypes[i]; From 9a9bc99a72bf475bd8f172416ac7f3cc414a9487 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Thu, 25 May 2023 13:17:22 +0100 Subject: [PATCH 18/54] removed for loop --- src/BrainPass/BrainPass.sol | 59 ++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index a6f2724..75557a1 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -67,6 +67,7 @@ contract BrainPassCollectibles is ERC721, Owned { mapping(uint256 => PassType) public passTypes; mapping(address => mapping(uint256 => UserPassItem)) public addressToNFTPass; + mapping(uint256 => mapping(address => UserPassItem)) internal ownerOfToken; mapping(address => mapping(uint256 => bool)) public addressToPassId; /// ----------------------------------------------------------------------- @@ -101,7 +102,7 @@ contract BrainPassCollectibles is ERC721, Owned { string memory tokenURI, string memory name, uint256 maxTokens, - uint256 discount + uint256 discount ) external onlyOwner { if (maxTokens <= 0) revert InvalidMaxTokensForAPass(); uint256 passId = passIdTracker.current(); @@ -114,9 +115,7 @@ contract BrainPassCollectibles is ERC721, Owned { discount, 0 ); - passIdTracker.increment(); - emit NewPassAdded(passId, name, maxTokens, pricePerDay); } @@ -136,7 +135,8 @@ contract BrainPassCollectibles is ERC721, Owned { if (passType.lastTokenIdMinted.add(1) >= passType.maxTokens) revert PassMaxSupplyReached(); - if (!validatePassDuration(startTimestamp, endTimestamp)) revert DurationNotInTimeFrame(); + if (!validatePassDuration(startTimestamp, endTimestamp)) + revert DurationNotInTimeFrame(); uint256 price = calculatePrice(passId, startTimestamp, endTimestamp); @@ -144,15 +144,15 @@ contract BrainPassCollectibles is ERC721, Owned { if (!success) revert MintingPaymentFailed(); uint256 tokenId = passType.lastTokenIdMinted + 1; - - addressToPassId[msg.sender][passId] = true; UserPassItem memory purchase = UserPassItem( tokenId, passId, startTimestamp, endTimestamp ); + addressToPassId[msg.sender][passId] = true; addressToNFTPass[msg.sender][tokenId] = purchase; + ownerOfToken[passId][msg.sender] = purchase; passType.lastTokenIdMinted = tokenId; _safeMint(msg.sender, tokenId); @@ -167,25 +167,21 @@ contract BrainPassCollectibles is ERC721, Owned { ); } - /// @notice Increase the time to hold a PassNft /// @param tokenId The Id of the NFT whose time is to be increased - function increasePassTime( - uint256 tokenId, - uint256 newEndTime - ) external { - if (msg.sender != ownerOf(tokenId)) revert NotTheOwnerOfThisNft(); - - UserPassItem storage pass = addressToNFTPass[msg.sender][tokenId]; - + function increasePassTime(uint256 tokenId, uint256 newEndTime) external { + UserPassItem memory pass = addressToNFTPass[msg.sender][tokenId]; + if (!addressToPassId[msg.sender][pass.passId]) + revert NotTheOwnerOfThisNft(); uint256 newStartTime; - if( pass.endTimestamp < block.timestamp ){ - newStartTime= block.timestamp; + if (pass.endTimestamp < block.timestamp) { + newStartTime = block.timestamp; } else { - newStartTime = pass.endTimestamp; + newStartTime = pass.endTimestamp; } - if (!validatePassDuration(newStartTime, newEndTime)) revert DurationNotInTimeFrame(); + if (!validatePassDuration(newStartTime, newEndTime)) + revert DurationNotInTimeFrame(); uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); @@ -223,7 +219,9 @@ contract BrainPassCollectibles is ERC721, Owned { PassType memory passType = passTypes[passId]; uint256 subscriptionPeriodInSeconds = endTimestamp.sub(startTimestamp); - uint256 subscriptionPeriodInDays = subscriptionPeriodInSeconds.div(SECONDS_IN_A_DAY); + uint256 subscriptionPeriodInDays = subscriptionPeriodInSeconds.div( + SECONDS_IN_A_DAY + ); // Calculate the total price uint256 totalPrice = subscriptionPeriodInDays.mul(passType.pricePerDay); @@ -238,9 +236,15 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Validates the Timestamp Duration for minting Nft /// @param startTimestamp and endTimestamp are used to check if the duration is within the subscription timeframe - function validatePassDuration(uint256 startTimestamp, uint256 endTimestamp) internal pure returns(bool){ - uint256 durationInDays = (endTimestamp.sub(startTimestamp)) / SECONDS_IN_A_DAY; - return durationInDays >= DAYS_MINT_LOWER_LIMIT && durationInDays <= DAYS_MINT_UPPER_LIMIT; + function validatePassDuration( + uint256 startTimestamp, + uint256 endTimestamp + ) internal pure returns (bool) { + uint256 durationInDays = (endTimestamp.sub(startTimestamp)) / + SECONDS_IN_A_DAY; + return + durationInDays >= DAYS_MINT_LOWER_LIMIT && + durationInDays <= DAYS_MINT_UPPER_LIMIT; } /// ----------------------------------------------------------------------- @@ -257,14 +261,7 @@ contract BrainPassCollectibles is ERC721, Owned { address user, uint passId ) public view returns (UserPassItem memory) { - PassType memory passType = passTypes[passId]; - UserPassItem memory userToken; - for (uint256 i = 1; i < passType.maxTokens; i++) { // use mapping instead - if (ownerOf(i) == user) { - userToken = addressToNFTPass[msg.sender][i]; - break; - } - } + UserPassItem memory userToken = ownerOfToken[passId][user]; return userToken; } From 77d41836d7f116baecc8d18bb6733b52f9ef1ae2 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Thu, 25 May 2023 13:32:21 +0100 Subject: [PATCH 19/54] renmoved a mapping --- src/BrainPass/BrainPass.sol | 10 ++++------ test/BrainPass.t.sol | 13 +++++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 75557a1..a45aa47 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -26,7 +26,7 @@ contract BrainPassCollectibles is ERC721, Owned { error MintingPaymentFailed(); error IncreseTimePaymentFailed(); error UserBalanceNotEnough(); - error MaxPassNFTsReached(); + error AlreadyMintedThisPass(); error NotTheOwnerOfThisNft(); error InvalidMaxTokensForAPass(); error PassTypeNotFound(); @@ -68,7 +68,6 @@ contract BrainPassCollectibles is ERC721, Owned { mapping(address => mapping(uint256 => UserPassItem)) public addressToNFTPass; mapping(uint256 => mapping(address => UserPassItem)) internal ownerOfToken; - mapping(address => mapping(uint256 => bool)) public addressToPassId; /// ----------------------------------------------------------------------- /// Constant @@ -126,8 +125,8 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 startTimestamp, uint256 endTimestamp ) external { - if (addressToPassId[msg.sender][passId] == true) - revert MaxPassNFTsReached(); + if (getUserPassDetails(msg.sender, passId).tokenId != 0) + revert AlreadyMintedThisPass(); PassType storage passType = passTypes[passId]; @@ -150,7 +149,6 @@ contract BrainPassCollectibles is ERC721, Owned { startTimestamp, endTimestamp ); - addressToPassId[msg.sender][passId] = true; addressToNFTPass[msg.sender][tokenId] = purchase; ownerOfToken[passId][msg.sender] = purchase; passType.lastTokenIdMinted = tokenId; @@ -171,7 +169,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// @param tokenId The Id of the NFT whose time is to be increased function increasePassTime(uint256 tokenId, uint256 newEndTime) external { UserPassItem memory pass = addressToNFTPass[msg.sender][tokenId]; - if (!addressToPassId[msg.sender][pass.passId]) + if (getUserPassDetails(msg.sender, pass.passId).tokenId != tokenId) revert NotTheOwnerOfThisNft(); uint256 newStartTime; if (pass.endTimestamp < block.timestamp) { diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 1dd216e..a65bb43 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -35,7 +35,7 @@ contract TestEditor is PRBTest, Cheats { brainPass.mintNFT(0, 172800, 518400); assertEq(brainPass.balanceOf(alice), 1); brainPass.mintNFT(0, 172800, 518400); - vm.expectRevert("MaxPassNFTsReached"); + vm.expectRevert("AlreadyMintedThisPass"); } function testmintNFT() public { @@ -49,8 +49,8 @@ contract TestEditor is PRBTest, Cheats { brainPass.mintNFT(0, 172800, 5184000); assertEq(brainPass.balanceOf(alice), 1); assertEq(mockERC20.balanceOf(address(this)), 870e18); - bool hasMinted = brainPass.addressToPassId(alice, 0); - assertEq(hasMinted, true); + uint256 mintedPass = brainPass.getUserPassDetails(alice, 0).passId; + assertEq(mintedPass, 0); vm.stopPrank(); } @@ -79,8 +79,8 @@ contract TestEditor is PRBTest, Cheats { console.log(mockERC20.balanceOf(address(this))); assertEq(mockERC20.balanceOf(address(this)), 1470e18); brainPass.addressToNFTPass(alice, _tokenId); - //uint _startTime = brainPass.getUserPassDetails(alice, 0).startTimestamp; - // assertEq(_startTime, 172800); + uint _startTime = brainPass.getUserPassDetails(alice, 0).startTimestamp; + assertEq(_startTime, 172800); } function testBaseTokenURI() public { @@ -101,6 +101,7 @@ contract TestEditor is PRBTest, Cheats { assertEq(brainPass.getPassType(1).name, "Platinum"); assertEq(brainPass.getAllPassType().length, 2); } + //function testGetAllPassType() public {} - //todo : upgrade /downgrade after subscription has ended + } From 0301aa2f36c785d706e579acde4eba88a50fe176 Mon Sep 17 00:00:00 2001 From: Damilare Bakare Date: Thu, 25 May 2023 14:02:54 +0100 Subject: [PATCH 20/54] removes unused script --- script/BrainPassMinter.sol | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 script/BrainPassMinter.sol diff --git a/script/BrainPassMinter.sol b/script/BrainPassMinter.sol deleted file mode 100644 index e82dd83..0000000 --- a/script/BrainPassMinter.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script} from "../lib/forge-std/src/Script.sol"; -import {console} from "../lib/forge-std/src/console.sol"; -import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; - -contract BrainPassMinter is Script { - address constant owner = address(0xaCa39B187352D9805DECEd6E73A3d72ABf86E7A0); - - function run() external { - vm.startBroadcast(); - - - vm.stopBroadcast(); - } -} From 7444fee708cb468ed05546620747dd8855364b3d Mon Sep 17 00:00:00 2001 From: Damilare Bakare Date: Thu, 25 May 2023 14:34:26 +0100 Subject: [PATCH 21/54] changes function name --- src/BrainPass/BrainPass.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index a45aa47..72de53a 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -167,7 +167,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Increase the time to hold a PassNft /// @param tokenId The Id of the NFT whose time is to be increased - function increasePassTime(uint256 tokenId, uint256 newEndTime) external { + function increaseEndTime(uint256 tokenId, uint256 newEndTime) external { UserPassItem memory pass = addressToNFTPass[msg.sender][tokenId]; if (getUserPassDetails(msg.sender, pass.passId).tokenId != tokenId) revert NotTheOwnerOfThisNft(); From 3bbca93709667ec923c16c5d2f18e9815dd4107d Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Thu, 25 May 2023 14:47:46 +0100 Subject: [PATCH 22/54] corrected the test script --- src/BrainPass/BrainPass.sol | 10 +++++++++- test/BrainPass.t.sol | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 72de53a..efca8b9 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -185,7 +185,15 @@ contract BrainPassCollectibles is ERC721, Owned { bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); if (!success) revert IncreseTimePaymentFailed(); - pass.endTimestamp = newEndTime; + UserPassItem memory purchase = UserPassItem( + pass.tokenId, + pass.passId, + pass.startTimestamp, + newEndTime + ); + + addressToNFTPass[msg.sender][tokenId] = purchase; + ownerOfToken[pass.passId][msg.sender] = purchase; emit TimeIncreased( msg.sender, diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index a65bb43..823c3c7 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -60,9 +60,9 @@ contract TestEditor is PRBTest, Cheats { mockERC20.approve(address(brainPass), 120e18); brainPass.mintNFT(0, 172800, 518400); uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; - brainPass.increasePassTime(_tokenId, 864000); + brainPass.increaseEndTime(_tokenId, 864000); vm.startPrank(bob); - brainPass.increasePassTime(_tokenId, 864000); + brainPass.increaseEndTime(_tokenId, 864000); vm.expectRevert("NotTheOwnerOfThisNft"); } @@ -75,7 +75,7 @@ contract TestEditor is PRBTest, Cheats { assertEq(brainPass.balanceOf(alice), 1); uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; assertEq(_tokenId, 1); - brainPass.increasePassTime(_tokenId, 8640000); + brainPass.increaseEndTime(_tokenId, 8640000); console.log(mockERC20.balanceOf(address(this))); assertEq(mockERC20.balanceOf(address(this)), 1470e18); brainPass.addressToNFTPass(alice, _tokenId); From f7c7a5dff9c0a2e7096811482df5a2ca92c9645a Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Thu, 25 May 2023 21:23:10 +0100 Subject: [PATCH 23/54] removing unused error and added maxSupplyReached test --- src/BrainPass/BrainPass.sol | 1 - test/BrainPass.t.sol | 29 +++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index efca8b9..6273da6 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -25,7 +25,6 @@ contract BrainPassCollectibles is ERC721, Owned { error MintingPaymentFailed(); error IncreseTimePaymentFailed(); - error UserBalanceNotEnough(); error AlreadyMintedThisPass(); error NotTheOwnerOfThisNft(); error InvalidMaxTokensForAPass(); diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 823c3c7..4a396be 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -13,6 +13,7 @@ contract TestEditor is PRBTest, Cheats { BrainPassCollectibles internal brainPass; address private alice = address(0x2); address private bob = address(0x3); + address private doe = address(0x4); MockERC20 private mockERC20; function setUp() public { @@ -79,8 +80,8 @@ contract TestEditor is PRBTest, Cheats { console.log(mockERC20.balanceOf(address(this))); assertEq(mockERC20.balanceOf(address(this)), 1470e18); brainPass.addressToNFTPass(alice, _tokenId); - uint _startTime = brainPass.getUserPassDetails(alice, 0).startTimestamp; - assertEq(_startTime, 172800); + uint _endTine = brainPass.getUserPassDetails(alice, 0).endTimestamp; + assertEq(_endTine, 8640000); } function testBaseTokenURI() public { @@ -101,7 +102,27 @@ contract TestEditor is PRBTest, Cheats { assertEq(brainPass.getPassType(1).name, "Platinum"); assertEq(brainPass.getAllPassType().length, 2); } - //function testGetAllPassType() public {} - + function testFailPassMaxSupplyReached() public { + brainPass.addPassType(15, "http://example.orgs", "OleanjiPass", 2, 0); + mockERC20.mint(alice, 20000e18); + mockERC20.mint(bob, 20000e18); + mockERC20.mint(doe, 20000e18); + vm.startPrank(alice); + mockERC20.approve(address(brainPass), 1700e18); + assertEq(brainPass.balanceOf(alice), 0); + brainPass.mintNFT(1, 172800, 5184000); + assertEq(brainPass.balanceOf(alice), 1); + vm.stopPrank(); + vm.startPrank(bob); + mockERC20.approve(address(brainPass), 1700e18); + brainPass.mintNFT(1, 172800, 5184000); + assertEq(brainPass.balanceOf(bob), 1); + vm.stopPrank(); + vm.startPrank(doe); + mockERC20.approve(address(brainPass), 1700e18); + + brainPass.mintNFT(1, 172800, 5184000); + vm.expectRevert("PassMaxSupplyReached()"); + } } From 888dc99d268240012c721166458af0e9ef5b8413 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Sat, 27 May 2023 22:22:39 +0100 Subject: [PATCH 24/54] little fix in maxtokenminted per pass --- src/BrainPass/BrainPass.sol | 2 +- test/BrainPass.t.sol | 121 ++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 6273da6..8033c9c 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -130,7 +130,7 @@ contract BrainPassCollectibles is ERC721, Owned { PassType storage passType = passTypes[passId]; if (passType.maxTokens == 0) revert PassTypeNotFound(); - if (passType.lastTokenIdMinted.add(1) >= passType.maxTokens) + if (passType.lastTokenIdMinted >= passType.maxTokens) revert PassMaxSupplyReached(); if (!validatePassDuration(startTimestamp, endTimestamp)) diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 4a396be..5372726 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -10,119 +10,120 @@ import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol"; import {MockERC20} from "../lib/solmate/src/test/utils/mocks/MockERC20.sol"; contract TestEditor is PRBTest, Cheats { - BrainPassCollectibles internal brainPass; - address private alice = address(0x2); - address private bob = address(0x3); - address private doe = address(0x4); - MockERC20 private mockERC20; + BrainPassCollectibles BrainPass; + address alice = vm.addr(0x2); + address bob = vm.addr(0x3); + address doe = vm.addr(0x4); + MockERC20 mockERC20; function setUp() public { mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token - brainPass = new BrainPassCollectibles(address(mockERC20)); - brainPass.addPassType(15, "http://example.com/1", "Gold", 200, 0); + BrainPass = new BrainPassCollectibles(address(mockERC20)); + BrainPass.addPassType(15, "http://example.com/1", "Gold", 200, 0); } function testAddPassType() public { - brainPass.addPassType(15, "http://example.com/56", "Gold", 200, 0); - string memory _name = brainPass.getPassType(1).name; + BrainPass.addPassType(15, "http://example.com/56", "Gold", 200, 0); + string memory _name = BrainPass.getPassType(1).name; assertEq(_name, "Gold"); } - function testFailMintNFT() public { - mockERC20.mint(alice, 300e18); + function testmintNFTWrong() public { + mockERC20.mint(alice, 20000e18); vm.startPrank(alice); - mockERC20.approve(address(brainPass), 120e18); - assertEq(brainPass.balanceOf(alice), 0); - brainPass.mintNFT(0, 172800, 518400); - assertEq(brainPass.balanceOf(alice), 1); - brainPass.mintNFT(0, 172800, 518400); - vm.expectRevert("AlreadyMintedThisPass"); + mockERC20.approve(address(BrainPass), 9000e18); + assertEq(BrainPass.balanceOf(alice), 0); + BrainPass.mintNFT(0, 172800, 5184000); + assertEq(BrainPass.balanceOf(alice), 1); + vm.expectRevert(BrainPassCollectibles.AlreadyMintedThisPass.selector); + BrainPass.mintNFT(0, 172800, 5184000); + vm.stopPrank(); } function testmintNFT() public { mockERC20.mint(alice, 20000e18); vm.startPrank(alice); - mockERC20.approve(address(brainPass), 3e18); + mockERC20.approve(address(BrainPass), 3e18); vm.expectRevert(stdError.arithmeticError); - brainPass.mintNFT(0, 172800, 5184000); - mockERC20.approve(address(brainPass), 9000e18); - assertEq(brainPass.balanceOf(alice), 0); - brainPass.mintNFT(0, 172800, 5184000); - assertEq(brainPass.balanceOf(alice), 1); + BrainPass.mintNFT(0, 172800, 5184000); + mockERC20.approve(address(BrainPass), 9000e18); + assertEq(BrainPass.balanceOf(alice), 0); + BrainPass.mintNFT(0, 172800, 5184000); + assertEq(BrainPass.balanceOf(alice), 1); assertEq(mockERC20.balanceOf(address(this)), 870e18); - uint256 mintedPass = brainPass.getUserPassDetails(alice, 0).passId; + uint256 mintedPass = BrainPass.getUserPassDetails(alice, 0).passId; assertEq(mintedPass, 0); vm.stopPrank(); } - function testFailIncreaseTime() public { - mockERC20.mint(alice, 300e18); + function testIncreaseTimeWrong() public { + mockERC20.mint(alice, 3000e18); vm.startPrank(alice); - mockERC20.approve(address(brainPass), 120e18); - brainPass.mintNFT(0, 172800, 518400); - uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; - brainPass.increaseEndTime(_tokenId, 864000); + mockERC20.approve(address(BrainPass), 9000e18); + BrainPass.mintNFT(0, 172800, 5184000); + uint256 _tokenId = BrainPass.getUserPassDetails(alice, 0).tokenId; + BrainPass.increaseEndTime(_tokenId, 8640000); + vm.stopPrank(); vm.startPrank(bob); - brainPass.increaseEndTime(_tokenId, 864000); - vm.expectRevert("NotTheOwnerOfThisNft"); + vm.expectRevert(BrainPassCollectibles.NotTheOwnerOfThisNft.selector); + BrainPass.increaseEndTime(_tokenId, 8640000); } function testIncreaseTime() public { mockERC20.mint(alice, 20000e18); vm.startPrank(alice); - mockERC20.approve(address(brainPass), 12000e18); - assertEq(brainPass.balanceOf(alice), 0); - brainPass.mintNFT(0, 172800, 5184000); - assertEq(brainPass.balanceOf(alice), 1); - uint256 _tokenId = brainPass.getUserPassDetails(alice, 0).tokenId; + mockERC20.approve(address(BrainPass), 12000e18); + assertEq(BrainPass.balanceOf(alice), 0); + BrainPass.mintNFT(0, 172800, 5184000); + assertEq(BrainPass.balanceOf(alice), 1); + uint256 _tokenId = BrainPass.getUserPassDetails(alice, 0).tokenId; assertEq(_tokenId, 1); - brainPass.increaseEndTime(_tokenId, 8640000); + BrainPass.increaseEndTime(_tokenId, 8640000); console.log(mockERC20.balanceOf(address(this))); assertEq(mockERC20.balanceOf(address(this)), 1470e18); - brainPass.addressToNFTPass(alice, _tokenId); - uint _endTine = brainPass.getUserPassDetails(alice, 0).endTimestamp; + BrainPass.addressToNFTPass(alice, _tokenId); + uint _endTine = BrainPass.getUserPassDetails(alice, 0).endTimestamp; assertEq(_endTine, 8640000); } function testBaseTokenURI() public { - assertEq(brainPass.baseTokenURI(), ""); - brainPass.setBaseURI("http://example.org.com/565"); - assertEq(brainPass.baseTokenURI(), "http://example.org.com/565"); + assertEq(BrainPass.baseTokenURI(), ""); + BrainPass.setBaseURI("http://example.org.com/565"); + assertEq(BrainPass.baseTokenURI(), "http://example.org.com/565"); } function testGetAllPassType() public { - assertEq(brainPass.getAllPassType().length, 1); - brainPass.addPassType( + assertEq(BrainPass.getAllPassType().length, 1); + BrainPass.addPassType( 400, "http://example.com/56", "Platinum", 3000, 10 ); - assertEq(brainPass.getPassType(1).name, "Platinum"); - assertEq(brainPass.getAllPassType().length, 2); + assertEq(BrainPass.getPassType(1).name, "Platinum"); + assertEq(BrainPass.getAllPassType().length, 2); } - function testFailPassMaxSupplyReached() public { - brainPass.addPassType(15, "http://example.orgs", "OleanjiPass", 2, 0); + function testMintNftWrong() public { + BrainPass.addPassType(15, "http://example.orgs", "OleanjiPass", 2, 0); mockERC20.mint(alice, 20000e18); mockERC20.mint(bob, 20000e18); mockERC20.mint(doe, 20000e18); vm.startPrank(alice); - mockERC20.approve(address(brainPass), 1700e18); - assertEq(brainPass.balanceOf(alice), 0); - brainPass.mintNFT(1, 172800, 5184000); - assertEq(brainPass.balanceOf(alice), 1); + mockERC20.approve(address(BrainPass), 1700e18); + assertEq(BrainPass.balanceOf(alice), 0); + BrainPass.mintNFT(1, 172800, 5184000); + assertEq(BrainPass.balanceOf(alice), 1); vm.stopPrank(); vm.startPrank(bob); - mockERC20.approve(address(brainPass), 1700e18); - brainPass.mintNFT(1, 172800, 5184000); - assertEq(brainPass.balanceOf(bob), 1); + mockERC20.approve(address(BrainPass), 1700e18); + BrainPass.mintNFT(1, 172800, 5184000); + assertEq(BrainPass.balanceOf(bob), 1); vm.stopPrank(); vm.startPrank(doe); - mockERC20.approve(address(brainPass), 1700e18); - - brainPass.mintNFT(1, 172800, 5184000); - vm.expectRevert("PassMaxSupplyReached()"); + mockERC20.approve(address(BrainPass), 1700e18); + vm.expectRevert(BrainPassCollectibles.PassMaxSupplyReached.selector); + BrainPass.mintNFT(1, 172800, 5184000); } } From 66967643c6cf574bea562b25c41137a80d9d277e Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Sun, 28 May 2023 11:13:35 +0100 Subject: [PATCH 25/54] describing functions well --- src/BrainPass/BrainPass.sol | 25 ++++++++++++++++++++++--- test/BrainPass.t.sol | 11 +++++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 8033c9c..155cfe1 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -37,12 +37,14 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Inheritances /// ----------------------------------------------------------------------- + using SafeMath for uint256; using Counters for Counters.Counter; /// ----------------------------------------------------------------------- /// Structs /// ----------------------------------------------------------------------- + struct UserPassItem { uint256 tokenId; uint256 passId; @@ -63,6 +65,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Mappings /// ----------------------------------------------------------------------- + mapping(uint256 => PassType) public passTypes; mapping(address => mapping(uint256 => UserPassItem)) public addressToNFTPass; @@ -71,6 +74,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Constant /// ----------------------------------------------------------------------- + address public immutable iqToken; uint256 immutable SECONDS_IN_A_DAY = 86400; uint256 immutable DAYS_MINT_LOWER_LIMIT = 28; @@ -79,12 +83,14 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Variables /// ----------------------------------------------------------------------- + string public baseTokenURI; Counters.Counter private passIdTracker; /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- + constructor(address IqAddr) ERC721("BAINPASS", "BEP") Owned(msg.sender) { iqToken = IqAddr; } @@ -94,7 +100,11 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// @notice Add a new Pass Type - /// @param name and others are the details needed for a passType + /// @param pricePerDay the price per day of the new pass type + /// @param tokenURI the link that stores the data of all the Nfts in the new pass + /// @param name the name of the new pass type to be added + /// @param maxTokens the total number of tokens in the pass + /// @param discount the amount in % to be deducted when buying the pass function addPassType( uint256 pricePerDay, string memory tokenURI, @@ -119,6 +129,8 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Mint and NFT of a particular passtype /// @param passId The id of the passtype to mint + /// @param startTimestamp The time when the NFT subcription time starts + /// @param endTimestamp The time when the NFT subcription time ends function mintNFT( uint256 passId, uint256 startTimestamp, @@ -166,6 +178,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Increase the time to hold a PassNft /// @param tokenId The Id of the NFT whose time is to be increased + /// @param newEndTime The new subcription endTime for the of the NFT function increaseEndTime(uint256 tokenId, uint256 newEndTime) external { UserPassItem memory pass = addressToNFTPass[msg.sender][tokenId]; if (getUserPassDetails(msg.sender, pass.passId).tokenId != tokenId) @@ -215,7 +228,8 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// @notice Calculate the price of an Nft - /// @param startTimestamp and endTimestamp are used to calc the price to be paid + /// @param startTimestamp The start time to calculate the price of the Nft + /// @param endTimestamp The end time to calculate the price of the Nft function calculatePrice( uint256 passId, uint256 startTimestamp, @@ -240,7 +254,8 @@ contract BrainPassCollectibles is ERC721, Owned { } /// @notice Validates the Timestamp Duration for minting Nft - /// @param startTimestamp and endTimestamp are used to check if the duration is within the subscription timeframe + /// @param startTimestamp The start time for checking the validity of a pass + /// @param endTimestamp The end time for checking the validity of a pass function validatePassDuration( uint256 startTimestamp, uint256 endTimestamp @@ -262,6 +277,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Gets all the NFT owned by an address /// @param user The address of the user + /// @param passId The Id of the pass to get the user's info on function getUserPassDetails( address user, uint passId @@ -292,6 +308,9 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Setters /// ----------------------------------------------------------------------- + + /// @notice Sets the tokenURI where the NFT data is gotten + /// @param tokenURI The tokenURI to be set function setBaseURI(string memory tokenURI) public { baseTokenURI = tokenURI; } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 5372726..af12274 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.13; import {PRBTest} from "prb-test/PRBTest.sol"; import {Cheats} from "forge-std/Cheats.sol"; -import "forge-std/console.sol"; import {stdError} from "forge-std/Errors.sol"; import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol"; @@ -40,6 +39,15 @@ contract TestEditor is PRBTest, Cheats { vm.stopPrank(); } + function testMintDurationNotInTimeFrame() public { + mockERC20.mint(alice, 20000e18); + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 9000e18); + vm.expectRevert(BrainPassCollectibles.DurationNotInTimeFrame.selector); + BrainPass.mintNFT(0, 172800, 518400); + vm.stopPrank(); + } + function testmintNFT() public { mockERC20.mint(alice, 20000e18); vm.startPrank(alice); @@ -79,7 +87,6 @@ contract TestEditor is PRBTest, Cheats { uint256 _tokenId = BrainPass.getUserPassDetails(alice, 0).tokenId; assertEq(_tokenId, 1); BrainPass.increaseEndTime(_tokenId, 8640000); - console.log(mockERC20.balanceOf(address(this))); assertEq(mockERC20.balanceOf(address(this)), 1470e18); BrainPass.addressToNFTPass(alice, _tokenId); uint _endTine = BrainPass.getUserPassDetails(alice, 0).endTimestamp; From b21b030854b1243cb945948046b128c8b36d4acd Mon Sep 17 00:00:00 2001 From: Adebayo Olamilekan <75235148+OleanjiKingCode@users.noreply.github.com> Date: Sun, 28 May 2023 11:30:09 +0100 Subject: [PATCH 26/54] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: César Rodríguez --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba93aff..6ebccc5 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ forge script script/BrainPassDeployer.s.sol:BrainPassDeployer --rpc-url $RPC_URL # Expalaining How BrainPassCollectibles Works -BrainPassCollectibles is a Solidity smart contract that enables the creation and management of BrainPass NFTs. It allows users to buy passes to have some Wiki previledges on IQ Wiki and provides functionalities to mint NFTs, increase pass time, and manage pass types. +BrainPassCollectibles is a Solidity smart contract that enables the creation and management of BrainPass NFTs. It allows users to buy passes to have some Wiki privileges on IQ Wiki and provides functionalities to mint NFTs, increase pass time, and manage pass types. ## Usage From 639d9a077b0f26bd0a3396837b838d0044bd8675 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Sun, 28 May 2023 18:35:33 +0100 Subject: [PATCH 27/54] more tests and pause Func --- README.md | 1 - src/BrainPass/BrainPass.sol | 69 ++++++++++++++++++++++------ test/BrainPass.t.sol | 91 ++++++++++++++++++++++++++++--------- 3 files changed, 126 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 6ebccc5..6e1d4cd 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,6 @@ Call the `mintNFT` function with the following parameters: This function is ued to increase the duration for which a pass is owned for. Call the `increasePassTime` function with the following parameters: - `tokenId`: The ID of the NFT whose time should be increased. -- `newStartTime`: The new start timestamp for the pass. - `newEndTime`: The new end timestamp for the pass. ### Get User Pass Details diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 155cfe1..ec1605d 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -12,6 +12,10 @@ interface IERC20 { address to, uint256 amount ) external returns (bool); + + function balanceOf(address account) external view returns (uint256); + + function transfer(address to, uint256 amount) external returns (bool); } /// @title BRAIN Pass NFT @@ -33,6 +37,8 @@ contract BrainPassCollectibles is ERC721, Owned { error NoEtherLeftToWithdraw(); error TransferFailed(); error DurationNotInTimeFrame(); + error CannotMintPausedPassType(); + error NoIQLeftToWithdraw(); /// ----------------------------------------------------------------------- /// Inheritances @@ -60,6 +66,7 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 maxTokens; uint256 discount; uint256 lastTokenIdMinted; + bool isPaused; } /// ----------------------------------------------------------------------- @@ -75,10 +82,10 @@ contract BrainPassCollectibles is ERC721, Owned { /// Constant /// ----------------------------------------------------------------------- - address public immutable iqToken; - uint256 immutable SECONDS_IN_A_DAY = 86400; - uint256 immutable DAYS_MINT_LOWER_LIMIT = 28; - uint256 immutable DAYS_MINT_UPPER_LIMIT = 365; + address public iqToken; + uint256 constant SECONDS_IN_A_DAY = 1 days; + uint256 constant DAYS_MINT_LOWER_LIMIT = 28; + uint256 constant DAYS_MINT_UPPER_LIMIT = 365; /// ----------------------------------------------------------------------- /// Variables @@ -90,9 +97,10 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- - + constructor(address IqAddr) ERC721("BAINPASS", "BEP") Owned(msg.sender) { iqToken = IqAddr; + passIdTracker.increment(); } /// ----------------------------------------------------------------------- @@ -100,7 +108,7 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// @notice Add a new Pass Type - /// @param pricePerDay the price per day of the new pass type + /// @param pricePerDay the price per day of the new pass type /// @param tokenURI the link that stores the data of all the Nfts in the new pass /// @param name the name of the new pass type to be added /// @param maxTokens the total number of tokens in the pass @@ -121,12 +129,33 @@ contract BrainPassCollectibles is ERC721, Owned { tokenURI, maxTokens, discount, - 0 + 0, + false ); passIdTracker.increment(); emit NewPassAdded(passId, name, maxTokens, pricePerDay); } + /// @notice Pause a Pass Type + /// @param passId the Id of the pass to be deactivated + function pausePassType(uint256 passId) external onlyOwner { + if (passId >= passIdTracker.current()) revert PassTypeNotFound(); + PassType storage passType = passTypes[passId]; + + passTypes[passId] = PassType( + passType.passId, + passType.name, + passType.pricePerDay, + passType.tokenURI, + passType.maxTokens, + passType.discount, + passType.discount, + true + ); + + emit PassTypePaused(passId, passType.name); + } + /// @notice Mint and NFT of a particular passtype /// @param passId The id of the passtype to mint /// @param startTimestamp The time when the NFT subcription time starts @@ -136,12 +165,13 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 startTimestamp, uint256 endTimestamp ) external { + if (passId >= passIdTracker.current()) revert PassTypeNotFound(); if (getUserPassDetails(msg.sender, passId).tokenId != 0) revert AlreadyMintedThisPass(); PassType storage passType = passTypes[passId]; - if (passType.maxTokens == 0) revert PassTypeNotFound(); + if (passType.isPaused) revert CannotMintPausedPassType(); if (passType.lastTokenIdMinted >= passType.maxTokens) revert PassMaxSupplyReached(); @@ -164,8 +194,8 @@ contract BrainPassCollectibles is ERC721, Owned { ownerOfToken[passId][msg.sender] = purchase; passType.lastTokenIdMinted = tokenId; - _safeMint(msg.sender, tokenId); setBaseURI(passType.tokenURI); + _safeMint(msg.sender, tokenId); emit BrainPassBought( msg.sender, @@ -181,6 +211,10 @@ contract BrainPassCollectibles is ERC721, Owned { /// @param newEndTime The new subcription endTime for the of the NFT function increaseEndTime(uint256 tokenId, uint256 newEndTime) external { UserPassItem memory pass = addressToNFTPass[msg.sender][tokenId]; + + PassType storage passType = passTypes[pass.passId]; + if (passType.isPaused) revert CannotMintPausedPassType(); + if (getUserPassDetails(msg.sender, pass.passId).tokenId != tokenId) revert NotTheOwnerOfThisNft(); uint256 newStartTime; @@ -217,9 +251,16 @@ contract BrainPassCollectibles is ERC721, Owned { /// @notice Withdraws any amount in the contract function withdraw() external payable onlyOwner { - uint256 balance = address(this).balance; - if (balance <= 0) revert NoEtherLeftToWithdraw(); - (bool success, ) = (msg.sender).call{value: balance}(""); + uint256 tokenBalance = IERC20(iqToken).balanceOf(address(this)); + uint256 ethbalance = address(this).balance; + + if (tokenBalance <= 0) revert NoIQLeftToWithdraw(); + if (ethbalance <= 0) revert NoEtherLeftToWithdraw(); + + bool tokenSuccess = IERC20(iqToken).transfer(msg.sender, tokenBalance); + if (!tokenSuccess) revert TransferFailed(); + + (bool success, ) = (msg.sender).call{value: ethbalance}(""); if (!success) revert TransferFailed(); } @@ -250,7 +291,7 @@ contract BrainPassCollectibles is ERC721, Owned { totalPrice = totalPrice.sub(discountAmount); } - return totalPrice * 1e18; + return totalPrice; } /// @notice Validates the Timestamp Duration for minting Nft @@ -340,4 +381,6 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 _maxtokens, uint256 _pricePerDay ); + + event PassTypePaused(uint256 indexed _passId, string _name); } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index af12274..b37b799 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -8,21 +8,22 @@ import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol"; import {MockERC20} from "../lib/solmate/src/test/utils/mocks/MockERC20.sol"; -contract TestEditor is PRBTest, Cheats { +contract BrainPassTest is PRBTest, Cheats { BrainPassCollectibles BrainPass; address alice = vm.addr(0x2); address bob = vm.addr(0x3); address doe = vm.addr(0x4); + address sam = vm.addr(0x1); MockERC20 mockERC20; function setUp() public { mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token BrainPass = new BrainPassCollectibles(address(mockERC20)); - BrainPass.addPassType(15, "http://example.com/1", "Gold", 200, 0); + BrainPass.addPassType(15e18, "http://example.com/1", "Gold", 200, 0); } function testAddPassType() public { - BrainPass.addPassType(15, "http://example.com/56", "Gold", 200, 0); + BrainPass.addPassType(15e18, "http://example.com/56", "Gold", 200, 0); string memory _name = BrainPass.getPassType(1).name; assertEq(_name, "Gold"); } @@ -32,10 +33,10 @@ contract TestEditor is PRBTest, Cheats { vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); assertEq(BrainPass.balanceOf(alice), 0); - BrainPass.mintNFT(0, 172800, 5184000); + BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); vm.expectRevert(BrainPassCollectibles.AlreadyMintedThisPass.selector); - BrainPass.mintNFT(0, 172800, 5184000); + BrainPass.mintNFT(1, 172800, 5184000); vm.stopPrank(); } @@ -44,19 +45,51 @@ contract TestEditor is PRBTest, Cheats { vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); vm.expectRevert(BrainPassCollectibles.DurationNotInTimeFrame.selector); - BrainPass.mintNFT(0, 172800, 518400); + BrainPass.mintNFT(1, 172800, 518400); vm.stopPrank(); } + function testInvalidMaxTokensForAPass() public { + vm.expectRevert( + BrainPassCollectibles.InvalidMaxTokensForAPass.selector + ); + BrainPass.addPassType( + 15e18, + "http://example.orgs", + "OleanjiPass", + 0, + 0 + ); + } + + function testPassTypeNotFound() public { + mockERC20.mint(alice, 20000e18); + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 9000e18); + vm.expectRevert(BrainPassCollectibles.PassTypeNotFound.selector); + BrainPass.mintNFT(4, 172800, 5184000); + } + + function testCannotMintPausedPass() public { + BrainPass.pausePassType(1); + mockERC20.mint(alice, 20000e18); + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 9000e18); + vm.expectRevert( + BrainPassCollectibles.CannotMintPausedPassType.selector + ); + BrainPass.mintNFT(1, 172800, 5184000); + } + function testmintNFT() public { mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 3e18); vm.expectRevert(stdError.arithmeticError); - BrainPass.mintNFT(0, 172800, 5184000); + BrainPass.mintNFT(1, 172800, 5184000); mockERC20.approve(address(BrainPass), 9000e18); assertEq(BrainPass.balanceOf(alice), 0); - BrainPass.mintNFT(0, 172800, 5184000); + BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); assertEq(mockERC20.balanceOf(address(this)), 870e18); uint256 mintedPass = BrainPass.getUserPassDetails(alice, 0).passId; @@ -68,8 +101,8 @@ contract TestEditor is PRBTest, Cheats { mockERC20.mint(alice, 3000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); - BrainPass.mintNFT(0, 172800, 5184000); - uint256 _tokenId = BrainPass.getUserPassDetails(alice, 0).tokenId; + BrainPass.mintNFT(1, 172800, 5184000); + uint256 _tokenId = BrainPass.getUserPassDetails(alice, 1).tokenId; BrainPass.increaseEndTime(_tokenId, 8640000); vm.stopPrank(); vm.startPrank(bob); @@ -82,14 +115,14 @@ contract TestEditor is PRBTest, Cheats { vm.startPrank(alice); mockERC20.approve(address(BrainPass), 12000e18); assertEq(BrainPass.balanceOf(alice), 0); - BrainPass.mintNFT(0, 172800, 5184000); + BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); - uint256 _tokenId = BrainPass.getUserPassDetails(alice, 0).tokenId; + uint256 _tokenId = BrainPass.getUserPassDetails(alice, 1).tokenId; assertEq(_tokenId, 1); BrainPass.increaseEndTime(_tokenId, 8640000); assertEq(mockERC20.balanceOf(address(this)), 1470e18); BrainPass.addressToNFTPass(alice, _tokenId); - uint _endTine = BrainPass.getUserPassDetails(alice, 0).endTimestamp; + uint _endTine = BrainPass.getUserPassDetails(alice, 1).endTimestamp; assertEq(_endTine, 8640000); } @@ -100,37 +133,53 @@ contract TestEditor is PRBTest, Cheats { } function testGetAllPassType() public { - assertEq(BrainPass.getAllPassType().length, 1); + assertEq(BrainPass.getAllPassType().length, 2); BrainPass.addPassType( - 400, + 400e18, "http://example.com/56", "Platinum", 3000, 10 ); - assertEq(BrainPass.getPassType(1).name, "Platinum"); - assertEq(BrainPass.getAllPassType().length, 2); + assertEq(BrainPass.getPassType(2).name, "Platinum"); + assertEq(BrainPass.getAllPassType().length, 3); } function testMintNftWrong() public { - BrainPass.addPassType(15, "http://example.orgs", "OleanjiPass", 2, 0); + BrainPass.addPassType( + 15e18, + "http://example.orgs", + "OleanjiPass", + 2, + 0 + ); mockERC20.mint(alice, 20000e18); mockERC20.mint(bob, 20000e18); mockERC20.mint(doe, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 1700e18); assertEq(BrainPass.balanceOf(alice), 0); - BrainPass.mintNFT(1, 172800, 5184000); + BrainPass.mintNFT(2, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); vm.stopPrank(); vm.startPrank(bob); mockERC20.approve(address(BrainPass), 1700e18); - BrainPass.mintNFT(1, 172800, 5184000); + BrainPass.mintNFT(2, 172800, 5184000); assertEq(BrainPass.balanceOf(bob), 1); vm.stopPrank(); vm.startPrank(doe); mockERC20.approve(address(BrainPass), 1700e18); vm.expectRevert(BrainPassCollectibles.PassMaxSupplyReached.selector); - BrainPass.mintNFT(1, 172800, 5184000); + BrainPass.mintNFT(2, 172800, 5184000); + } + + function testWithdrawTokens() public { + vm.expectRevert(BrainPassCollectibles.NoIQLeftToWithdraw.selector); + BrainPass.withdraw(); + assertEq(mockERC20.balanceOf(address(BrainPass)), 0); + mockERC20.mint(address(BrainPass), 20000e18); + assertEq(mockERC20.balanceOf(address(BrainPass)), 20000e18); + vm.expectRevert(BrainPassCollectibles.NoEtherLeftToWithdraw.selector); + BrainPass.withdraw(); } } From 8379083488486bd5c05797f345c8e6ffe2a3bbd3 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Mon, 29 May 2023 15:57:42 +0100 Subject: [PATCH 28/54] Updated readme file, allows different url for differnt pass type, more tests, --- README.md | 94 +++++++++++++------------ remappings.txt | 1 + src/BrainPass/BrainPass.sol | 134 +++++++++++++++++++++++++----------- test/BrainPass.t.sol | 40 ++++++++--- 4 files changed, 175 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index 6e1d4cd..fa67afb 100644 --- a/README.md +++ b/README.md @@ -47,51 +47,59 @@ BrainPassCollectibles is a Solidity smart contract that enables the creation and ## Usage ### Contract Deployment - Deploy the `BrainPassCollectibles` contract by providing the address of the IQ token contract as a constructor parameter. -### Adding a Pass Type -This function allows the contract owner to add a new pass type. - -Call the `addPassType` function with the following parameters: -- `pricePerDay`: The price per day in IQ tokens for the pass. -- `tokenURI`: The base URI for the pass token metadata. -- `name`: The name of the pass type. -- `maxTokens`: The maximum number of tokens that can be minted for this pass type. -- `discount`: The discount percentage applied to the pass price (optional). - -### Minting an NFT Pass -This function allows users to mint a BrainPass NFT for a specific pass type and duration. -Call the `mintNFT` function with the following parameters: -- `passId`: The ID of the pass type to mint. -- `startTimestamp`: The start timestamp for the pass. -- `endTimestamp`: The end timestamp for the pass. -- the duration is calculated by endTimestamp - startTimestamp -- An address can only mint one passtype. -- The payment is in IQ Token - -### Increasing Pass Time -This function is ued to increase the duration for which a pass is owned for. -Call the `increasePassTime` function with the following parameters: -- `tokenId`: The ID of the NFT whose time should be increased. -- `newEndTime`: The new end timestamp for the pass. - -### Get User Pass Details -This gets the details of the tokenId the user has in the particular passId -Call the `getUserPassDetails` function with the following parameters: -- `user`: The address of the user. -- `passId`: The ID of the pass type. - -### Get Pass Types -Call the `getAllPassType` function to get an array of all pass types. - -### Get Pass Type Details -This gets a single passtype details -Call the `getPassType` function with the following parameters: -- `passId`: The ID of the pass type. - -### Withdraw Ether -Only the contract owner can call the `withdraw` function to withdraw any amount of Ether stored in the contract. +## `pause()` +Pauses the contract and prevents any further actions. Can only be called by the contract owner. + +## `unpause()` +Unpauses the contract and resumes normal operations. Can only be called by the contract owner. + +## `configureMintLimit()` +Configures the lower and upper limits for minting NFTs. +- `lowerLimit`: Minimum duration (in days) for a subscription. +- `upperLimit`: Maximum duration (in days) for a subscription. +Only the contract owner can call this function. + +## `addPassType()` +Adds a new pass type. +- `pricePerDay`: Price per day of the new pass type. +- `tokenUri`: URI that stores the data of all the NFTs in the new pass. +- `name`: Name of the new pass type. +- `maxTokens`: Total number of tokens in the pass. +- `discount`: Discount amount (in percentage) to be deducted when buying the pass. +Only the contract owner can call this function. + +## `pausePassType()` +Pauses a specific pass type. +- `passId`: ID of the pass type to be deactivated. +Only the contract owner can call this function. + +## `mintNFT()` +Mints an NFT of a particular pass type. +- `passId`: ID of the pass type to mint. +- `startTimestamp`: Time when the NFT subscription starts. +- `endTimestamp`: Time when the NFT subscription ends. + +## `increaseEndTime()` +Increases the subscription time of an NFT. +- `tokenId`: ID of the NFT whose time is to be increased. +- `newEndTime`: New subscription end time for the NFT. + +## `withdraw()` +Withdraws any amount of IQ tokens and Ether held in the contract. Can only be called by the contract owner. + +## `getUserPassDetails()` +Retrieves the details of an NFT owned by a specific user for a given pass type. +- `user`: Address of the user. +- `passId`: ID of the pass type. + +## `getAllPassType()` +Retrieves the details of all the pass types added to the contract. + +## `getPassType()` +Retrieves the details of a specific pass type. +- `passId`: ID of the pass type ## Events diff --git a/remappings.txt b/remappings.txt index 2cf5bd5..eed2212 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,3 +4,4 @@ forge-std/=lib/forge-std/src/ openzeppelin-contracts/=lib/openzeppelin-contracts/ prb-test/=lib/prb-test/src/ solmate/=lib/solmate/src/ + diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index ec1605d..5802d08 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; +import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; import {Counters} from "openzeppelin-contracts/contracts/utils/Counters.sol"; -import {Owned} from "solmate/auth/Owned.sol"; -import {SafeMath} from "openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; +import "openzeppelin-contracts/contracts/access/Ownable.sol"; +import "openzeppelin-contracts/contracts/security/Pausable.sol"; +import "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +import "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import {Wiki} from "../../src/Wiki.sol"; interface IERC20 { function transferFrom( @@ -22,7 +25,13 @@ interface IERC20 { /// @author Oleanji /// @notice A pass for IQ Wiki Editors -contract BrainPassCollectibles is ERC721, Owned { +contract BrainPassCollectibles is + ERC721, + ERC721Enumerable, + ERC721URIStorage, + Pausable, + Ownable +{ /// ----------------------------------------------------------------------- /// Errors /// ----------------------------------------------------------------------- @@ -43,8 +52,6 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- /// Inheritances /// ----------------------------------------------------------------------- - - using SafeMath for uint256; using Counters for Counters.Counter; /// ----------------------------------------------------------------------- @@ -66,6 +73,7 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 maxTokens; uint256 discount; uint256 lastTokenIdMinted; + uint256 currentNftCount; bool isPaused; } @@ -83,39 +91,60 @@ contract BrainPassCollectibles is ERC721, Owned { /// ----------------------------------------------------------------------- address public iqToken; - uint256 constant SECONDS_IN_A_DAY = 1 days; - uint256 constant DAYS_MINT_LOWER_LIMIT = 28; - uint256 constant DAYS_MINT_UPPER_LIMIT = 365; + + /// @notice contract that validates if wiki should be pushed + Wiki private wiki; /// ----------------------------------------------------------------------- /// Variables /// ----------------------------------------------------------------------- - string public baseTokenURI; Counters.Counter private passIdTracker; + uint256 MINT_LOWER_LIMIT = 28; + uint256 MINT_UPPER_LIMIT = 365; /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- - constructor(address IqAddr) ERC721("BAINPASS", "BEP") Owned(msg.sender) { + constructor(address IqAddr, address wikiAddr) ERC721("BAINPASS", "BEP") { iqToken = IqAddr; passIdTracker.increment(); + wiki = Wiki(wikiAddr); } /// ----------------------------------------------------------------------- /// External functions /// ----------------------------------------------------------------------- + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + + /// @notice Change the MintLimit for the Nfts + /// @param lowerLimit the new lower limit for how short a nft can be subscribed for + /// @param upperLimit the new upper limit for how long a nft can be subscribed for + function configureMintLimit( + uint256 lowerLimit, + uint256 upperLimit + ) external onlyOwner { + MINT_UPPER_LIMIT = upperLimit; + MINT_LOWER_LIMIT = lowerLimit; + } + /// @notice Add a new Pass Type /// @param pricePerDay the price per day of the new pass type - /// @param tokenURI the link that stores the data of all the Nfts in the new pass + /// @param tokenUri the link that stores the data of all the Nfts in the new pass /// @param name the name of the new pass type to be added /// @param maxTokens the total number of tokens in the pass /// @param discount the amount in % to be deducted when buying the pass function addPassType( uint256 pricePerDay, - string memory tokenURI, + string memory tokenUri, string memory name, uint256 maxTokens, uint256 discount @@ -126,10 +155,11 @@ contract BrainPassCollectibles is ERC721, Owned { passId, name, pricePerDay, - tokenURI, + tokenUri, maxTokens, discount, - 0, + passTypes[passId - 1].currentNftCount, + passTypes[passId - 1].currentNftCount + maxTokens, false ); passIdTracker.increment(); @@ -149,7 +179,8 @@ contract BrainPassCollectibles is ERC721, Owned { passType.tokenURI, passType.maxTokens, passType.discount, - passType.discount, + passType.lastTokenIdMinted, + passType.currentNftCount, true ); @@ -172,7 +203,7 @@ contract BrainPassCollectibles is ERC721, Owned { PassType storage passType = passTypes[passId]; if (passType.isPaused) revert CannotMintPausedPassType(); - if (passType.lastTokenIdMinted >= passType.maxTokens) + if (passType.lastTokenIdMinted >= passType.currentNftCount) revert PassMaxSupplyReached(); if (!validatePassDuration(startTimestamp, endTimestamp)) @@ -180,7 +211,7 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 price = calculatePrice(passId, startTimestamp, endTimestamp); - bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); + bool success = IERC20(iqToken).transferFrom(msg.sender, owner(), price); if (!success) revert MintingPaymentFailed(); uint256 tokenId = passType.lastTokenIdMinted + 1; @@ -194,8 +225,8 @@ contract BrainPassCollectibles is ERC721, Owned { ownerOfToken[passId][msg.sender] = purchase; passType.lastTokenIdMinted = tokenId; - setBaseURI(passType.tokenURI); _safeMint(msg.sender, tokenId); + _setTokenURI(tokenId, passType.tokenURI); emit BrainPassBought( msg.sender, @@ -228,7 +259,7 @@ contract BrainPassCollectibles is ERC721, Owned { revert DurationNotInTimeFrame(); uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); - bool success = IERC20(iqToken).transferFrom(msg.sender, owner, price); + bool success = IERC20(iqToken).transferFrom(msg.sender, owner(), price); if (!success) revert IncreseTimePaymentFailed(); UserPassItem memory purchase = UserPassItem( @@ -277,18 +308,16 @@ contract BrainPassCollectibles is ERC721, Owned { uint256 endTimestamp ) internal view returns (uint256) { PassType memory passType = passTypes[passId]; - uint256 subscriptionPeriodInSeconds = endTimestamp.sub(startTimestamp); + uint256 subscriptionPeriodInSeconds = endTimestamp - startTimestamp; - uint256 subscriptionPeriodInDays = subscriptionPeriodInSeconds.div( - SECONDS_IN_A_DAY - ); + uint256 subscriptionPeriodInDays = subscriptionPeriodInSeconds / 1 days; // Calculate the total price - uint256 totalPrice = subscriptionPeriodInDays.mul(passType.pricePerDay); + uint256 totalPrice = subscriptionPeriodInDays * passType.pricePerDay; if (passType.discount > 0) { - uint256 discountAmount = totalPrice.mul(passType.discount).div(100); - totalPrice = totalPrice.sub(discountAmount); + uint256 discountAmount = (totalPrice * passType.discount) / (100); + totalPrice = totalPrice - discountAmount; } return totalPrice; @@ -300,22 +329,17 @@ contract BrainPassCollectibles is ERC721, Owned { function validatePassDuration( uint256 startTimestamp, uint256 endTimestamp - ) internal pure returns (bool) { - uint256 durationInDays = (endTimestamp.sub(startTimestamp)) / - SECONDS_IN_A_DAY; + ) internal view returns (bool) { + uint256 durationInDays = (endTimestamp - startTimestamp) / 1 days; return - durationInDays >= DAYS_MINT_LOWER_LIMIT && - durationInDays <= DAYS_MINT_UPPER_LIMIT; + durationInDays >= MINT_LOWER_LIMIT && + durationInDays <= MINT_UPPER_LIMIT; } /// ----------------------------------------------------------------------- /// Getters /// ----------------------------------------------------------------------- - function _baseURI() internal view virtual override returns (string memory) { - return baseTokenURI; - } - /// @notice Gets all the NFT owned by an address /// @param user The address of the user /// @param passId The Id of the pass to get the user's info on @@ -347,13 +371,41 @@ contract BrainPassCollectibles is ERC721, Owned { } /// ----------------------------------------------------------------------- - /// Setters + /// Override Functions /// ----------------------------------------------------------------------- - /// @notice Sets the tokenURI where the NFT data is gotten - /// @param tokenURI The tokenURI to be set - function setBaseURI(string memory tokenURI) public { - baseTokenURI = tokenURI; + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal override(ERC721, ERC721Enumerable) { + super._beforeTokenTransfer(from, to, tokenId); + } + + function _burn( + uint256 tokenId + ) internal override(ERC721, ERC721URIStorage) { + super._burn(tokenId); + } + + function tokenURI( + uint256 tokenId + ) public view override(ERC721, ERC721URIStorage) returns (string memory) { + return super.tokenURI(tokenId); + } + + function supportsInterface( + bytes4 interfaceId + ) public view override(ERC721, ERC721Enumerable) returns (bool) { + return super.supportsInterface(interfaceId); + } + + modifier updateReward(uint256 passId, address account) { + UserPassItem memory userItem = ownerOfToken[passId][account]; + if (userItem.endTimestamp > block.timestamp) { + //unwhitelist address here + } + _; } /// ----------------------------------------------------------------------- diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index b37b799..e7b4ce4 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import {PRBTest} from "prb-test/PRBTest.sol"; import {Cheats} from "forge-std/Cheats.sol"; +import "forge-std/console.sol"; import {stdError} from "forge-std/Errors.sol"; import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol"; @@ -19,11 +20,11 @@ contract BrainPassTest is PRBTest, Cheats { function setUp() public { mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token BrainPass = new BrainPassCollectibles(address(mockERC20)); - BrainPass.addPassType(15e18, "http://example.com/1", "Gold", 200, 0); + BrainPass.addPassType(15e18, "http://example.com", "Gold", 200, 0); } function testAddPassType() public { - BrainPass.addPassType(15e18, "http://example.com/56", "Gold", 200, 0); + BrainPass.addPassType(15e18, "http://example.com", "Gold", 200, 0); string memory _name = BrainPass.getPassType(1).name; assertEq(_name, "Gold"); } @@ -92,8 +93,33 @@ contract BrainPassTest is PRBTest, Cheats { BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); assertEq(mockERC20.balanceOf(address(this)), 870e18); - uint256 mintedPass = BrainPass.getUserPassDetails(alice, 0).passId; - assertEq(mintedPass, 0); + uint256 mintedPass = BrainPass.getUserPassDetails(alice, 1).tokenId; + assertEq(mintedPass, 1); + vm.stopPrank(); + } + + function testdiffmintNFT() public { + BrainPass.addPassType( + 15e18, + "http://example.orgs", + "OleanjiPass", + 2, + 0 + ); + BrainPass.addPassType(15e18, "http://oleanji.com", "KesarPass", 2, 0); + mockERC20.mint(alice, 20000e18); + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 20000e18); + BrainPass.mintNFT(1, 172800, 5184000); + console.log(BrainPass.tokenURI(1)); + BrainPass.mintNFT(2, 172800, 5184000); + uint256 newId = BrainPass.getUserPassDetails(alice, 2).tokenId; + console.log(BrainPass.tokenURI(newId)); + BrainPass.mintNFT(3, 172800, 5184000); + uint256 newIds = BrainPass.getUserPassDetails(alice, 3).tokenId; + console.log(BrainPass.tokenURI(newIds)); + uint256 mintedPass = BrainPass.getUserPassDetails(alice, 2).tokenId; + assertEq(mintedPass, 201); vm.stopPrank(); } @@ -126,12 +152,6 @@ contract BrainPassTest is PRBTest, Cheats { assertEq(_endTine, 8640000); } - function testBaseTokenURI() public { - assertEq(BrainPass.baseTokenURI(), ""); - BrainPass.setBaseURI("http://example.org.com/565"); - assertEq(BrainPass.baseTokenURI(), "http://example.org.com/565"); - } - function testGetAllPassType() public { assertEq(BrainPass.getAllPassType().length, 2); BrainPass.addPassType( From 92444b87e90198b8609233709455e9529a578872 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Mon, 29 May 2023 17:43:44 +0100 Subject: [PATCH 29/54] removed unsed contrac import,toggle pass type status --- src/BrainPass/BrainPass.sol | 27 +++++++-------------------- src/BrainPass/BrainPassValidator.sol | 0 test/BrainPass.t.sol | 19 +++++++++++-------- 3 files changed, 18 insertions(+), 28 deletions(-) create mode 100644 src/BrainPass/BrainPassValidator.sol diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 5802d08..0449058 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -92,9 +92,6 @@ contract BrainPassCollectibles is address public iqToken; - /// @notice contract that validates if wiki should be pushed - Wiki private wiki; - /// ----------------------------------------------------------------------- /// Variables /// ----------------------------------------------------------------------- @@ -107,21 +104,20 @@ contract BrainPassCollectibles is /// Constructor /// ----------------------------------------------------------------------- - constructor(address IqAddr, address wikiAddr) ERC721("BAINPASS", "BEP") { + constructor(address IqAddr) ERC721("BAINPASS", "BEP") { iqToken = IqAddr; passIdTracker.increment(); - wiki = Wiki(wikiAddr); } /// ----------------------------------------------------------------------- /// External functions /// ----------------------------------------------------------------------- - function pause() public onlyOwner { + function pause() external onlyOwner { _pause(); } - function unpause() public onlyOwner { + function unpause() external onlyOwner { _unpause(); } @@ -168,9 +164,10 @@ contract BrainPassCollectibles is /// @notice Pause a Pass Type /// @param passId the Id of the pass to be deactivated - function pausePassType(uint256 passId) external onlyOwner { + function togglePassTypeStatus(uint256 passId) external onlyOwner { if (passId >= passIdTracker.current()) revert PassTypeNotFound(); PassType storage passType = passTypes[passId]; + bool newStatus = !passType.isPaused; passTypes[passId] = PassType( passType.passId, @@ -181,7 +178,7 @@ contract BrainPassCollectibles is passType.discount, passType.lastTokenIdMinted, passType.currentNftCount, - true + newStatus ); emit PassTypePaused(passId, passType.name); @@ -199,13 +196,11 @@ contract BrainPassCollectibles is if (passId >= passIdTracker.current()) revert PassTypeNotFound(); if (getUserPassDetails(msg.sender, passId).tokenId != 0) revert AlreadyMintedThisPass(); - PassType storage passType = passTypes[passId]; if (passType.isPaused) revert CannotMintPausedPassType(); if (passType.lastTokenIdMinted >= passType.currentNftCount) revert PassMaxSupplyReached(); - if (!validatePassDuration(startTimestamp, endTimestamp)) revert DurationNotInTimeFrame(); @@ -293,7 +288,7 @@ contract BrainPassCollectibles is (bool success, ) = (msg.sender).call{value: ethbalance}(""); if (!success) revert TransferFailed(); - } + } // make this as two func /// ----------------------------------------------------------------------- /// Internal Functions @@ -400,14 +395,6 @@ contract BrainPassCollectibles is return super.supportsInterface(interfaceId); } - modifier updateReward(uint256 passId, address account) { - UserPassItem memory userItem = ownerOfToken[passId][account]; - if (userItem.endTimestamp > block.timestamp) { - //unwhitelist address here - } - _; - } - /// ----------------------------------------------------------------------- /// Events /// ----------------------------------------------------------------------- diff --git a/src/BrainPass/BrainPassValidator.sol b/src/BrainPass/BrainPassValidator.sol new file mode 100644 index 0000000..e69de29 diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index e7b4ce4..3c44638 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -72,7 +72,7 @@ contract BrainPassTest is PRBTest, Cheats { } function testCannotMintPausedPass() public { - BrainPass.pausePassType(1); + BrainPass.togglePassTypeStatus(1); mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); @@ -98,7 +98,7 @@ contract BrainPassTest is PRBTest, Cheats { vm.stopPrank(); } - function testdiffmintNFT() public { + function testDifferentPassMint() public { BrainPass.addPassType( 15e18, "http://example.orgs", @@ -110,14 +110,15 @@ contract BrainPassTest is PRBTest, Cheats { mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 20000e18); - BrainPass.mintNFT(1, 172800, 5184000); - console.log(BrainPass.tokenURI(1)); - BrainPass.mintNFT(2, 172800, 5184000); + BrainPass.mintNFT(1, 172800, 5184000); //"http://example.com" + uint256 firstId = BrainPass.getUserPassDetails(alice, 1).tokenId; + console.log(BrainPass.tokenURI(firstId), firstId); + BrainPass.mintNFT(2, 172800, 5184000); // "http://example.orgs", uint256 newId = BrainPass.getUserPassDetails(alice, 2).tokenId; - console.log(BrainPass.tokenURI(newId)); - BrainPass.mintNFT(3, 172800, 5184000); + console.log(BrainPass.tokenURI(newId), newId); + BrainPass.mintNFT(3, 172800, 5184000); //"http://oleanji.com" uint256 newIds = BrainPass.getUserPassDetails(alice, 3).tokenId; - console.log(BrainPass.tokenURI(newIds)); + console.log(BrainPass.tokenURI(newIds), newIds); uint256 mintedPass = BrainPass.getUserPassDetails(alice, 2).tokenId; assertEq(mintedPass, 201); vm.stopPrank(); @@ -202,4 +203,6 @@ contract BrainPassTest is PRBTest, Cheats { vm.expectRevert(BrainPassCollectibles.NoEtherLeftToWithdraw.selector); BrainPass.withdraw(); } + + //configureMintLimit } From d9b5bda773e1dec59fd0a1553a3405778d2f732e Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Mon, 29 May 2023 19:26:39 +0100 Subject: [PATCH 30/54] validating users to know if they do an activity that requires their pass --- src/BrainPass/BrainPass.sol | 26 +++++++----------- src/BrainPass/BrainPassValidator.sol | 38 ++++++++++++++++++++++++++ test/BrainPass.t.sol | 33 +++++++++++++++------- test/BrainPassValidator.t.sol | 41 ++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 test/BrainPassValidator.t.sol diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 0449058..9d48a8d 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -38,7 +38,7 @@ contract BrainPassCollectibles is error MintingPaymentFailed(); error IncreseTimePaymentFailed(); - error AlreadyMintedThisPass(); + error AlreadyMintedAPass(); error NotTheOwnerOfThisNft(); error InvalidMaxTokensForAPass(); error PassTypeNotFound(); @@ -82,9 +82,7 @@ contract BrainPassCollectibles is /// ----------------------------------------------------------------------- mapping(uint256 => PassType) public passTypes; - mapping(address => mapping(uint256 => UserPassItem)) - public addressToNFTPass; - mapping(uint256 => mapping(address => UserPassItem)) internal ownerOfToken; + mapping(address => UserPassItem) public addressToNFTPass; /// ----------------------------------------------------------------------- /// Constant @@ -194,8 +192,8 @@ contract BrainPassCollectibles is uint256 endTimestamp ) external { if (passId >= passIdTracker.current()) revert PassTypeNotFound(); - if (getUserPassDetails(msg.sender, passId).tokenId != 0) - revert AlreadyMintedThisPass(); + if (addressToNFTPass[msg.sender].tokenId != 0) + revert AlreadyMintedAPass(); PassType storage passType = passTypes[passId]; if (passType.isPaused) revert CannotMintPausedPassType(); @@ -216,8 +214,7 @@ contract BrainPassCollectibles is startTimestamp, endTimestamp ); - addressToNFTPass[msg.sender][tokenId] = purchase; - ownerOfToken[passId][msg.sender] = purchase; + addressToNFTPass[msg.sender] = purchase; passType.lastTokenIdMinted = tokenId; _safeMint(msg.sender, tokenId); @@ -236,12 +233,12 @@ contract BrainPassCollectibles is /// @param tokenId The Id of the NFT whose time is to be increased /// @param newEndTime The new subcription endTime for the of the NFT function increaseEndTime(uint256 tokenId, uint256 newEndTime) external { - UserPassItem memory pass = addressToNFTPass[msg.sender][tokenId]; + UserPassItem memory pass = addressToNFTPass[msg.sender]; PassType storage passType = passTypes[pass.passId]; if (passType.isPaused) revert CannotMintPausedPassType(); - if (getUserPassDetails(msg.sender, pass.passId).tokenId != tokenId) + if (addressToNFTPass[msg.sender].tokenId != tokenId) revert NotTheOwnerOfThisNft(); uint256 newStartTime; if (pass.endTimestamp < block.timestamp) { @@ -264,8 +261,7 @@ contract BrainPassCollectibles is newEndTime ); - addressToNFTPass[msg.sender][tokenId] = purchase; - ownerOfToken[pass.passId][msg.sender] = purchase; + addressToNFTPass[msg.sender] = purchase; emit TimeIncreased( msg.sender, @@ -337,12 +333,10 @@ contract BrainPassCollectibles is /// @notice Gets all the NFT owned by an address /// @param user The address of the user - /// @param passId The Id of the pass to get the user's info on function getUserPassDetails( - address user, - uint passId + address user ) public view returns (UserPassItem memory) { - UserPassItem memory userToken = ownerOfToken[passId][user]; + UserPassItem memory userToken = addressToNFTPass[user]; return userToken; } diff --git a/src/BrainPass/BrainPassValidator.sol b/src/BrainPass/BrainPassValidator.sol index e69de29..a74f3a5 100644 --- a/src/BrainPass/BrainPassValidator.sol +++ b/src/BrainPass/BrainPassValidator.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {BrainPassCollectibles} from "./BrainPass.sol"; + +/// @title BRAIN Pass Validator +/// @author Oleanji +/// @notice A validation for the Nft +contract BrainPassValidiator { + /// ----------------------------------------------------------------------- + /// Errors + /// ----------------------------------------------------------------------- + error UserDoesNotHaveAPass(); + error UserPassExpired(); + + /// ----------------------------------------------------------------------- + /// variables + /// ----------------------------------------------------------------------- + BrainPassCollectibles brainPass; + + constructor(address brainPassAddr) { + brainPass = BrainPassCollectibles(brainPassAddr); + } + + /// ----------------------------------------------------------------------- + /// External functions + /// ----------------------------------------------------------------------- + + /// @notice Validate Post + /// @param user The user to validiate + function validate(address user) external view returns (bool) { + if (brainPass.balanceOf(user) <= 0) revert UserDoesNotHaveAPass(); + if (brainPass.getUserPassDetails(user).endTimestamp < block.timestamp) { + revert UserPassExpired(); + } + return true; + } +} diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 3c44638..71b7b56 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -36,7 +36,7 @@ contract BrainPassTest is PRBTest, Cheats { assertEq(BrainPass.balanceOf(alice), 0); BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); - vm.expectRevert(BrainPassCollectibles.AlreadyMintedThisPass.selector); + vm.expectRevert(BrainPassCollectibles.AlreadyMintedAPass.selector); BrainPass.mintNFT(1, 172800, 5184000); vm.stopPrank(); } @@ -93,7 +93,7 @@ contract BrainPassTest is PRBTest, Cheats { BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); assertEq(mockERC20.balanceOf(address(this)), 870e18); - uint256 mintedPass = BrainPass.getUserPassDetails(alice, 1).tokenId; + uint256 mintedPass = BrainPass.getUserPassDetails(alice).tokenId; assertEq(mintedPass, 1); vm.stopPrank(); } @@ -108,18 +108,29 @@ contract BrainPassTest is PRBTest, Cheats { ); BrainPass.addPassType(15e18, "http://oleanji.com", "KesarPass", 2, 0); mockERC20.mint(alice, 20000e18); + mockERC20.mint(doe, 20000e18); + + mockERC20.mint(bob, 20000e18); + vm.startPrank(alice); mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(1, 172800, 5184000); //"http://example.com" - uint256 firstId = BrainPass.getUserPassDetails(alice, 1).tokenId; + uint256 firstId = BrainPass.getUserPassDetails(alice).tokenId; console.log(BrainPass.tokenURI(firstId), firstId); + vm.stopPrank(); + vm.startPrank(doe); + mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(2, 172800, 5184000); // "http://example.orgs", - uint256 newId = BrainPass.getUserPassDetails(alice, 2).tokenId; + uint256 newId = BrainPass.getUserPassDetails(doe).tokenId; console.log(BrainPass.tokenURI(newId), newId); + vm.stopPrank(); + vm.startPrank(bob); + mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(3, 172800, 5184000); //"http://oleanji.com" - uint256 newIds = BrainPass.getUserPassDetails(alice, 3).tokenId; + uint256 newIds = BrainPass.getUserPassDetails(bob).tokenId; console.log(BrainPass.tokenURI(newIds), newIds); - uint256 mintedPass = BrainPass.getUserPassDetails(alice, 2).tokenId; + uint256 mintedPass = BrainPass.getUserPassDetails(doe).tokenId; + console.log(BrainPass.balanceOf(alice)); assertEq(mintedPass, 201); vm.stopPrank(); } @@ -129,7 +140,7 @@ contract BrainPassTest is PRBTest, Cheats { vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); BrainPass.mintNFT(1, 172800, 5184000); - uint256 _tokenId = BrainPass.getUserPassDetails(alice, 1).tokenId; + uint256 _tokenId = BrainPass.getUserPassDetails(alice).tokenId; BrainPass.increaseEndTime(_tokenId, 8640000); vm.stopPrank(); vm.startPrank(bob); @@ -144,12 +155,12 @@ contract BrainPassTest is PRBTest, Cheats { assertEq(BrainPass.balanceOf(alice), 0); BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); - uint256 _tokenId = BrainPass.getUserPassDetails(alice, 1).tokenId; + uint256 _tokenId = BrainPass.getUserPassDetails(alice).tokenId; assertEq(_tokenId, 1); BrainPass.increaseEndTime(_tokenId, 8640000); assertEq(mockERC20.balanceOf(address(this)), 1470e18); - BrainPass.addressToNFTPass(alice, _tokenId); - uint _endTine = BrainPass.getUserPassDetails(alice, 1).endTimestamp; + BrainPass.getUserPassDetails(alice); + uint _endTine = BrainPass.getUserPassDetails(alice).endTimestamp; assertEq(_endTine, 8640000); } @@ -204,5 +215,7 @@ contract BrainPassTest is PRBTest, Cheats { BrainPass.withdraw(); } + + //configureMintLimit } diff --git a/test/BrainPassValidator.t.sol b/test/BrainPassValidator.t.sol new file mode 100644 index 0000000..f25c35c --- /dev/null +++ b/test/BrainPassValidator.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.13; + +import {PRBTest} from "prb-test/PRBTest.sol"; +import {Cheats} from "forge-std/Cheats.sol"; +import "forge-std/console.sol"; +import {stdError} from "forge-std/Errors.sol"; +import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; +import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol"; +import {MockERC20} from "../lib/solmate/src/test/utils/mocks/MockERC20.sol"; +import {BrainPassValidiator} from "../src/BrainPass/BrainPassValidator.sol"; + +contract BrainPassValidatorTest is PRBTest, Cheats { + BrainPassCollectibles BrainPass; + BrainPassValidiator brainPassValidator; + address alice = vm.addr(0x2); + address bob = vm.addr(0x3); + MockERC20 mockERC20; + + function setUp() public { + mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token + BrainPass = new BrainPassCollectibles(address(mockERC20)); + brainPassValidator = new BrainPassValidiator(address(BrainPass)); + BrainPass.addPassType(15e18, "http://example.com", "Gold", 200, 0); + } + + function testPostWiki() public { + vm.expectRevert(BrainPassValidiator.UserDoesNotHaveAPass.selector); + brainPassValidator.validate(alice); + mockERC20.mint(alice, 20000e18); + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 1700e18); + BrainPass.mintNFT(1, 1685638993, 1693587793); // june 1st - sept 1st (3 months) + vm.stopPrank(); + assertEq(brainPassValidator.validate(alice), true); + skip(1685638993 + 7948800); //skipped from the start date till the end of the 3 months + console.log(block.timestamp); + vm.expectRevert(BrainPassValidiator.UserPassExpired.selector); + brainPassValidator.validate(alice); + } +} From 9ed21bcc22ae68de9f02647be54c42eb99f5bf60 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Tue, 30 May 2023 17:07:54 +0100 Subject: [PATCH 31/54] configureMintlimit test and pause contract testing --- src/BrainPass/BrainPass.sol | 12 ++++++------ test/BrainPass.t.sol | 23 +++++++++++++++++++++-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 9d48a8d..3a65b53 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -95,8 +95,8 @@ contract BrainPassCollectibles is /// ----------------------------------------------------------------------- Counters.Counter private passIdTracker; - uint256 MINT_LOWER_LIMIT = 28; - uint256 MINT_UPPER_LIMIT = 365; + uint256 public MINT_LOWER_LIMIT = 28; + uint256 public MINT_UPPER_LIMIT = 365; /// ----------------------------------------------------------------------- /// Constructor @@ -142,7 +142,7 @@ contract BrainPassCollectibles is string memory name, uint256 maxTokens, uint256 discount - ) external onlyOwner { + ) external onlyOwner whenNotPaused { if (maxTokens <= 0) revert InvalidMaxTokensForAPass(); uint256 passId = passIdTracker.current(); passTypes[passId] = PassType( @@ -162,7 +162,7 @@ contract BrainPassCollectibles is /// @notice Pause a Pass Type /// @param passId the Id of the pass to be deactivated - function togglePassTypeStatus(uint256 passId) external onlyOwner { + function togglePassTypeStatus(uint256 passId) external onlyOwner whenNotPaused { if (passId >= passIdTracker.current()) revert PassTypeNotFound(); PassType storage passType = passTypes[passId]; bool newStatus = !passType.isPaused; @@ -190,7 +190,7 @@ contract BrainPassCollectibles is uint256 passId, uint256 startTimestamp, uint256 endTimestamp - ) external { + ) external whenNotPaused { if (passId >= passIdTracker.current()) revert PassTypeNotFound(); if (addressToNFTPass[msg.sender].tokenId != 0) revert AlreadyMintedAPass(); @@ -232,7 +232,7 @@ contract BrainPassCollectibles is /// @notice Increase the time to hold a PassNft /// @param tokenId The Id of the NFT whose time is to be increased /// @param newEndTime The new subcription endTime for the of the NFT - function increaseEndTime(uint256 tokenId, uint256 newEndTime) external { + function increaseEndTime(uint256 tokenId, uint256 newEndTime) external whenNotPaused { UserPassItem memory pass = addressToNFTPass[msg.sender]; PassType storage passType = passTypes[pass.passId]; diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 71b7b56..e976d59 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -215,7 +215,26 @@ contract BrainPassTest is PRBTest, Cheats { BrainPass.withdraw(); } - + function testConfigureMintLimit() public { + assertEq(BrainPass.MINT_LOWER_LIMIT(), 28); + assertEq(BrainPass.MINT_UPPER_LIMIT(), 365); + BrainPass.configureMintLimit(50, 730); + assertEq(BrainPass.MINT_LOWER_LIMIT(), 50); + assertEq(BrainPass.MINT_UPPER_LIMIT(), 730); + } - //configureMintLimit + function testPauseAndUnPauseContract() public { + BrainPass.pause(); + vm.startPrank(alice); + vm.expectRevert("Pausable: paused"); + BrainPass.mintNFT(1, 172800, 5184000); + vm.stopPrank(); + BrainPass.unpause(); + mockERC20.mint(alice, 20000e18); + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 19700e18); + assertEq(BrainPass.balanceOf(alice), 0); + BrainPass.mintNFT(1, 172800, 5184000); + assertEq(BrainPass.balanceOf(alice), 1); + } } From f0dc3682e34ec254cd7a1298edf33e1afc3bb883 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Tue, 30 May 2023 19:59:31 +0100 Subject: [PATCH 32/54] seperated the withdraw func --- src/BrainPass/BrainPass.sol | 26 +++++++++++++++++--------- test/BrainPass.t.sol | 4 ++-- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 3a65b53..aed0a65 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -162,7 +162,9 @@ contract BrainPassCollectibles is /// @notice Pause a Pass Type /// @param passId the Id of the pass to be deactivated - function togglePassTypeStatus(uint256 passId) external onlyOwner whenNotPaused { + function togglePassTypeStatus( + uint256 passId + ) external onlyOwner whenNotPaused { if (passId >= passIdTracker.current()) revert PassTypeNotFound(); PassType storage passType = passTypes[passId]; bool newStatus = !passType.isPaused; @@ -232,7 +234,10 @@ contract BrainPassCollectibles is /// @notice Increase the time to hold a PassNft /// @param tokenId The Id of the NFT whose time is to be increased /// @param newEndTime The new subcription endTime for the of the NFT - function increaseEndTime(uint256 tokenId, uint256 newEndTime) external whenNotPaused { + function increaseEndTime( + uint256 tokenId, + uint256 newEndTime + ) external whenNotPaused { UserPassItem memory pass = addressToNFTPass[msg.sender]; PassType storage passType = passTypes[pass.passId]; @@ -272,19 +277,22 @@ contract BrainPassCollectibles is } /// @notice Withdraws any amount in the contract - function withdraw() external payable onlyOwner { - uint256 tokenBalance = IERC20(iqToken).balanceOf(address(this)); + function withdrawEther() external payable onlyOwner { uint256 ethbalance = address(this).balance; + if (ethbalance <= 0) revert NoEtherLeftToWithdraw(); + + (bool success, ) = (msg.sender).call{value: ethbalance}(""); + if (!success) revert TransferFailed(); + } + + function withdrawIQ() external payable onlyOwner { + uint256 tokenBalance = IERC20(iqToken).balanceOf(address(this)); if (tokenBalance <= 0) revert NoIQLeftToWithdraw(); - if (ethbalance <= 0) revert NoEtherLeftToWithdraw(); bool tokenSuccess = IERC20(iqToken).transfer(msg.sender, tokenBalance); if (!tokenSuccess) revert TransferFailed(); - - (bool success, ) = (msg.sender).call{value: ethbalance}(""); - if (!success) revert TransferFailed(); - } // make this as two func + } /// ----------------------------------------------------------------------- /// Internal Functions diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index e976d59..c334005 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -207,12 +207,12 @@ contract BrainPassTest is PRBTest, Cheats { function testWithdrawTokens() public { vm.expectRevert(BrainPassCollectibles.NoIQLeftToWithdraw.selector); - BrainPass.withdraw(); + BrainPass.withdrawIQ(); assertEq(mockERC20.balanceOf(address(BrainPass)), 0); mockERC20.mint(address(BrainPass), 20000e18); assertEq(mockERC20.balanceOf(address(BrainPass)), 20000e18); vm.expectRevert(BrainPassCollectibles.NoEtherLeftToWithdraw.selector); - BrainPass.withdraw(); + BrainPass.withdrawEther(); } function testConfigureMintLimit() public { From 86eb11d2d58b2c9726a918227a070f3143d3f6dc Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Tue, 30 May 2023 20:07:29 +0100 Subject: [PATCH 33/54] updated README.md --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index fa67afb..e773806 100644 --- a/README.md +++ b/README.md @@ -112,3 +112,44 @@ The contract emits the following events: ## PICTORAL EXPLANATION ![image](https://github.com/EveripediaNetwork/ep-contract/assets/75235148/eee4d631-28d9-4ca4-bc0e-62e5a02998a2) +# Token ID Assignment Scenario + +In this scenario, we will discuss how token IDs are assigned when adding new PassTypes to a system. The goal is to ensure a single ID counter for all tokens while maintaining unique IDs for each PassType. + +## OleanjiPass + +Existing PassType: OleanjiPass +- Name: "OleanjiPass" +- Max Tokens: 10 +- Last Token Minted: 0 +- Current NFT Count: 10 + +For the OleanjiPass PassType, the last token that was minted has an ID of 10, and there are currently a total of 10 tokens minted for this PassType. + +## KesarPass + +New PassType: KesarPass +- Name: "KesarPass" +- Max Tokens: 15 +- Last Token Minted: 10 +- Current NFT Count: 25 + +Since the last token minted for OleanjiPass is 10, we want to start counting the tokens for KesarPass from where OleanjiPass left off. + +For the KesarPass, if a token is minted, it will have an ID of 11 since it follows the lastTokenMinted of OleanjiPass, which was 10. The currentNftCount for KesarPass will be updated to 25, indicating that there are now a total of 25 tokens minted for this PassType. + +Therefore, the token IDs for KesarPass will range from 11 to 25. The corresponding URLs for these tokens would be as follows: + +- [https://oleanjiPass.com/1](https://oleanjiPass.com/1) +- [https://oleanjiPass.com/2](https://oleanjiPass.com/2) +- ... +- [https://oleanjiPass.com/10](https://oleanjiPass.com/10) +- [https://KesarPass.com/11](https://KesarPass.com/11) +- [https://KesarPass.com/12](https://KesarPass.com/12) +- ... +- [https://KesarPass.com/25](https://KesarPass.com/25) + +The JSON file for the token data of KesarPass will start from an ID of 11 and go up to 25, reflecting the corresponding token information for each ID. + +This approach allows for a consistent numbering sequence across different PassTypes while maintaining unique IDs for each PassType's tokens. + From cef37c7c9673946745f9914acab2d3d673dd3f8b Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 31 May 2023 11:44:09 +0100 Subject: [PATCH 34/54] updated readme file and contract with test --- README.md | 20 +++++++++++++------- src/BrainPass/BrainPass.sol | 8 ++++---- test/BrainPass.t.sol | 3 +++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e773806..39a87fe 100644 --- a/README.md +++ b/README.md @@ -68,26 +68,31 @@ Adds a new pass type. - `name`: Name of the new pass type. - `maxTokens`: Total number of tokens in the pass. - `discount`: Discount amount (in percentage) to be deducted when buying the pass. -Only the contract owner can call this function. +Only the contract owner can call this function and only when the contract is not paused -## `pausePassType()` -Pauses a specific pass type. +## `togglePassTypeStatus()` +toggles the status of a specific pass type from paused to unpaused. - `passId`: ID of the pass type to be deactivated. -Only the contract owner can call this function. +Only the contract owner can call this function and only when the contract is not paused ## `mintNFT()` Mints an NFT of a particular pass type. - `passId`: ID of the pass type to mint. - `startTimestamp`: Time when the NFT subscription starts. - `endTimestamp`: Time when the NFT subscription ends. +Can call this function only when the contract is not paused ## `increaseEndTime()` -Increases the subscription time of an NFT. +Increases the EndTime time of an NFT. - `tokenId`: ID of the NFT whose time is to be increased. - `newEndTime`: New subscription end time for the NFT. +Only when the contrasct is not paused can this function be called. + +## `withdrawEther()` +Withdraws any amount of Ether held in the contract. Can only be called by the contract owner. -## `withdraw()` -Withdraws any amount of IQ tokens and Ether held in the contract. Can only be called by the contract owner. +## `withdrawIQ()` +Withdraws any amount of IQ tokens held in the contract. Can only be called by the contract owner. ## `getUserPassDetails()` Retrieves the details of an NFT owned by a specific user for a given pass type. @@ -108,6 +113,7 @@ The contract emits the following events: - `BrainPassBought`: Emitted when a user buys a BrainPass NFT. - `TimeIncreased`: Emitted when the time of a BrainPass NFT is increased. - `NewPassAdded`: Emitted when a new pass type is added. +- `PassTypePaused`: Emitted when a pass is paused. ## PICTORAL EXPLANATION ![image](https://github.com/EveripediaNetwork/ep-contract/assets/75235148/eee4d631-28d9-4ca4-bc0e-62e5a02998a2) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index aed0a65..c480562 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -160,8 +160,8 @@ contract BrainPassCollectibles is emit NewPassAdded(passId, name, maxTokens, pricePerDay); } - /// @notice Pause a Pass Type - /// @param passId the Id of the pass to be deactivated + /// @notice Toggles the staus of a pass type (paused or unpaused) + /// @param passId the Id of the pass function togglePassTypeStatus( uint256 passId ) external onlyOwner whenNotPaused { @@ -181,7 +181,7 @@ contract BrainPassCollectibles is newStatus ); - emit PassTypePaused(passId, passType.name); + emit PassTypeStatusToggled(passId, passType.name); } /// @notice Mint and NFT of a particular passtype @@ -423,5 +423,5 @@ contract BrainPassCollectibles is uint256 _pricePerDay ); - event PassTypePaused(uint256 indexed _passId, string _name); + event PassTypeStatusToggled(uint256 indexed _passId, string _name); } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index c334005..48980f0 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -116,18 +116,21 @@ contract BrainPassTest is PRBTest, Cheats { mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(1, 172800, 5184000); //"http://example.com" uint256 firstId = BrainPass.getUserPassDetails(alice).tokenId; + assertEq(BrainPass.tokenURI(firstId), "http://example.com"); console.log(BrainPass.tokenURI(firstId), firstId); vm.stopPrank(); vm.startPrank(doe); mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(2, 172800, 5184000); // "http://example.orgs", uint256 newId = BrainPass.getUserPassDetails(doe).tokenId; + assertEq(BrainPass.tokenURI(newId), "http://example.orgs"); console.log(BrainPass.tokenURI(newId), newId); vm.stopPrank(); vm.startPrank(bob); mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(3, 172800, 5184000); //"http://oleanji.com" uint256 newIds = BrainPass.getUserPassDetails(bob).tokenId; + assertEq(BrainPass.tokenURI(newIds), "http://oleanji.com"); console.log(BrainPass.tokenURI(newIds), newIds); uint256 mintedPass = BrainPass.getUserPassDetails(doe).tokenId; console.log(BrainPass.balanceOf(alice)); From b8fffdfdaa10d738e845ba486b9df81203133521 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 31 May 2023 15:04:18 +0100 Subject: [PATCH 35/54] removed the id logic for passtypes --- README.md | 40 ----------------- script/BrainPassDeployer.s.sol | 9 ++-- src/BrainPass/BrainPass.sol | 81 +++++++++++----------------------- test/BrainPass.t.sol | 57 ++++++++---------------- test/BrainPassValidator.t.sol | 4 +- 5 files changed, 51 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index 39a87fe..0610f43 100644 --- a/README.md +++ b/README.md @@ -118,44 +118,4 @@ The contract emits the following events: ## PICTORAL EXPLANATION ![image](https://github.com/EveripediaNetwork/ep-contract/assets/75235148/eee4d631-28d9-4ca4-bc0e-62e5a02998a2) -# Token ID Assignment Scenario - -In this scenario, we will discuss how token IDs are assigned when adding new PassTypes to a system. The goal is to ensure a single ID counter for all tokens while maintaining unique IDs for each PassType. - -## OleanjiPass - -Existing PassType: OleanjiPass -- Name: "OleanjiPass" -- Max Tokens: 10 -- Last Token Minted: 0 -- Current NFT Count: 10 - -For the OleanjiPass PassType, the last token that was minted has an ID of 10, and there are currently a total of 10 tokens minted for this PassType. - -## KesarPass - -New PassType: KesarPass -- Name: "KesarPass" -- Max Tokens: 15 -- Last Token Minted: 10 -- Current NFT Count: 25 - -Since the last token minted for OleanjiPass is 10, we want to start counting the tokens for KesarPass from where OleanjiPass left off. - -For the KesarPass, if a token is minted, it will have an ID of 11 since it follows the lastTokenMinted of OleanjiPass, which was 10. The currentNftCount for KesarPass will be updated to 25, indicating that there are now a total of 25 tokens minted for this PassType. - -Therefore, the token IDs for KesarPass will range from 11 to 25. The corresponding URLs for these tokens would be as follows: - -- [https://oleanjiPass.com/1](https://oleanjiPass.com/1) -- [https://oleanjiPass.com/2](https://oleanjiPass.com/2) -- ... -- [https://oleanjiPass.com/10](https://oleanjiPass.com/10) -- [https://KesarPass.com/11](https://KesarPass.com/11) -- [https://KesarPass.com/12](https://KesarPass.com/12) -- ... -- [https://KesarPass.com/25](https://KesarPass.com/25) - -The JSON file for the token data of KesarPass will start from an ID of 11 and go up to 25, reflecting the corresponding token information for each ID. - -This approach allows for a consistent numbering sequence across different PassTypes while maintaining unique IDs for each PassType's tokens. diff --git a/script/BrainPassDeployer.s.sol b/script/BrainPassDeployer.s.sol index d897aa7..9496b78 100644 --- a/script/BrainPassDeployer.s.sol +++ b/script/BrainPassDeployer.s.sol @@ -6,15 +6,18 @@ import {console} from "../lib/forge-std/src/console.sol"; import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; - contract BrainPassDeployer is Script { - address constant owner = address(0xE161eB85f00eC6471E0de06bA1Cfc136C053fFfe); + address constant owner = + address(0xE161eB85f00eC6471E0de06bA1Cfc136C053fFfe); function run() external { vm.startBroadcast(); console.log("Deploying Brainpass deployer...."); - BrainPassCollectibles validator = new BrainPassCollectibles(0x5E959c60f86D17fb7D764AB69B654227d464E820); + BrainPassCollectibles validator = new BrainPassCollectibles( + 0x5E959c60f86D17fb7D764AB69B654227d464E820, + "http://example.com" + ); console.log("Brainpass Deployed", address(validator)); vm.stopBroadcast(); diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index c480562..1b50ee8 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -5,8 +5,6 @@ import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; import {Counters} from "openzeppelin-contracts/contracts/utils/Counters.sol"; import "openzeppelin-contracts/contracts/access/Ownable.sol"; import "openzeppelin-contracts/contracts/security/Pausable.sol"; -import "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import {Wiki} from "../../src/Wiki.sol"; interface IERC20 { @@ -25,13 +23,7 @@ interface IERC20 { /// @author Oleanji /// @notice A pass for IQ Wiki Editors -contract BrainPassCollectibles is - ERC721, - ERC721Enumerable, - ERC721URIStorage, - Pausable, - Ownable -{ +contract BrainPassCollectibles is ERC721, Pausable, Ownable { /// ----------------------------------------------------------------------- /// Errors /// ----------------------------------------------------------------------- @@ -69,11 +61,9 @@ contract BrainPassCollectibles is uint256 passId; string name; uint256 pricePerDay; - string tokenURI; uint256 maxTokens; uint256 discount; - uint256 lastTokenIdMinted; - uint256 currentNftCount; + uint256 lastMintedId; bool isPaused; } @@ -95,16 +85,23 @@ contract BrainPassCollectibles is /// ----------------------------------------------------------------------- Counters.Counter private passIdTracker; + Counters.Counter private tokenIdTracker; uint256 public MINT_LOWER_LIMIT = 28; uint256 public MINT_UPPER_LIMIT = 365; + string public baseTokenURI; /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- - constructor(address IqAddr) ERC721("BAINPASS", "BEP") { + constructor( + address IqAddr, + string memory _baseTokenURI + ) ERC721("BAINPASS", "BEP") { iqToken = IqAddr; passIdTracker.increment(); + tokenIdTracker.increment(); + setBaseURI(_baseTokenURI); } /// ----------------------------------------------------------------------- @@ -119,6 +116,14 @@ contract BrainPassCollectibles is _unpause(); } + function _baseURI() internal view virtual override returns (string memory) { + return baseTokenURI; + } + + function setBaseURI(string memory _baseTokenURI) public onlyOwner { + baseTokenURI = _baseTokenURI; + } + /// @notice Change the MintLimit for the Nfts /// @param lowerLimit the new lower limit for how short a nft can be subscribed for /// @param upperLimit the new upper limit for how long a nft can be subscribed for @@ -132,13 +137,11 @@ contract BrainPassCollectibles is /// @notice Add a new Pass Type /// @param pricePerDay the price per day of the new pass type - /// @param tokenUri the link that stores the data of all the Nfts in the new pass /// @param name the name of the new pass type to be added /// @param maxTokens the total number of tokens in the pass /// @param discount the amount in % to be deducted when buying the pass function addPassType( uint256 pricePerDay, - string memory tokenUri, string memory name, uint256 maxTokens, uint256 discount @@ -149,11 +152,9 @@ contract BrainPassCollectibles is passId, name, pricePerDay, - tokenUri, maxTokens, discount, - passTypes[passId - 1].currentNftCount, - passTypes[passId - 1].currentNftCount + maxTokens, + 0, false ); passIdTracker.increment(); @@ -161,7 +162,7 @@ contract BrainPassCollectibles is } /// @notice Toggles the staus of a pass type (paused or unpaused) - /// @param passId the Id of the pass + /// @param passId the Id of the pass function togglePassTypeStatus( uint256 passId ) external onlyOwner whenNotPaused { @@ -173,11 +174,9 @@ contract BrainPassCollectibles is passType.passId, passType.name, passType.pricePerDay, - passType.tokenURI, passType.maxTokens, passType.discount, - passType.lastTokenIdMinted, - passType.currentNftCount, + passType.lastMintedId, newStatus ); @@ -199,7 +198,7 @@ contract BrainPassCollectibles is PassType storage passType = passTypes[passId]; if (passType.isPaused) revert CannotMintPausedPassType(); - if (passType.lastTokenIdMinted >= passType.currentNftCount) + if (passType.lastMintedId >= passType.maxTokens) revert PassMaxSupplyReached(); if (!validatePassDuration(startTimestamp, endTimestamp)) revert DurationNotInTimeFrame(); @@ -209,7 +208,8 @@ contract BrainPassCollectibles is bool success = IERC20(iqToken).transferFrom(msg.sender, owner(), price); if (!success) revert MintingPaymentFailed(); - uint256 tokenId = passType.lastTokenIdMinted + 1; + uint256 tokenId = tokenIdTracker.current(); + passType.lastMintedId += 1; UserPassItem memory purchase = UserPassItem( tokenId, passId, @@ -217,10 +217,9 @@ contract BrainPassCollectibles is endTimestamp ); addressToNFTPass[msg.sender] = purchase; - passType.lastTokenIdMinted = tokenId; + tokenIdTracker.increment(); _safeMint(msg.sender, tokenId); - _setTokenURI(tokenId, passType.tokenURI); emit BrainPassBought( msg.sender, @@ -367,36 +366,6 @@ contract BrainPassCollectibles is return (passType); } - /// ----------------------------------------------------------------------- - /// Override Functions - /// ----------------------------------------------------------------------- - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal override(ERC721, ERC721Enumerable) { - super._beforeTokenTransfer(from, to, tokenId); - } - - function _burn( - uint256 tokenId - ) internal override(ERC721, ERC721URIStorage) { - super._burn(tokenId); - } - - function tokenURI( - uint256 tokenId - ) public view override(ERC721, ERC721URIStorage) returns (string memory) { - return super.tokenURI(tokenId); - } - - function supportsInterface( - bytes4 interfaceId - ) public view override(ERC721, ERC721Enumerable) returns (bool) { - return super.supportsInterface(interfaceId); - } - /// ----------------------------------------------------------------------- /// Events /// ----------------------------------------------------------------------- diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 48980f0..5a5b8d4 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -19,12 +19,15 @@ contract BrainPassTest is PRBTest, Cheats { function setUp() public { mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token - BrainPass = new BrainPassCollectibles(address(mockERC20)); - BrainPass.addPassType(15e18, "http://example.com", "Gold", 200, 0); + BrainPass = new BrainPassCollectibles( + address(mockERC20), + "http://example.com/" + ); + BrainPass.addPassType(15e18, "Gold", 200, 0); } function testAddPassType() public { - BrainPass.addPassType(15e18, "http://example.com", "Gold", 200, 0); + BrainPass.addPassType(15e18, "Gold", 200, 0); string memory _name = BrainPass.getPassType(1).name; assertEq(_name, "Gold"); } @@ -54,13 +57,7 @@ contract BrainPassTest is PRBTest, Cheats { vm.expectRevert( BrainPassCollectibles.InvalidMaxTokensForAPass.selector ); - BrainPass.addPassType( - 15e18, - "http://example.orgs", - "OleanjiPass", - 0, - 0 - ); + BrainPass.addPassType(15e18, "OleanjiPass", 0, 0); } function testPassTypeNotFound() public { @@ -99,14 +96,8 @@ contract BrainPassTest is PRBTest, Cheats { } function testDifferentPassMint() public { - BrainPass.addPassType( - 15e18, - "http://example.orgs", - "OleanjiPass", - 2, - 0 - ); - BrainPass.addPassType(15e18, "http://oleanji.com", "KesarPass", 2, 0); + BrainPass.addPassType(15e18, "OleanjiPass", 2, 0); + BrainPass.addPassType(15e18, "KesarPass", 2, 0); mockERC20.mint(alice, 20000e18); mockERC20.mint(doe, 20000e18); @@ -114,27 +105,27 @@ contract BrainPassTest is PRBTest, Cheats { vm.startPrank(alice); mockERC20.approve(address(BrainPass), 20000e18); - BrainPass.mintNFT(1, 172800, 5184000); //"http://example.com" + BrainPass.mintNFT(1, 172800, 5184000); uint256 firstId = BrainPass.getUserPassDetails(alice).tokenId; - assertEq(BrainPass.tokenURI(firstId), "http://example.com"); + assertEq(BrainPass.tokenURI(firstId), "http://example.com/1"); console.log(BrainPass.tokenURI(firstId), firstId); vm.stopPrank(); vm.startPrank(doe); mockERC20.approve(address(BrainPass), 20000e18); - BrainPass.mintNFT(2, 172800, 5184000); // "http://example.orgs", + BrainPass.mintNFT(2, 172800, 5184000); uint256 newId = BrainPass.getUserPassDetails(doe).tokenId; - assertEq(BrainPass.tokenURI(newId), "http://example.orgs"); + assertEq(BrainPass.tokenURI(newId), "http://example.com/2"); console.log(BrainPass.tokenURI(newId), newId); vm.stopPrank(); vm.startPrank(bob); mockERC20.approve(address(BrainPass), 20000e18); - BrainPass.mintNFT(3, 172800, 5184000); //"http://oleanji.com" + BrainPass.mintNFT(3, 172800, 5184000); uint256 newIds = BrainPass.getUserPassDetails(bob).tokenId; - assertEq(BrainPass.tokenURI(newIds), "http://oleanji.com"); + assertEq(BrainPass.tokenURI(newIds), "http://example.com/3"); console.log(BrainPass.tokenURI(newIds), newIds); uint256 mintedPass = BrainPass.getUserPassDetails(doe).tokenId; console.log(BrainPass.balanceOf(alice)); - assertEq(mintedPass, 201); + assertEq(mintedPass, 2); vm.stopPrank(); } @@ -169,25 +160,13 @@ contract BrainPassTest is PRBTest, Cheats { function testGetAllPassType() public { assertEq(BrainPass.getAllPassType().length, 2); - BrainPass.addPassType( - 400e18, - "http://example.com/56", - "Platinum", - 3000, - 10 - ); + BrainPass.addPassType(400e18, "Platinum", 3000, 10); assertEq(BrainPass.getPassType(2).name, "Platinum"); assertEq(BrainPass.getAllPassType().length, 3); } function testMintNftWrong() public { - BrainPass.addPassType( - 15e18, - "http://example.orgs", - "OleanjiPass", - 2, - 0 - ); + BrainPass.addPassType(15e18, "OleanjiPass", 2, 0); mockERC20.mint(alice, 20000e18); mockERC20.mint(bob, 20000e18); mockERC20.mint(doe, 20000e18); diff --git a/test/BrainPassValidator.t.sol b/test/BrainPassValidator.t.sol index f25c35c..e4f34b0 100644 --- a/test/BrainPassValidator.t.sol +++ b/test/BrainPassValidator.t.sol @@ -19,9 +19,9 @@ contract BrainPassValidatorTest is PRBTest, Cheats { function setUp() public { mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token - BrainPass = new BrainPassCollectibles(address(mockERC20)); + BrainPass = new BrainPassCollectibles(address(mockERC20), "http://example.com"); brainPassValidator = new BrainPassValidiator(address(BrainPass)); - BrainPass.addPassType(15e18, "http://example.com", "Gold", 200, 0); + BrainPass.addPassType(15e18,"Gold", 200, 0); } function testPostWiki() public { From 9a230bf7481e0fdfbc4b8bcf9b92d4ab36133add Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 31 May 2023 15:43:04 +0100 Subject: [PATCH 36/54] added more tests for the validator --- src/BrainPass/BrainPass.sol | 8 +++++--- test/BrainPass.t.sol | 2 +- test/BrainPassValidator.t.sol | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 1b50ee8..7181d99 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -38,7 +38,7 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { error NoEtherLeftToWithdraw(); error TransferFailed(); error DurationNotInTimeFrame(); - error CannotMintPausedPassType(); + error PassTypeIsPaused(); error NoIQLeftToWithdraw(); /// ----------------------------------------------------------------------- @@ -197,7 +197,7 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { revert AlreadyMintedAPass(); PassType storage passType = passTypes[passId]; - if (passType.isPaused) revert CannotMintPausedPassType(); + if (passType.isPaused) revert PassTypeIsPaused(); if (passType.lastMintedId >= passType.maxTokens) revert PassMaxSupplyReached(); if (!validatePassDuration(startTimestamp, endTimestamp)) @@ -240,7 +240,7 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { UserPassItem memory pass = addressToNFTPass[msg.sender]; PassType storage passType = passTypes[pass.passId]; - if (passType.isPaused) revert CannotMintPausedPassType(); + if (passType.isPaused) revert PassTypeIsPaused(); if (addressToNFTPass[msg.sender].tokenId != tokenId) revert NotTheOwnerOfThisNft(); @@ -344,6 +344,8 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { address user ) public view returns (UserPassItem memory) { UserPassItem memory userToken = addressToNFTPass[user]; + PassType storage passType = passTypes[userToken.passId]; + if (passType.isPaused) revert PassTypeIsPaused(); return userToken; } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 5a5b8d4..8d6786a 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -74,7 +74,7 @@ contract BrainPassTest is PRBTest, Cheats { vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); vm.expectRevert( - BrainPassCollectibles.CannotMintPausedPassType.selector + BrainPassCollectibles.PassTypeIsPaused.selector ); BrainPass.mintNFT(1, 172800, 5184000); } diff --git a/test/BrainPassValidator.t.sol b/test/BrainPassValidator.t.sol index e4f34b0..32722bd 100644 --- a/test/BrainPassValidator.t.sol +++ b/test/BrainPassValidator.t.sol @@ -19,23 +19,48 @@ contract BrainPassValidatorTest is PRBTest, Cheats { function setUp() public { mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token - BrainPass = new BrainPassCollectibles(address(mockERC20), "http://example.com"); + BrainPass = new BrainPassCollectibles( + address(mockERC20), + "http://example.com" + ); brainPassValidator = new BrainPassValidiator(address(BrainPass)); - BrainPass.addPassType(15e18,"Gold", 200, 0); + BrainPass.addPassType(15e18, "Gold", 200, 0); } - function testPostWiki() public { + function testPostWikiUserWithNoPass() public { vm.expectRevert(BrainPassValidiator.UserDoesNotHaveAPass.selector); brainPassValidator.validate(alice); + } + + function testPostWikiRight() public { mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 1700e18); BrainPass.mintNFT(1, 1685638993, 1693587793); // june 1st - sept 1st (3 months) + assertEq(brainPassValidator.validate(alice), true); vm.stopPrank(); + } + + function testPostWikiPassExpired() public { + mockERC20.mint(alice, 20000e18); + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 1700e18); + BrainPass.mintNFT(1, 1685638993, 1693587793); assertEq(brainPassValidator.validate(alice), true); - skip(1685638993 + 7948800); //skipped from the start date till the end of the 3 months - console.log(block.timestamp); + skip(1685638993 + 7948800); vm.expectRevert(BrainPassValidiator.UserPassExpired.selector); brainPassValidator.validate(alice); } + + function testPostWikiWithPausedPass() public { + mockERC20.mint(alice, 20000e18); + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 1700e18); + BrainPass.mintNFT(1, 1685638993, 1693587793); + vm.stopPrank(); + assertEq(brainPassValidator.validate(alice), true); + BrainPass.togglePassTypeStatus(1); + vm.expectRevert(BrainPassCollectibles.PassTypeIsPaused.selector); + brainPassValidator.validate(alice); + } } From 3e6c1f67b55fe9a9a44cc962d43d03d55fb3686b Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 31 May 2023 15:47:25 +0100 Subject: [PATCH 37/54] updated the contract --- README.md | 1 - src/BrainPass/BrainPass.sol | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0610f43..864991f 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,6 @@ Only the contract owner can call this function. ## `addPassType()` Adds a new pass type. - `pricePerDay`: Price per day of the new pass type. -- `tokenUri`: URI that stores the data of all the NFTs in the new pass. - `name`: Name of the new pass type. - `maxTokens`: Total number of tokens in the pass. - `discount`: Discount amount (in percentage) to be deducted when buying the pass. diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 7181d99..2fae0f4 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -116,10 +116,6 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { _unpause(); } - function _baseURI() internal view virtual override returns (string memory) { - return baseTokenURI; - } - function setBaseURI(string memory _baseTokenURI) public onlyOwner { baseTokenURI = _baseTokenURI; } @@ -338,6 +334,11 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { /// Getters /// ----------------------------------------------------------------------- + /// @notice Gets the tokenUri for the contract + function _baseURI() internal view virtual override returns (string memory) { + return baseTokenURI; + } + /// @notice Gets all the NFT owned by an address /// @param user The address of the user function getUserPassDetails( From a4065fcd9ddc5773528d81eeff7066574fa77a09 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Thu, 1 Jun 2023 10:44:08 +0100 Subject: [PATCH 38/54] little changes --- script/BrainPassDeployer.s.sol | 4 ++-- src/BrainPass/BrainPass.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/script/BrainPassDeployer.s.sol b/script/BrainPassDeployer.s.sol index 9496b78..b42025e 100644 --- a/script/BrainPassDeployer.s.sol +++ b/script/BrainPassDeployer.s.sol @@ -14,11 +14,11 @@ contract BrainPassDeployer is Script { vm.startBroadcast(); console.log("Deploying Brainpass deployer...."); - BrainPassCollectibles validator = new BrainPassCollectibles( + BrainPassCollectibles brainPass = new BrainPassCollectibles( 0x5E959c60f86D17fb7D764AB69B654227d464E820, "http://example.com" ); - console.log("Brainpass Deployed", address(validator)); + console.log("Brainpass Deployed", address(brainPass)); vm.stopBroadcast(); } diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 2fae0f4..bee2d97 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -72,7 +72,7 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { /// ----------------------------------------------------------------------- mapping(uint256 => PassType) public passTypes; - mapping(address => UserPassItem) public addressToNFTPass; + mapping(address => UserPassItem) internal addressToNFTPass; /// ----------------------------------------------------------------------- /// Constant @@ -354,7 +354,7 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { function getAllPassType() external view returns (PassType[] memory) { uint256 total = passIdTracker.current(); PassType[] memory passType = new PassType[](total); - for (uint256 i = 0; i < total; i++) { + for (uint256 i = 1; i < total; i++) { passType[i] = passTypes[i]; } return passType; From 49a366158636b0b17ea6afa20e05b90894b1c476 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Mon, 5 Jun 2023 20:38:59 +0100 Subject: [PATCH 39/54] checking for validity of the timestamp entered --- src/BrainPass/BrainPass.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index bee2d97..4f1ee2e 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -325,6 +325,9 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { uint256 endTimestamp ) internal view returns (bool) { uint256 durationInDays = (endTimestamp - startTimestamp) / 1 days; + if (endTimestamp < block.timestamp) { + revert DurationNotInTimeFrame(); + } return durationInDays >= MINT_LOWER_LIMIT && durationInDays <= MINT_UPPER_LIMIT; From 2fe00bdef68de169f7321f25450a8ca3416eb9a3 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Fri, 9 Jun 2023 16:10:19 +0100 Subject: [PATCH 40/54] little fixes in the contract --- src/BrainPass/BrainPass.sol | 44 +++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 4f1ee2e..021caaa 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -24,10 +24,10 @@ interface IERC20 { /// @notice A pass for IQ Wiki Editors contract BrainPassCollectibles is ERC721, Pausable, Ownable { + /// ----------------------------------------------------------------------- /// Errors /// ----------------------------------------------------------------------- - error MintingPaymentFailed(); error IncreseTimePaymentFailed(); error AlreadyMintedAPass(); @@ -35,11 +35,11 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { error InvalidMaxTokensForAPass(); error PassTypeNotFound(); error PassMaxSupplyReached(); - error NoEtherLeftToWithdraw(); + error EtherNotEnoughToWithdraw(); error TransferFailed(); error DurationNotInTimeFrame(); error PassTypeIsPaused(); - error NoIQLeftToWithdraw(); + error IQNotEnoughToWithdraw(); /// ----------------------------------------------------------------------- /// Inheritances @@ -201,7 +201,11 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { uint256 price = calculatePrice(passId, startTimestamp, endTimestamp); - bool success = IERC20(iqToken).transferFrom(msg.sender, owner(), price); + bool success = IERC20(iqToken).transferFrom( + msg.sender, + address(this), + price + ); if (!success) revert MintingPaymentFailed(); uint256 tokenId = tokenIdTracker.current(); @@ -235,7 +239,7 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { ) external whenNotPaused { UserPassItem memory pass = addressToNFTPass[msg.sender]; - PassType storage passType = passTypes[pass.passId]; + PassType memory passType = passTypes[pass.passId]; if (passType.isPaused) revert PassTypeIsPaused(); if (addressToNFTPass[msg.sender].tokenId != tokenId) @@ -251,7 +255,11 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { revert DurationNotInTimeFrame(); uint256 price = calculatePrice(pass.passId, newStartTime, newEndTime); - bool success = IERC20(iqToken).transferFrom(msg.sender, owner(), price); + bool success = IERC20(iqToken).transferFrom( + msg.sender, + address(this), + price + ); if (!success) revert IncreseTimePaymentFailed(); UserPassItem memory purchase = UserPassItem( @@ -272,20 +280,23 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { } /// @notice Withdraws any amount in the contract - function withdrawEther() external payable onlyOwner { + function withdrawEther( + address receiver, + uint256 amount + ) external payable onlyOwner { uint256 ethbalance = address(this).balance; - if (ethbalance <= 0) revert NoEtherLeftToWithdraw(); - - (bool success, ) = (msg.sender).call{value: ethbalance}(""); + if (ethbalance < amount) revert EtherNotEnoughToWithdraw(); + (bool success, ) = (receiver).call{value: amount}(""); if (!success) revert TransferFailed(); } - function withdrawIQ() external payable onlyOwner { + function withdrawIQ( + address receiver, + uint256 amount + ) external payable onlyOwner { uint256 tokenBalance = IERC20(iqToken).balanceOf(address(this)); - - if (tokenBalance <= 0) revert NoIQLeftToWithdraw(); - - bool tokenSuccess = IERC20(iqToken).transfer(msg.sender, tokenBalance); + if (tokenBalance < amount) revert IQNotEnoughToWithdraw(); + bool tokenSuccess = IERC20(iqToken).transfer(receiver, amount); if (!tokenSuccess) revert TransferFailed(); } @@ -348,8 +359,7 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { address user ) public view returns (UserPassItem memory) { UserPassItem memory userToken = addressToNFTPass[user]; - PassType storage passType = passTypes[userToken.passId]; - if (passType.isPaused) revert PassTypeIsPaused(); + PassType memory passType = passTypes[userToken.passId]; return userToken; } From 721b93174ae1b602c8bdd827d651bf5374ddcfa3 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Fri, 9 Jun 2023 16:29:30 +0100 Subject: [PATCH 41/54] improved test from edited contract --- src/BrainPass/BrainPass.sol | 5 ++--- test/BrainPass.t.sol | 18 +++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 021caaa..931fcf5 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -24,7 +24,7 @@ interface IERC20 { /// @notice A pass for IQ Wiki Editors contract BrainPassCollectibles is ERC721, Pausable, Ownable { - + /// ----------------------------------------------------------------------- /// Errors /// ----------------------------------------------------------------------- @@ -311,7 +311,7 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { uint256 passId, uint256 startTimestamp, uint256 endTimestamp - ) internal view returns (uint256) { + ) public view returns (uint256) { PassType memory passType = passTypes[passId]; uint256 subscriptionPeriodInSeconds = endTimestamp - startTimestamp; @@ -359,7 +359,6 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { address user ) public view returns (UserPassItem memory) { UserPassItem memory userToken = addressToNFTPass[user]; - PassType memory passType = passTypes[userToken.passId]; return userToken; } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 8d6786a..04aeeac 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -73,9 +73,7 @@ contract BrainPassTest is PRBTest, Cheats { mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); - vm.expectRevert( - BrainPassCollectibles.PassTypeIsPaused.selector - ); + vm.expectRevert(BrainPassCollectibles.PassTypeIsPaused.selector); BrainPass.mintNFT(1, 172800, 5184000); } @@ -89,7 +87,7 @@ contract BrainPassTest is PRBTest, Cheats { assertEq(BrainPass.balanceOf(alice), 0); BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); - assertEq(mockERC20.balanceOf(address(this)), 870e18); + assertEq(mockERC20.balanceOf(address(BrainPass)), 870e18); uint256 mintedPass = BrainPass.getUserPassDetails(alice).tokenId; assertEq(mintedPass, 1); vm.stopPrank(); @@ -152,7 +150,7 @@ contract BrainPassTest is PRBTest, Cheats { uint256 _tokenId = BrainPass.getUserPassDetails(alice).tokenId; assertEq(_tokenId, 1); BrainPass.increaseEndTime(_tokenId, 8640000); - assertEq(mockERC20.balanceOf(address(this)), 1470e18); + assertEq(mockERC20.balanceOf(address(BrainPass)), 1470e18); BrainPass.getUserPassDetails(alice); uint _endTine = BrainPass.getUserPassDetails(alice).endTimestamp; assertEq(_endTine, 8640000); @@ -188,13 +186,15 @@ contract BrainPassTest is PRBTest, Cheats { } function testWithdrawTokens() public { - vm.expectRevert(BrainPassCollectibles.NoIQLeftToWithdraw.selector); - BrainPass.withdrawIQ(); + vm.expectRevert(BrainPassCollectibles.IQNotEnoughToWithdraw.selector); + BrainPass.withdrawIQ(alice, 200e18); assertEq(mockERC20.balanceOf(address(BrainPass)), 0); mockERC20.mint(address(BrainPass), 20000e18); assertEq(mockERC20.balanceOf(address(BrainPass)), 20000e18); - vm.expectRevert(BrainPassCollectibles.NoEtherLeftToWithdraw.selector); - BrainPass.withdrawEther(); + vm.expectRevert( + BrainPassCollectibles.EtherNotEnoughToWithdraw.selector + ); + BrainPass.withdrawEther(alice, 200e18); } function testConfigureMintLimit() public { From 3f9a1ebcd6d6619df683669750205c5a9b156033 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Fri, 9 Jun 2023 16:40:12 +0100 Subject: [PATCH 42/54] improved test from edited contract --- src/BrainPass/BrainPass.sol | 2 +- src/BrainPass/BrainPassValidator.sol | 9 +++++++++ test/BrainPassValidator.t.sol | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 931fcf5..c2135fe 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -311,7 +311,7 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { uint256 passId, uint256 startTimestamp, uint256 endTimestamp - ) public view returns (uint256) { + ) internal view returns (uint256) { PassType memory passType = passTypes[passId]; uint256 subscriptionPeriodInSeconds = endTimestamp - startTimestamp; diff --git a/src/BrainPass/BrainPassValidator.sol b/src/BrainPass/BrainPassValidator.sol index a74f3a5..4b47477 100644 --- a/src/BrainPass/BrainPassValidator.sol +++ b/src/BrainPass/BrainPassValidator.sol @@ -12,6 +12,7 @@ contract BrainPassValidiator { /// ----------------------------------------------------------------------- error UserDoesNotHaveAPass(); error UserPassExpired(); + error PassIsPaused(); /// ----------------------------------------------------------------------- /// variables @@ -33,6 +34,14 @@ contract BrainPassValidiator { if (brainPass.getUserPassDetails(user).endTimestamp < block.timestamp) { revert UserPassExpired(); } + if ( + brainPass + .getPassType(brainPass.getUserPassDetails(user).passId) + .isPaused + ) { + revert PassIsPaused(); + } + return true; } } diff --git a/test/BrainPassValidator.t.sol b/test/BrainPassValidator.t.sol index 32722bd..43a595b 100644 --- a/test/BrainPassValidator.t.sol +++ b/test/BrainPassValidator.t.sol @@ -60,7 +60,7 @@ contract BrainPassValidatorTest is PRBTest, Cheats { vm.stopPrank(); assertEq(brainPassValidator.validate(alice), true); BrainPass.togglePassTypeStatus(1); - vm.expectRevert(BrainPassCollectibles.PassTypeIsPaused.selector); + vm.expectRevert(BrainPassValidiator.PassIsPaused.selector); brainPassValidator.validate(alice); } } From f717fe8a8d4bfe2875406276f81a2a0dff026c7a Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Fri, 9 Jun 2023 20:19:22 +0100 Subject: [PATCH 43/54] using erc721pausable --- src/BrainPass/BrainPass.sol | 17 +++++++++++++---- test/BrainPass.t.sol | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index c2135fe..57f6c90 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.13; import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; +import {ERC721Pausable} from "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Pausable.sol"; import {Counters} from "openzeppelin-contracts/contracts/utils/Counters.sol"; import "openzeppelin-contracts/contracts/access/Ownable.sol"; -import "openzeppelin-contracts/contracts/security/Pausable.sol"; import {Wiki} from "../../src/Wiki.sol"; interface IERC20 { @@ -23,8 +23,7 @@ interface IERC20 { /// @author Oleanji /// @notice A pass for IQ Wiki Editors -contract BrainPassCollectibles is ERC721, Pausable, Ownable { - +contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { /// ----------------------------------------------------------------------- /// Errors /// ----------------------------------------------------------------------- @@ -97,13 +96,23 @@ contract BrainPassCollectibles is ERC721, Pausable, Ownable { constructor( address IqAddr, string memory _baseTokenURI - ) ERC721("BAINPASS", "BEP") { + ) ERC721("BRAINPASS", "BRP") { iqToken = IqAddr; passIdTracker.increment(); tokenIdTracker.increment(); setBaseURI(_baseTokenURI); } + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override(ERC721, ERC721Pausable) { + super._beforeTokenTransfer(from, to, amount); + // if(!paused()) + // require(, "ERC721Pausable: token transfer while paused"); + } + /// ----------------------------------------------------------------------- /// External functions /// ----------------------------------------------------------------------- diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 04aeeac..ac04cf5 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -218,5 +218,7 @@ contract BrainPassTest is PRBTest, Cheats { assertEq(BrainPass.balanceOf(alice), 0); BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); + BrainPass.safeTransferFrom(alice, bob, 1); + assertEq(BrainPass.balanceOf(bob), 1); } } From 717d7a0a3b2a6baf8c572e36705afdb8340b99cc Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Fri, 9 Jun 2023 20:48:23 +0100 Subject: [PATCH 44/54] using erc721pausable tests --- src/BrainPass/BrainPass.sol | 2 -- test/BrainPass.t.sol | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 57f6c90..3ab15dd 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -109,8 +109,6 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { uint256 amount ) internal virtual override(ERC721, ERC721Pausable) { super._beforeTokenTransfer(from, to, amount); - // if(!paused()) - // require(, "ERC721Pausable: token transfer while paused"); } /// ----------------------------------------------------------------------- diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index ac04cf5..611c0f7 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -218,7 +218,11 @@ contract BrainPassTest is PRBTest, Cheats { assertEq(BrainPass.balanceOf(alice), 0); BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); + vm.stopPrank(); + BrainPass.pause(); + vm.startPrank(alice); + vm.expectRevert("ERC721Pausable: token transfer while paused"); BrainPass.safeTransferFrom(alice, bob, 1); - assertEq(BrainPass.balanceOf(bob), 1); + assertEq(BrainPass.balanceOf(bob), 0); } } From 62606752e276cdd8d28a098a9dd82a543c316ad5 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Sat, 10 Jun 2023 17:34:24 +0100 Subject: [PATCH 45/54] improving the brainPassValidator --- script/BrainPassValidator.s.sol | 21 +++++++++++++++++++++ src/BrainPass/BrainPassValidator.sol | 12 +++--------- test/BrainPassValidator.t.sol | 10 ++++------ 3 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 script/BrainPassValidator.s.sol diff --git a/script/BrainPassValidator.s.sol b/script/BrainPassValidator.s.sol new file mode 100644 index 0000000..b559fa5 --- /dev/null +++ b/script/BrainPassValidator.s.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "../lib/forge-std/src/Script.sol"; +import {console} from "../lib/forge-std/src/console.sol"; + +import {BrainPassValidiator} from "../src/BrainPass/BrainPassValidator.sol"; + +contract BrainPassValidiatorDeployer is Script { + function run() external { + vm.startBroadcast(); + + console.log("Deploying Brainpass Valdiator...."); + BrainPassValidiator brainPassValidator = new BrainPassValidiator( + 0x6e213cE219d7ef282ACCC7734040D67875828be4 + ); + console.log("Brainpass Deployed", address(brainPassValidator)); + + vm.stopBroadcast(); + } +} diff --git a/src/BrainPass/BrainPassValidator.sol b/src/BrainPass/BrainPassValidator.sol index 4b47477..6683f56 100644 --- a/src/BrainPass/BrainPassValidator.sol +++ b/src/BrainPass/BrainPassValidator.sol @@ -7,12 +7,6 @@ import {BrainPassCollectibles} from "./BrainPass.sol"; /// @author Oleanji /// @notice A validation for the Nft contract BrainPassValidiator { - /// ----------------------------------------------------------------------- - /// Errors - /// ----------------------------------------------------------------------- - error UserDoesNotHaveAPass(); - error UserPassExpired(); - error PassIsPaused(); /// ----------------------------------------------------------------------- /// variables @@ -30,16 +24,16 @@ contract BrainPassValidiator { /// @notice Validate Post /// @param user The user to validiate function validate(address user) external view returns (bool) { - if (brainPass.balanceOf(user) <= 0) revert UserDoesNotHaveAPass(); + if (brainPass.balanceOf(user) <= 0) return false; if (brainPass.getUserPassDetails(user).endTimestamp < block.timestamp) { - revert UserPassExpired(); + return false; } if ( brainPass .getPassType(brainPass.getUserPassDetails(user).passId) .isPaused ) { - revert PassIsPaused(); + return false; } return true; diff --git a/test/BrainPassValidator.t.sol b/test/BrainPassValidator.t.sol index 43a595b..b986793 100644 --- a/test/BrainPassValidator.t.sol +++ b/test/BrainPassValidator.t.sol @@ -28,8 +28,7 @@ contract BrainPassValidatorTest is PRBTest, Cheats { } function testPostWikiUserWithNoPass() public { - vm.expectRevert(BrainPassValidiator.UserDoesNotHaveAPass.selector); - brainPassValidator.validate(alice); + assertEq(brainPassValidator.validate(alice), false); } function testPostWikiRight() public { @@ -48,8 +47,7 @@ contract BrainPassValidatorTest is PRBTest, Cheats { BrainPass.mintNFT(1, 1685638993, 1693587793); assertEq(brainPassValidator.validate(alice), true); skip(1685638993 + 7948800); - vm.expectRevert(BrainPassValidiator.UserPassExpired.selector); - brainPassValidator.validate(alice); + assertEq(brainPassValidator.validate(alice), false); } function testPostWikiWithPausedPass() public { @@ -60,7 +58,7 @@ contract BrainPassValidatorTest is PRBTest, Cheats { vm.stopPrank(); assertEq(brainPassValidator.validate(alice), true); BrainPass.togglePassTypeStatus(1); - vm.expectRevert(BrainPassValidiator.PassIsPaused.selector); - brainPassValidator.validate(alice); + + assertEq(brainPassValidator.validate(alice), false); } } From fc932edad7a5e60d19909434aea837754fc91752 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Sun, 11 Jun 2023 19:56:15 +0100 Subject: [PATCH 46/54] improved the READMe file --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 864991f..97b5ff9 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,6 @@ Withdraws any amount of IQ tokens held in the contract. Can only be called by th ## `getUserPassDetails()` Retrieves the details of an NFT owned by a specific user for a given pass type. - `user`: Address of the user. -- `passId`: ID of the pass type. ## `getAllPassType()` Retrieves the details of all the pass types added to the contract. @@ -112,7 +111,7 @@ The contract emits the following events: - `BrainPassBought`: Emitted when a user buys a BrainPass NFT. - `TimeIncreased`: Emitted when the time of a BrainPass NFT is increased. - `NewPassAdded`: Emitted when a new pass type is added. -- `PassTypePaused`: Emitted when a pass is paused. +- `PassTypeStatusToggled`: Emitted when a pass is paused or unpaused. ## PICTORAL EXPLANATION ![image](https://github.com/EveripediaNetwork/ep-contract/assets/75235148/eee4d631-28d9-4ca4-bc0e-62e5a02998a2) From 8669373811e1a6b482371883a9ca82a9e92d8e57 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Tue, 13 Jun 2023 12:02:13 +0100 Subject: [PATCH 47/54] removed unused code and improved tests --- src/BrainPass/BrainPassValidator.sol | 9 +-------- test/BrainPass.t.sol | 13 ++++++++++++- test/BrainPassValidator.t.sol | 12 ------------ 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/BrainPass/BrainPassValidator.sol b/src/BrainPass/BrainPassValidator.sol index 6683f56..bfa33ec 100644 --- a/src/BrainPass/BrainPassValidator.sol +++ b/src/BrainPass/BrainPassValidator.sol @@ -24,17 +24,10 @@ contract BrainPassValidiator { /// @notice Validate Post /// @param user The user to validiate function validate(address user) external view returns (bool) { - if (brainPass.balanceOf(user) <= 0) return false; + if (brainPass.balanceOf(user) == 0) return false; if (brainPass.getUserPassDetails(user).endTimestamp < block.timestamp) { return false; } - if ( - brainPass - .getPassType(brainPass.getUserPassDetails(user).passId) - .isPaused - ) { - return false; - } return true; } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 611c0f7..99c4c95 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -185,7 +185,7 @@ contract BrainPassTest is PRBTest, Cheats { BrainPass.mintNFT(2, 172800, 5184000); } - function testWithdrawTokens() public { + function testWithdrawTokensErrors() public { vm.expectRevert(BrainPassCollectibles.IQNotEnoughToWithdraw.selector); BrainPass.withdrawIQ(alice, 200e18); assertEq(mockERC20.balanceOf(address(BrainPass)), 0); @@ -197,6 +197,17 @@ contract BrainPassTest is PRBTest, Cheats { BrainPass.withdrawEther(alice, 200e18); } + function testWithdrawTokens() public { + mockERC20.mint(address(BrainPass), 200e18); + BrainPass.withdrawIQ(alice, 200e18); + assertEq(mockERC20.balanceOf(address(alice)), 200e18); + vm.deal(address(BrainPass), 4 ether); + assertEq(address(BrainPass).balance, 4e18); + assertEq(address(alice).balance, 0); + BrainPass.withdrawEther(alice, 4e18); + assertEq(address(alice).balance, 4e18); + } + function testConfigureMintLimit() public { assertEq(BrainPass.MINT_LOWER_LIMIT(), 28); assertEq(BrainPass.MINT_UPPER_LIMIT(), 365); diff --git a/test/BrainPassValidator.t.sol b/test/BrainPassValidator.t.sol index b986793..839df32 100644 --- a/test/BrainPassValidator.t.sol +++ b/test/BrainPassValidator.t.sol @@ -49,16 +49,4 @@ contract BrainPassValidatorTest is PRBTest, Cheats { skip(1685638993 + 7948800); assertEq(brainPassValidator.validate(alice), false); } - - function testPostWikiWithPausedPass() public { - mockERC20.mint(alice, 20000e18); - vm.startPrank(alice); - mockERC20.approve(address(BrainPass), 1700e18); - BrainPass.mintNFT(1, 1685638993, 1693587793); - vm.stopPrank(); - assertEq(brainPassValidator.validate(alice), true); - BrainPass.togglePassTypeStatus(1); - - assertEq(brainPassValidator.validate(alice), false); - } } From fa5b0fb32eb372fc60066227c5adee1a8e3d0973 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Tue, 13 Jun 2023 15:45:58 +0100 Subject: [PATCH 48/54] removed discount --- README.md | 1 - src/BrainPass/BrainPass.sol | 30 ++++++++++-------------------- test/BrainPass.t.sol | 14 +++++++------- test/BrainPassValidator.t.sol | 2 +- 4 files changed, 18 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 97b5ff9..904f52b 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,6 @@ Adds a new pass type. - `pricePerDay`: Price per day of the new pass type. - `name`: Name of the new pass type. - `maxTokens`: Total number of tokens in the pass. -- `discount`: Discount amount (in percentage) to be deducted when buying the pass. Only the contract owner can call this function and only when the contract is not paused ## `togglePassTypeStatus()` diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 3ab15dd..5a116eb 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -61,7 +61,6 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { string name; uint256 pricePerDay; uint256 maxTokens; - uint256 discount; uint256 lastMintedId; bool isPaused; } @@ -123,10 +122,6 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { _unpause(); } - function setBaseURI(string memory _baseTokenURI) public onlyOwner { - baseTokenURI = _baseTokenURI; - } - /// @notice Change the MintLimit for the Nfts /// @param lowerLimit the new lower limit for how short a nft can be subscribed for /// @param upperLimit the new upper limit for how long a nft can be subscribed for @@ -142,12 +137,10 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { /// @param pricePerDay the price per day of the new pass type /// @param name the name of the new pass type to be added /// @param maxTokens the total number of tokens in the pass - /// @param discount the amount in % to be deducted when buying the pass function addPassType( uint256 pricePerDay, string memory name, - uint256 maxTokens, - uint256 discount + uint256 maxTokens ) external onlyOwner whenNotPaused { if (maxTokens <= 0) revert InvalidMaxTokensForAPass(); uint256 passId = passIdTracker.current(); @@ -156,7 +149,6 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { name, pricePerDay, maxTokens, - discount, 0, false ); @@ -178,7 +170,6 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { passType.name, passType.pricePerDay, passType.maxTokens, - passType.discount, passType.lastMintedId, newStatus ); @@ -320,18 +311,9 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { uint256 endTimestamp ) internal view returns (uint256) { PassType memory passType = passTypes[passId]; - uint256 subscriptionPeriodInSeconds = endTimestamp - startTimestamp; - - uint256 subscriptionPeriodInDays = subscriptionPeriodInSeconds / 1 days; - + uint256 subscriptionPeriodInDays = (endTimestamp - startTimestamp) /1 days; // Calculate the total price uint256 totalPrice = subscriptionPeriodInDays * passType.pricePerDay; - - if (passType.discount > 0) { - uint256 discountAmount = (totalPrice * passType.discount) / (100); - totalPrice = totalPrice - discountAmount; - } - return totalPrice; } @@ -351,6 +333,14 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { durationInDays <= MINT_UPPER_LIMIT; } + /// ----------------------------------------------------------------------- + /// Public + /// ----------------------------------------------------------------------- + + function setBaseURI(string memory _baseTokenURI) public onlyOwner { + baseTokenURI = _baseTokenURI; + } + /// ----------------------------------------------------------------------- /// Getters /// ----------------------------------------------------------------------- diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 99c4c95..02963f7 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -23,11 +23,11 @@ contract BrainPassTest is PRBTest, Cheats { address(mockERC20), "http://example.com/" ); - BrainPass.addPassType(15e18, "Gold", 200, 0); + BrainPass.addPassType(15e18, "Gold", 200); } function testAddPassType() public { - BrainPass.addPassType(15e18, "Gold", 200, 0); + BrainPass.addPassType(15e18, "Gold", 200); string memory _name = BrainPass.getPassType(1).name; assertEq(_name, "Gold"); } @@ -57,7 +57,7 @@ contract BrainPassTest is PRBTest, Cheats { vm.expectRevert( BrainPassCollectibles.InvalidMaxTokensForAPass.selector ); - BrainPass.addPassType(15e18, "OleanjiPass", 0, 0); + BrainPass.addPassType(15e18, "OleanjiPass", 0); } function testPassTypeNotFound() public { @@ -94,8 +94,8 @@ contract BrainPassTest is PRBTest, Cheats { } function testDifferentPassMint() public { - BrainPass.addPassType(15e18, "OleanjiPass", 2, 0); - BrainPass.addPassType(15e18, "KesarPass", 2, 0); + BrainPass.addPassType(15e18, "OleanjiPass", 2); + BrainPass.addPassType(15e18, "KesarPass", 2); mockERC20.mint(alice, 20000e18); mockERC20.mint(doe, 20000e18); @@ -158,13 +158,13 @@ contract BrainPassTest is PRBTest, Cheats { function testGetAllPassType() public { assertEq(BrainPass.getAllPassType().length, 2); - BrainPass.addPassType(400e18, "Platinum", 3000, 10); + BrainPass.addPassType(400e18, "Platinum", 3000); assertEq(BrainPass.getPassType(2).name, "Platinum"); assertEq(BrainPass.getAllPassType().length, 3); } function testMintNftWrong() public { - BrainPass.addPassType(15e18, "OleanjiPass", 2, 0); + BrainPass.addPassType(15e18, "OleanjiPass", 2); mockERC20.mint(alice, 20000e18); mockERC20.mint(bob, 20000e18); mockERC20.mint(doe, 20000e18); diff --git a/test/BrainPassValidator.t.sol b/test/BrainPassValidator.t.sol index 839df32..6a5eaed 100644 --- a/test/BrainPassValidator.t.sol +++ b/test/BrainPassValidator.t.sol @@ -24,7 +24,7 @@ contract BrainPassValidatorTest is PRBTest, Cheats { "http://example.com" ); brainPassValidator = new BrainPassValidiator(address(BrainPass)); - BrainPass.addPassType(15e18, "Gold", 200, 0); + BrainPass.addPassType(15e18, "Gold", 200); } function testPostWikiUserWithNoPass() public { From b7f9781cb565ed2580b16ce8545ec7303567f174 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 14 Jun 2023 16:27:50 +0100 Subject: [PATCH 49/54] reworked the event --- src/BrainPass/BrainPass.sol | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index 5a116eb..ccbef00 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -102,14 +102,6 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { setBaseURI(_baseTokenURI); } - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual override(ERC721, ERC721Pausable) { - super._beforeTokenTransfer(from, to, amount); - } - /// ----------------------------------------------------------------------- /// External functions /// ----------------------------------------------------------------------- @@ -190,7 +182,6 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { if (addressToNFTPass[msg.sender].tokenId != 0) revert AlreadyMintedAPass(); PassType storage passType = passTypes[passId]; - if (passType.isPaused) revert PassTypeIsPaused(); if (passType.lastMintedId >= passType.maxTokens) revert PassMaxSupplyReached(); @@ -198,14 +189,12 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { revert DurationNotInTimeFrame(); uint256 price = calculatePrice(passId, startTimestamp, endTimestamp); - bool success = IERC20(iqToken).transferFrom( msg.sender, address(this), price ); if (!success) revert MintingPaymentFailed(); - uint256 tokenId = tokenIdTracker.current(); passType.lastMintedId += 1; UserPassItem memory purchase = UserPassItem( @@ -215,14 +204,14 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { endTimestamp ); addressToNFTPass[msg.sender] = purchase; - tokenIdTracker.increment(); _safeMint(msg.sender, tokenId); emit BrainPassBought( msg.sender, - tokenId, + price, passId, + tokenId, startTimestamp, endTimestamp ); @@ -248,7 +237,6 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { } else { newStartTime = pass.endTimestamp; } - if (!validatePassDuration(newStartTime, newEndTime)) revert DurationNotInTimeFrame(); @@ -259,7 +247,6 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { price ); if (!success) revert IncreseTimePaymentFailed(); - UserPassItem memory purchase = UserPassItem( pass.tokenId, pass.passId, @@ -268,9 +255,9 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { ); addressToNFTPass[msg.sender] = purchase; - emit TimeIncreased( msg.sender, + price, tokenId, pass.startTimestamp, pass.endTimestamp @@ -311,7 +298,8 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { uint256 endTimestamp ) internal view returns (uint256) { PassType memory passType = passTypes[passId]; - uint256 subscriptionPeriodInDays = (endTimestamp - startTimestamp) /1 days; + uint256 subscriptionPeriodInDays = (endTimestamp - startTimestamp) / + 1 days; // Calculate the total price uint256 totalPrice = subscriptionPeriodInDays * passType.pricePerDay; return totalPrice; @@ -333,6 +321,14 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { durationInDays <= MINT_UPPER_LIMIT; } + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override(ERC721, ERC721Pausable) { + super._beforeTokenTransfer(from, to, amount); + } + /// ----------------------------------------------------------------------- /// Public /// ----------------------------------------------------------------------- @@ -384,6 +380,7 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { event BrainPassBought( address indexed _owner, + uint256 amount, uint256 _passId, uint256 _tokenId, uint256 _startTimestamp, @@ -392,6 +389,7 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { event TimeIncreased( address indexed _owner, + uint256 amount, uint256 _tokenId, uint256 _startTimestamp, uint256 _newEndTimestamp From bb03b8ee1a11e5343c2ffbd7b7826e08407ba53e Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 14 Jun 2023 17:39:26 +0100 Subject: [PATCH 50/54] added data to event --- src/BrainPass/BrainPass.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index ccbef00..af09755 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -209,6 +209,7 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { emit BrainPassBought( msg.sender, + passType.name, price, passId, tokenId, @@ -380,6 +381,7 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { event BrainPassBought( address indexed _owner, + string passType, uint256 amount, uint256 _passId, uint256 _tokenId, From f63d3fad7f4a6cd27ea9833b8a2e9b26cbb9c0a1 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 14 Jun 2023 21:52:32 +0100 Subject: [PATCH 51/54] improved the contract, tests, deployment scripts --- README.md | 40 ++++++++++++++++++---- script/BrainPassDeployer.s.sol | 10 ++---- script/BrainPassValidator.s.sol | 5 +-- src/BrainPass/BrainPass.sol | 14 ++++---- src/BrainPass/BrainPassValidator.sol | 1 - test/BrainPass.t.sol | 51 +++++++++++++++------------- test/BrainPassValidator.t.sol | 4 +-- 7 files changed, 73 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 904f52b..740edc0 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,14 @@ forge script script/BrainPassDeployer.s.sol:BrainPassDeployer --rpc-url $RPC_URL ``` -# Expalaining How BrainPassCollectibles Works +# Expalaining How BrainPassCollectibles Contract Works BrainPassCollectibles is a Solidity smart contract that enables the creation and management of BrainPass NFTs. It allows users to buy passes to have some Wiki privileges on IQ Wiki and provides functionalities to mint NFTs, increase pass time, and manage pass types. ## Usage ### Contract Deployment -Deploy the `BrainPassCollectibles` contract by providing the address of the IQ token contract as a constructor parameter. +Deploy the `BrainPassCollectibles` contract by providing the address of the IQ token and the `baseTokenURI` contract as constructor parameters. ## `pause()` Pauses the contract and prevents any further actions. Can only be called by the contract owner. @@ -78,19 +78,32 @@ Mints an NFT of a particular pass type. - `passId`: ID of the pass type to mint. - `startTimestamp`: Time when the NFT subscription starts. - `endTimestamp`: Time when the NFT subscription ends. -Can call this function only when the contract is not paused +Can call this function only when the contract is not paused, using the `whenNotPaused` modifier ## `increaseEndTime()` Increases the EndTime time of an NFT. - `tokenId`: ID of the NFT whose time is to be increased. - `newEndTime`: New subscription end time for the NFT. -Only when the contrasct is not paused can this function be called. +Only when the contract is not paused can this function be called, using the `whenNotPaused` modifier ## `withdrawEther()` -Withdraws any amount of Ether held in the contract. Can only be called by the contract owner. +Withdraws any amount of Ether held in the contract. +- `receiver`: The address the Ether would be sent to. +- `amount`: The amount to be sent to the address from the contract. +Only the contract owner can call this function. + ## `withdrawIQ()` -Withdraws any amount of IQ tokens held in the contract. Can only be called by the contract owner. +Withdraws any amount of IQ tokens held in the contract. +- `receiver`: The address the Ether would be sent to. +- `amount`: The amount to be sent to the address from the contract. +Only the contract owner can call this function. + +## `setBaseURI` +sets the BaseUri in the contructor, during deployment of the contract. + +## `_baseURI` +Returns the baseUri of the all tokens ## `getUserPassDetails()` Retrieves the details of an NFT owned by a specific user for a given pass type. @@ -108,7 +121,7 @@ Retrieves the details of a specific pass type. The contract emits the following events: - `BrainPassBought`: Emitted when a user buys a BrainPass NFT. -- `TimeIncreased`: Emitted when the time of a BrainPass NFT is increased. +- `PassTimeIncreased`: Emitted when the time of a BrainPass NFT is increased. - `NewPassAdded`: Emitted when a new pass type is added. - `PassTypeStatusToggled`: Emitted when a pass is paused or unpaused. @@ -116,3 +129,16 @@ The contract emits the following events: ![image](https://github.com/EveripediaNetwork/ep-contract/assets/75235148/eee4d631-28d9-4ca4-bc0e-62e5a02998a2) +# Expalaining How BrainPassValidiator Contract Works +BrainPassValidiator is a Solidity smart contract that validates a user to have the ability to post a wiki on IQ Wiki. + + +## Usage + +### Contract Deployment +Deploy the `BrainPassValidiator` contract by providing the address of the BrainPassCollectibles as a constructor parameter. + +## `validate()` +`user`: The user address that is validated +Checks the validity of an address and returns a boolean values based on the result. + diff --git a/script/BrainPassDeployer.s.sol b/script/BrainPassDeployer.s.sol index b42025e..642e069 100644 --- a/script/BrainPassDeployer.s.sol +++ b/script/BrainPassDeployer.s.sol @@ -3,23 +3,17 @@ pragma solidity ^0.8.13; import {Script} from "../lib/forge-std/src/Script.sol"; import {console} from "../lib/forge-std/src/console.sol"; - import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; contract BrainPassDeployer is Script { - address constant owner = - address(0xE161eB85f00eC6471E0de06bA1Cfc136C053fFfe); - function run() external { vm.startBroadcast(); - console.log("Deploying Brainpass deployer...."); BrainPassCollectibles brainPass = new BrainPassCollectibles( 0x5E959c60f86D17fb7D764AB69B654227d464E820, - "http://example.com" + "https://example.com/" ); - console.log("Brainpass Deployed", address(brainPass)); - + console.log("Brainpass Deployed To:", address(brainPass)); vm.stopBroadcast(); } } diff --git a/script/BrainPassValidator.s.sol b/script/BrainPassValidator.s.sol index b559fa5..4447eff 100644 --- a/script/BrainPassValidator.s.sol +++ b/script/BrainPassValidator.s.sol @@ -3,19 +3,16 @@ pragma solidity ^0.8.13; import {Script} from "../lib/forge-std/src/Script.sol"; import {console} from "../lib/forge-std/src/console.sol"; - import {BrainPassValidiator} from "../src/BrainPass/BrainPassValidator.sol"; contract BrainPassValidiatorDeployer is Script { function run() external { vm.startBroadcast(); - console.log("Deploying Brainpass Valdiator...."); BrainPassValidiator brainPassValidator = new BrainPassValidiator( 0x6e213cE219d7ef282ACCC7734040D67875828be4 ); - console.log("Brainpass Deployed", address(brainPassValidator)); - + console.log("Brainpass Deployed To", address(brainPassValidator)); vm.stopBroadcast(); } } diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index af09755..bebcb5d 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -256,9 +256,10 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { ); addressToNFTPass[msg.sender] = purchase; - emit TimeIncreased( + emit PassTimeIncreased( msg.sender, price, + pass.passId, tokenId, pass.startTimestamp, pass.endTimestamp @@ -342,7 +343,7 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { /// Getters /// ----------------------------------------------------------------------- - /// @notice Gets the tokenUri for the contract + /// @notice Gets the baseUri for the contract function _baseURI() internal view virtual override returns (string memory) { return baseTokenURI; } @@ -381,17 +382,18 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { event BrainPassBought( address indexed _owner, - string passType, - uint256 amount, + string _passName, + uint256 _price, uint256 _passId, uint256 _tokenId, uint256 _startTimestamp, uint256 _endTimestamp ); - event TimeIncreased( + event PassTimeIncreased( address indexed _owner, - uint256 amount, + uint256 _price, + uint256 _passId, uint256 _tokenId, uint256 _startTimestamp, uint256 _newEndTimestamp diff --git a/src/BrainPass/BrainPassValidator.sol b/src/BrainPass/BrainPassValidator.sol index bfa33ec..7f3bc80 100644 --- a/src/BrainPass/BrainPassValidator.sol +++ b/src/BrainPass/BrainPassValidator.sol @@ -28,7 +28,6 @@ contract BrainPassValidiator { if (brainPass.getUserPassDetails(user).endTimestamp < block.timestamp) { return false; } - return true; } } diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 02963f7..3a73987 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -14,26 +14,27 @@ contract BrainPassTest is PRBTest, Cheats { address alice = vm.addr(0x2); address bob = vm.addr(0x3); address doe = vm.addr(0x4); - address sam = vm.addr(0x1); MockERC20 mockERC20; function setUp() public { mockERC20 = new MockERC20("Mock IQ Token", "MIT", 18); //mocking IQ token BrainPass = new BrainPassCollectibles( address(mockERC20), - "http://example.com/" + "https://example.com/" ); - BrainPass.addPassType(15e18, "Gold", 200); + BrainPass.addPassType(15e18, "GoldPass", 200); + mockERC20.mint(alice, 20000e18); + mockERC20.mint(bob, 20000e18); + mockERC20.mint(doe, 20000e18); } function testAddPassType() public { - BrainPass.addPassType(15e18, "Gold", 200); + BrainPass.addPassType(15e18, "GoldPass", 200); string memory _name = BrainPass.getPassType(1).name; - assertEq(_name, "Gold"); + assertEq(_name, "GoldPass"); } function testmintNFTWrong() public { - mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); assertEq(BrainPass.balanceOf(alice), 0); @@ -45,7 +46,6 @@ contract BrainPassTest is PRBTest, Cheats { } function testMintDurationNotInTimeFrame() public { - mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); vm.expectRevert(BrainPassCollectibles.DurationNotInTimeFrame.selector); @@ -61,7 +61,6 @@ contract BrainPassTest is PRBTest, Cheats { } function testPassTypeNotFound() public { - mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); vm.expectRevert(BrainPassCollectibles.PassTypeNotFound.selector); @@ -70,7 +69,6 @@ contract BrainPassTest is PRBTest, Cheats { function testCannotMintPausedPass() public { BrainPass.togglePassTypeStatus(1); - mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); vm.expectRevert(BrainPassCollectibles.PassTypeIsPaused.selector); @@ -78,7 +76,6 @@ contract BrainPassTest is PRBTest, Cheats { } function testmintNFT() public { - mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 3e18); vm.expectRevert(stdError.arithmeticError); @@ -96,11 +93,6 @@ contract BrainPassTest is PRBTest, Cheats { function testDifferentPassMint() public { BrainPass.addPassType(15e18, "OleanjiPass", 2); BrainPass.addPassType(15e18, "KesarPass", 2); - mockERC20.mint(alice, 20000e18); - mockERC20.mint(doe, 20000e18); - - mockERC20.mint(bob, 20000e18); - vm.startPrank(alice); mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(1, 172800, 5184000); @@ -128,7 +120,6 @@ contract BrainPassTest is PRBTest, Cheats { } function testIncreaseTimeWrong() public { - mockERC20.mint(alice, 3000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 9000e18); BrainPass.mintNFT(1, 172800, 5184000); @@ -141,7 +132,6 @@ contract BrainPassTest is PRBTest, Cheats { } function testIncreaseTime() public { - mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 12000e18); assertEq(BrainPass.balanceOf(alice), 0); @@ -158,16 +148,13 @@ contract BrainPassTest is PRBTest, Cheats { function testGetAllPassType() public { assertEq(BrainPass.getAllPassType().length, 2); - BrainPass.addPassType(400e18, "Platinum", 3000); + BrainPass.addPassType(400e18, "PlatinumPass", 3000); assertEq(BrainPass.getPassType(2).name, "Platinum"); assertEq(BrainPass.getAllPassType().length, 3); } function testMintNftWrong() public { BrainPass.addPassType(15e18, "OleanjiPass", 2); - mockERC20.mint(alice, 20000e18); - mockERC20.mint(bob, 20000e18); - mockERC20.mint(doe, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 1700e18); assertEq(BrainPass.balanceOf(alice), 0); @@ -200,7 +187,7 @@ contract BrainPassTest is PRBTest, Cheats { function testWithdrawTokens() public { mockERC20.mint(address(BrainPass), 200e18); BrainPass.withdrawIQ(alice, 200e18); - assertEq(mockERC20.balanceOf(address(alice)), 200e18); + assertEq(mockERC20.balanceOf(address(alice)), 20200e18); vm.deal(address(BrainPass), 4 ether); assertEq(address(BrainPass).balance, 4e18); assertEq(address(alice).balance, 0); @@ -223,17 +210,35 @@ contract BrainPassTest is PRBTest, Cheats { BrainPass.mintNFT(1, 172800, 5184000); vm.stopPrank(); BrainPass.unpause(); - mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 19700e18); assertEq(BrainPass.balanceOf(alice), 0); BrainPass.mintNFT(1, 172800, 5184000); assertEq(BrainPass.balanceOf(alice), 1); vm.stopPrank(); + } + + function testTokenTranferWrong() public { + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 19700e18); + assertEq(BrainPass.balanceOf(alice), 0); + BrainPass.mintNFT(1, 172800, 5184000); + vm.stopPrank(); BrainPass.pause(); vm.startPrank(alice); vm.expectRevert("ERC721Pausable: token transfer while paused"); BrainPass.safeTransferFrom(alice, bob, 1); + assertEq(BrainPass.balanceOf(alice), 1); assertEq(BrainPass.balanceOf(bob), 0); } + + function testTokenTranfer() public { + vm.startPrank(alice); + mockERC20.approve(address(BrainPass), 19700e18); + assertEq(BrainPass.balanceOf(alice), 0); + BrainPass.mintNFT(1, 172800, 5184000); + BrainPass.safeTransferFrom(alice, bob, 1); + assertEq(BrainPass.balanceOf(alice), 0); + assertEq(BrainPass.balanceOf(bob), 1); + } } diff --git a/test/BrainPassValidator.t.sol b/test/BrainPassValidator.t.sol index 6a5eaed..67fde91 100644 --- a/test/BrainPassValidator.t.sol +++ b/test/BrainPassValidator.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.13; import {PRBTest} from "prb-test/PRBTest.sol"; import {Cheats} from "forge-std/Cheats.sol"; -import "forge-std/console.sol"; import {stdError} from "forge-std/Errors.sol"; import {BrainPassCollectibles} from "../src/BrainPass/BrainPass.sol"; import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol"; @@ -25,6 +24,7 @@ contract BrainPassValidatorTest is PRBTest, Cheats { ); brainPassValidator = new BrainPassValidiator(address(BrainPass)); BrainPass.addPassType(15e18, "Gold", 200); + mockERC20.mint(alice, 20000e18); } function testPostWikiUserWithNoPass() public { @@ -32,7 +32,6 @@ contract BrainPassValidatorTest is PRBTest, Cheats { } function testPostWikiRight() public { - mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 1700e18); BrainPass.mintNFT(1, 1685638993, 1693587793); // june 1st - sept 1st (3 months) @@ -41,7 +40,6 @@ contract BrainPassValidatorTest is PRBTest, Cheats { } function testPostWikiPassExpired() public { - mockERC20.mint(alice, 20000e18); vm.startPrank(alice); mockERC20.approve(address(BrainPass), 1700e18); BrainPass.mintNFT(1, 1685638993, 1693587793); From 7095eb3c06a138670de6ff6c76f100dcd9fe55e2 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Wed, 14 Jun 2023 21:57:31 +0100 Subject: [PATCH 52/54] fixed failed assertion --- test/BrainPass.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 3a73987..2e7b2a7 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -97,21 +97,21 @@ contract BrainPassTest is PRBTest, Cheats { mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(1, 172800, 5184000); uint256 firstId = BrainPass.getUserPassDetails(alice).tokenId; - assertEq(BrainPass.tokenURI(firstId), "http://example.com/1"); + assertEq(BrainPass.tokenURI(firstId), "https://example.com/1"); console.log(BrainPass.tokenURI(firstId), firstId); vm.stopPrank(); vm.startPrank(doe); mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(2, 172800, 5184000); uint256 newId = BrainPass.getUserPassDetails(doe).tokenId; - assertEq(BrainPass.tokenURI(newId), "http://example.com/2"); + assertEq(BrainPass.tokenURI(newId), "https://example.com/2"); console.log(BrainPass.tokenURI(newId), newId); vm.stopPrank(); vm.startPrank(bob); mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(3, 172800, 5184000); uint256 newIds = BrainPass.getUserPassDetails(bob).tokenId; - assertEq(BrainPass.tokenURI(newIds), "http://example.com/3"); + assertEq(BrainPass.tokenURI(newIds), "https://example.com/3"); console.log(BrainPass.tokenURI(newIds), newIds); uint256 mintedPass = BrainPass.getUserPassDetails(doe).tokenId; console.log(BrainPass.balanceOf(alice)); @@ -149,7 +149,7 @@ contract BrainPassTest is PRBTest, Cheats { function testGetAllPassType() public { assertEq(BrainPass.getAllPassType().length, 2); BrainPass.addPassType(400e18, "PlatinumPass", 3000); - assertEq(BrainPass.getPassType(2).name, "Platinum"); + assertEq(BrainPass.getPassType(2).name, "PlatinumPass"); assertEq(BrainPass.getAllPassType().length, 3); } From 903fdfc0a00ff2a83d3ec48da44549c454affe60 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Thu, 15 Jun 2023 16:26:27 +0100 Subject: [PATCH 53/54] deployer script --- script/BrainPassDeployer.s.sol | 2 +- src/BrainPass/BrainPass.sol | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/script/BrainPassDeployer.s.sol b/script/BrainPassDeployer.s.sol index 642e069..3c86b78 100644 --- a/script/BrainPassDeployer.s.sol +++ b/script/BrainPassDeployer.s.sol @@ -11,7 +11,7 @@ contract BrainPassDeployer is Script { console.log("Deploying Brainpass deployer...."); BrainPassCollectibles brainPass = new BrainPassCollectibles( 0x5E959c60f86D17fb7D764AB69B654227d464E820, - "https://example.com/" + "https://api.dev.braindao.org/brainpass/" ); console.log("Brainpass Deployed To:", address(brainPass)); vm.stopBroadcast(); diff --git a/src/BrainPass/BrainPass.sol b/src/BrainPass/BrainPass.sol index bebcb5d..7af8255 100644 --- a/src/BrainPass/BrainPass.sol +++ b/src/BrainPass/BrainPass.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.13; import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; +import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; import {ERC721Pausable} from "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Pausable.sol"; import {Counters} from "openzeppelin-contracts/contracts/utils/Counters.sol"; import "openzeppelin-contracts/contracts/access/Ownable.sol"; @@ -88,6 +89,8 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { uint256 public MINT_UPPER_LIMIT = 365; string public baseTokenURI; + using Strings for uint256; + /// ----------------------------------------------------------------------- /// Constructor /// ----------------------------------------------------------------------- @@ -331,6 +334,19 @@ contract BrainPassCollectibles is ERC721, ERC721Pausable, Ownable { super._beforeTokenTransfer(from, to, amount); } + function tokenURI( + uint256 tokenId + ) public view virtual override returns (string memory) { + require(_exists(tokenId), "Not Exist"); + + string memory baseURI = _baseURI(); + + return + bytes(baseURI).length > 0 + ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json")) + : ""; + } + /// ----------------------------------------------------------------------- /// Public /// ----------------------------------------------------------------------- From b2283b28709d13b91c5d38412f68369e55545c41 Mon Sep 17 00:00:00 2001 From: OleanjiKingCode Date: Thu, 15 Jun 2023 17:35:49 +0100 Subject: [PATCH 54/54] updated the tests --- test/BrainPass.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/BrainPass.t.sol b/test/BrainPass.t.sol index 2e7b2a7..217f314 100644 --- a/test/BrainPass.t.sol +++ b/test/BrainPass.t.sol @@ -97,21 +97,21 @@ contract BrainPassTest is PRBTest, Cheats { mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(1, 172800, 5184000); uint256 firstId = BrainPass.getUserPassDetails(alice).tokenId; - assertEq(BrainPass.tokenURI(firstId), "https://example.com/1"); + assertEq(BrainPass.tokenURI(firstId), "https://example.com/1.json"); console.log(BrainPass.tokenURI(firstId), firstId); vm.stopPrank(); vm.startPrank(doe); mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(2, 172800, 5184000); uint256 newId = BrainPass.getUserPassDetails(doe).tokenId; - assertEq(BrainPass.tokenURI(newId), "https://example.com/2"); + assertEq(BrainPass.tokenURI(newId), "https://example.com/2.json"); console.log(BrainPass.tokenURI(newId), newId); vm.stopPrank(); vm.startPrank(bob); mockERC20.approve(address(BrainPass), 20000e18); BrainPass.mintNFT(3, 172800, 5184000); uint256 newIds = BrainPass.getUserPassDetails(bob).tokenId; - assertEq(BrainPass.tokenURI(newIds), "https://example.com/3"); + assertEq(BrainPass.tokenURI(newIds), "https://example.com/3.json"); console.log(BrainPass.tokenURI(newIds), newIds); uint256 mintedPass = BrainPass.getUserPassDetails(doe).tokenId; console.log(BrainPass.balanceOf(alice));