diff --git a/hardhat-tutorial/contracts/Greeter.sol b/hardhat-tutorial/contracts/Greeter.sol deleted file mode 100644 index efffb8f..0000000 --- a/hardhat-tutorial/contracts/Greeter.sol +++ /dev/null @@ -1,22 +0,0 @@ -//SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.0; - -import "hardhat/console.sol"; - -contract Greeter { - string private greeting; - - constructor(string memory _greeting) { - console.log("Deploying a Greeter with greeting:", _greeting); - greeting = _greeting; - } - - function greet() public view returns (string memory) { - return greeting; - } - - function setGreeting(string memory _greeting) public { - console.log("Changing greeting from '%s' to '%s'", greeting, _greeting); - greeting = _greeting; - } -} diff --git a/hardhat-tutorial/contracts/tests/CryptoDevToken.sol b/hardhat-tutorial/contracts/tests/CryptoDevToken.sol new file mode 100644 index 0000000..5898263 --- /dev/null +++ b/hardhat-tutorial/contracts/tests/CryptoDevToken.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "./ICryptoDevs.sol"; + +contract CryptoDevToken is ERC20, Ownable { + // Price of one Crypto Dev token + uint256 public constant tokenPrice = 0.001 ether; + // Each NFT would give the user 10 tokens + // It needs to be represented as 10 * (10 ** 18) as ERC20 tokens are represented by the smallest denomination possible for the token + // By default, ERC20 tokens have the smallest denomination of 10^(-18). This means, having a balance of (1) + // is actually equal to (10 ^ -18) tokens. + // Owning 1 full token is equivalent to owning (10^18) tokens when you account for the decimal places. + // More information on this can be found in the Freshman Track Cryptocurrency tutorial. + uint256 public constant tokensPerNFT = 10 * 10**18; + // the max total supply is 10000 for Crypto Dev Tokens + uint256 public constant maxTotalSupply = 10000 * 10**18; + // CryptoDevsNFT contract instance + ICryptoDevs CryptoDevsNFT; + // Mapping to keep track of which tokenIds have been claimed + mapping(uint256 => bool) public tokenIdsClaimed; + + constructor(address _cryptoDevsContract) ERC20("Crypto Dev Token", "CD") { + CryptoDevsNFT = ICryptoDevs(_cryptoDevsContract); + } + + /** + * @dev Mints `amount` number of CryptoDevTokens + * Requirements: + * - `msg.value` should be equal or greater than the tokenPrice * amount + */ + function mint(uint256 amount) public payable { + // the value of ether that should be equal or greater than tokenPrice * amount; + uint256 _requiredAmount = tokenPrice * amount; + require(msg.value >= _requiredAmount, "Ether sent is incorrect"); + // total tokens + amount <= 10000, otherwise revert the transaction + uint256 amountWithDecimals = amount * 10**18; + require( + (totalSupply() + amountWithDecimals) <= maxTotalSupply, + "Exceeds the max total supply available." + ); + // call the internal function from Openzeppelin's ERC20 contract + _mint(msg.sender, amountWithDecimals); + } + + /** + * @dev Mints tokens based on the number of NFT's held by the sender + * Requirements: + * balance of Crypto Dev NFT's owned by the sender should be greater than 0 + * Tokens should have not been claimed for all the NFTs owned by the sender + */ + function claim() public { + address sender = msg.sender; + // Get the number of CryptoDev NFT's held by a given sender address + uint256 balance = CryptoDevsNFT.balanceOf(sender); + // If the balance is zero, revert the transaction + require(balance > 0, "You dont own any Crypto Dev NFT's"); + // amount keeps track of number of unclaimed tokenIds + uint256 amount = 0; + // loop over the balance and get the token ID owned by `sender` at a given `index` of its token list. + for (uint256 i = 0; i < balance; i++) { + uint256 tokenId = CryptoDevsNFT.tokenOfOwnerByIndex(sender, i); + // if the tokenId has not been claimed, increase the amount + if (!tokenIdsClaimed[tokenId]) { + amount += 1; + tokenIdsClaimed[tokenId] = true; + } + } + // If all the token Ids have been claimed, revert the transaction; + require(amount > 0, "You have already claimed all the tokens"); + // call the internal function from Openzeppelin's ERC20 contract + // Mint (amount * 10) tokens for each NFT + _mint(msg.sender, amount * tokensPerNFT); + } + + // Enables withdrawal of ether sent to contract for ICO + function withdraw() public onlyOwner { + address _owner = owner(); + uint256 amount = address(this).balance; + (bool sent, ) = _owner.call{value: amount}(""); + require(sent, "Failed to send Ether"); + } + + // Function to receive Ether. msg.data must be empty + receive() external payable {} + + // Fallback function is called when msg.data is not empty + fallback() external payable {} +} diff --git a/hardhat-tutorial/contracts/tests/CryptoDevs.sol b/hardhat-tutorial/contracts/tests/CryptoDevs.sol new file mode 100644 index 0000000..b894bbd --- /dev/null +++ b/hardhat-tutorial/contracts/tests/CryptoDevs.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "./IWhitelist.sol"; + +contract CryptoDevs is ERC721Enumerable, Ownable { + /** + * @dev _baseTokenURI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. + */ + string _baseTokenURI; + + // _price is the price of one Crypto Dev NFT + uint256 public _price = 0.01 ether; + + // _paused is used to pause the contract in case of an emergency + bool public _paused; + + // max number of CryptoDevs + uint256 public maxTokenIds = 10; // changed to 10 just for test sake, it should be on the constructor + + // total number of tokenIds minted + uint256 public tokenIds; + + // Whitelist contract instance + IWhitelist whitelist; + + // boolean to keep track of when presale started + bool public presaleStarted; + + // timestamp for even presale would end + uint256 public presaleEnded; + + modifier onlyWhenNotPaused { + require(!_paused, "Contract currently paused"); + _; + } + + /** + * @dev ERC721 constructor takes in a `name` and a `symbol` to the token collection. + * name in our case is `Crypto Devs` and symbol is `CD`. + * Constructor for Crypto Devs takes in the baseURI to set _baseTokenURI for the collection. + * It also initializes an instance of whitelist interface. + */ + constructor (string memory baseURI, address whitelistContract) ERC721("Crypto Devs", "CD") { + _baseTokenURI = baseURI; + whitelist = IWhitelist(whitelistContract); + } + + /** + * @dev startPresale starts a presale for the whitelisted addresses + */ + function startPresale() public onlyOwner { + presaleStarted = true; + // Set presaleEnded time as current timestamp + 5 minutes + // Solidity has cool syntax for timestamps (seconds, minutes, hours, days, years) + presaleEnded = block.timestamp + 5 minutes; + } + + /** + * @dev presaleMint allows an user to mint one NFT per transaction during the presale. + */ + function presaleMint() public payable onlyWhenNotPaused { + require(presaleStarted && block.timestamp < presaleEnded, "Presale is not running"); + require(whitelist.whitelistedAddresses(msg.sender), "You are not whitelisted"); + require(tokenIds < maxTokenIds, "Exceeded maximum Cypto Devs supply"); + require(msg.value >= _price, "Ether sent is not correct"); + tokenIds += 1; + //_safeMint is a safer version of the _mint function as it ensures that + // if the address being minted to is a contract, then it knows how to deal with ERC721 tokens + // If the address being minted to is not a contract, it works the same way as _mint + _safeMint(msg.sender, tokenIds); + } + + /** + * @dev mint allows an user to mint 1 NFT per transaction after the presale has ended. + */ + function mint() public payable onlyWhenNotPaused { + require(presaleStarted && block.timestamp >= presaleEnded, "Presale has not ended yet"); + require(tokenIds < maxTokenIds, "Exceed maximum Cypto Devs supply"); + require(msg.value >= _price, "Ether sent is not correct"); + tokenIds += 1; + _safeMint(msg.sender, tokenIds); + } + + /** + * @dev _baseURI overides the Openzeppelin's ERC721 implementation which by default + * returned an empty string for the baseURI + */ + function _baseURI() internal view virtual override returns (string memory) { + return _baseTokenURI; + } + + /** + * @dev setPaused makes the contract paused or unpaused + */ + function setPaused(bool val) public onlyOwner { + _paused = val; + } + + /** + * @dev withdraw sends all the ether in the contract + * to the owner of the contract + */ + function withdraw() public onlyOwner { + address _owner = owner(); + uint256 amount = address(this).balance; + (bool sent, ) = _owner.call{value: amount}(""); + require(sent, "Failed to send Ether"); + } + + // Function to receive Ether. msg.data must be empty + receive() external payable {} + + // Fallback function is called when msg.data is not empty + fallback() external payable {} +} \ No newline at end of file diff --git a/hardhat-tutorial/contracts/tests/ICryptoDevs.sol b/hardhat-tutorial/contracts/tests/ICryptoDevs.sol new file mode 100644 index 0000000..f9e52ea --- /dev/null +++ b/hardhat-tutorial/contracts/tests/ICryptoDevs.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +interface ICryptoDevs { + /** + * @dev Returns a token ID owned by `owner` at a given `index` of its token list. + * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) + external + view + returns (uint256 tokenId); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); +} diff --git a/hardhat-tutorial/contracts/tests/IWhitelist.sol b/hardhat-tutorial/contracts/tests/IWhitelist.sol new file mode 100644 index 0000000..16db8cb --- /dev/null +++ b/hardhat-tutorial/contracts/tests/IWhitelist.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +interface IWhitelist { + function whitelistedAddresses(address) external view returns (bool); +} \ No newline at end of file diff --git a/hardhat-tutorial/contracts/tests/Whitelist.sol b/hardhat-tutorial/contracts/tests/Whitelist.sol new file mode 100644 index 0000000..9516a13 --- /dev/null +++ b/hardhat-tutorial/contracts/tests/Whitelist.sol @@ -0,0 +1,36 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.4; + + +contract Whitelist { + + // Max number of whitelisted addresses allowed + uint8 public maxWhitelistedAddresses; + + // Create a mapping of whitelistedAddresses + // if an address is whitelisted, we would set it to true, it is false my default for all other addresses. + mapping(address => bool) public whitelistedAddresses; + + // numAddressesWhitelisted would be used to keep track of how many addresses have been whitelisted + uint8 public numAddressesWhitelisted; + + constructor(uint8 _maxWhitelistedAddresses) { + maxWhitelistedAddresses = _maxWhitelistedAddresses; + } + + /** + addAddressToWhitelist - This function adds the address of the sender to the + whitelist + */ + function addAddressToWhitelist() public { + // check if the user has already been whitelisted + require(!whitelistedAddresses[msg.sender], "Sender has already been whitelisted"); + // check if the numAddressesWhitelisted < maxWhitelistedAddresses, if not then throw an error. + require(numAddressesWhitelisted < maxWhitelistedAddresses, "More addresses cant be added, limit reached"); + // Add the address which called the function to the whitelistedAddress array + whitelistedAddresses[msg.sender] = true; + // Increase the number of whitelisted addresses + numAddressesWhitelisted += 1; + } + +} \ No newline at end of file diff --git a/hardhat-tutorial/hardhat.config.js b/hardhat-tutorial/hardhat.config.js index 36b53e5..ff68278 100644 --- a/hardhat-tutorial/hardhat.config.js +++ b/hardhat-tutorial/hardhat.config.js @@ -1,4 +1,4 @@ -require("@nomiclabs/hardhat-waffle"); +require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config({ path: ".env" }); const ALCHEMY_API_KEY_URL = process.env.ALCHEMY_API_KEY_URL; @@ -6,7 +6,7 @@ const ALCHEMY_API_KEY_URL = process.env.ALCHEMY_API_KEY_URL; const RINKEBY_PRIVATE_KEY = process.env.RINKEBY_PRIVATE_KEY; module.exports = { - solidity: "0.8.4", + solidity: "0.8.10", networks: { rinkeby: { url: ALCHEMY_API_KEY_URL, diff --git a/hardhat-tutorial/package.json b/hardhat-tutorial/package.json index c0fe071..fcdab53 100644 --- a/hardhat-tutorial/package.json +++ b/hardhat-tutorial/package.json @@ -10,12 +10,21 @@ "author": "", "license": "ISC", "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.4", - "@nomiclabs/hardhat-waffle": "^2.0.1", - "chai": "^4.3.4", - "ethereum-waffle": "^3.4.0", - "ethers": "^5.5.3", - "hardhat": "^2.8.2" + "@ethersproject/abi": "^5.7.0", + "@ethersproject/providers": "^5.7.1", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", + "@nomicfoundation/hardhat-network-helpers": "^1.0.6", + "@nomicfoundation/hardhat-toolbox": "^2.0.0", + "@nomiclabs/hardhat-ethers": "^2.1.1", + "@nomiclabs/hardhat-etherscan": "^3.1.0", + "@typechain/ethers-v5": "^10.1.0", + "@typechain/hardhat": "^6.1.3", + "chai": "^4.3.6", + "ethers": "^5.7.1", + "hardhat": "^2.11.2", + "hardhat-gas-reporter": "^1.0.9", + "solidity-coverage": "^0.8.2", + "typechain": "^8.1.0" }, "dependencies": { "@openzeppelin/contracts": "^4.4.1", diff --git a/hardhat-tutorial/scripts/sample-script.js b/hardhat-tutorial/scripts/sample-script.js deleted file mode 100644 index 90cd819..0000000 --- a/hardhat-tutorial/scripts/sample-script.js +++ /dev/null @@ -1,32 +0,0 @@ -// We require the Hardhat Runtime Environment explicitly here. This is optional -// but useful for running the script in a standalone fashion through `node