// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract DandiToken is ERC20 { uint256 public constant INITIAL_SUPPLY = 1000000 * 10**18; // Initial supply of 1 million tokens with 18 decimals uint256 public releaseAmount = 1000 * 10**18; // Release rate of 1000 tokens per release period uint256 public constant START_TIME = 1718078400; // Start time for the token release mechanism uint256 public lastReleaseTime; // Timestamp of the last token release uint256 public releaseInterval = 23 hours + 58 minutes; // Release interval of 1 day uint256 public constant TARA_MIN = 100 ether; // TARA holdings needed to submit a vote uint256 public accumulatedTokens; // Accumulated tokens not yet released uint256 public totalReleasedTokens; // Total tokens released so far // Make secretHash private to prevent it from being accessed publicly bytes32 private secretHash; // Hash of the secret used to verify votes event TokensReleased(uint256 amount); event VoteSubmitted(address voter, address candidate, bytes32 memeID); event TokensDistributed(uint256 amountToWinner, uint256 amountToVoters); event MemeIDApproved(bytes32 memeID); event MemeIDRemoved(bytes32 memeID); struct Vote { address voter; address candidate; bytes32 memeID; } mapping(bytes32 => uint256) public memeVoteCount; mapping(address => bool) public hasVoted; mapping(bytes32 => address) public memeToCandidate; mapping(bytes32 => bool) public approvedMemeIDs; // Mapping to check if a memeID is approved bytes32[] public uniqueMemeIDs; Vote[] public votes; // Constructor to initialize the contract constructor(bytes32 _secretHash) ERC20("DandiToken", "DANDI") { _mint(address(this), INITIAL_SUPPLY); // Mint the initial supply of tokens to the contract itself lastReleaseTime = START_TIME; // Set the initial last release time totalReleasedTokens = 0; // Initialize total released tokens secretHash = _secretHash; // Store the secret hash } // Function to approve multiple memeIDs function approveMemeIDs(bytes32[] memory memeIDs, bytes32 secret) public { require(secret == secretHash, "Invalid secret"); for (uint256 i = 0; i < memeIDs.length; i++) { approvedMemeIDs[memeIDs[i]] = true; // Mark the memeID as approved emit MemeIDApproved(memeIDs[i]); // Emit a memeID approved event } } // Function to remove multiple approved memeIDs function removeMemeIDs(bytes32[] memory memeIDs, bytes32 secret) public { require(secret == secretHash, "Invalid secret"); for (uint256 i = 0; i < memeIDs.length; i++) { require(approvedMemeIDs[memeIDs[i]], "MemeID not approved"); delete approvedMemeIDs[memeIDs[i]]; // Remove the memeID from the approved list emit MemeIDRemoved(memeIDs[i]); // Emit a memeID removed event } } // Function to remove all approved memeIDs function removeAllMemeIDs(bytes32 secret) public { require(secret == secretHash, "Invalid secret"); // Create an array to store the meme IDs to remove bytes32[] memory memeIDsToRemove = new bytes32[](uniqueMemeIDs.length); uint256 count = 0; // Iterate over uniqueMemeIDs to find approved meme IDs for (uint256 i = 0; i < uniqueMemeIDs.length; i++) { bytes32 memeID = uniqueMemeIDs[i]; if (approvedMemeIDs[memeID]) { memeIDsToRemove[count] = memeID; count++; } } // Remove the approved meme IDs for (uint256 i = 0; i < count; i++) { delete approvedMemeIDs[memeIDsToRemove[i]]; // Remove the memeID from the approved list emit MemeIDRemoved(memeIDsToRemove[i]); // Emit a memeID removed event } } // Function to submit a vote function submitVote(address candidate, bytes32 memeID) public { uint256 requiredBalance = TARA_MIN; // Required TARA balance to submit a vote require(msg.sender.balance >= requiredBalance, "Insufficient TARA balance to vote"); require(!hasVoted[msg.sender], "You have already voted"); require(approvedMemeIDs[memeID], "MemeID not approved for voting"); votes.push(Vote({voter: msg.sender, candidate: candidate, memeID: memeID})); // Store the vote hasVoted[msg.sender] = true; // Mark the voter as having voted memeVoteCount[memeID] += 1; // Increment the vote count for the meme if (memeToCandidate[memeID] == address(0)) { memeToCandidate[memeID] = candidate; // Associate the meme ID with the candidate uniqueMemeIDs.push(memeID); // Add the meme ID to the list of unique meme IDs } emit VoteSubmitted(msg.sender, candidate, memeID); // Emit a vote submitted event } // Function to release tokens based on voting results, restricted to the owner function releaseTokens() public { uint256 currentTime = block.timestamp; uint256 elapsedTime = currentTime - lastReleaseTime; //Restrict to once a day but allow a 2 minute grace period require(elapsedTime >= releaseInterval, "Can only release tokens once a day"); bytes32 winningMemeID = determineWinningMemeID(); // Determine the winning meme ID address winner = memeToCandidate[winningMemeID]; // Get the candidate associated with the winning meme uint256 tokensToRelease = releaseAmount + accumulatedTokens; // Include accumulated tokens if (votes.length == 0 || winner == address(0)) { accumulatedTokens += releaseAmount; // Accumulate tokens if no votes or no valid winner } else { uint256 amountToWinner = tokensToRelease / 2; uint256 amountToVoters = tokensToRelease - amountToWinner; super._transfer(address(this), winner, amountToWinner); // Transfer tokens to the winner distributeTokensToVoters(amountToVoters); // Distribute tokens to the voters accumulatedTokens = 0; // Reset accumulated tokens totalReleasedTokens += tokensToRelease; // Update total released tokens emit TokensReleased(tokensToRelease); // Emit tokens released event emit TokensDistributed(amountToWinner, amountToVoters); // Emit tokens distributed event // Remove the winning memeID from the approved list delete approvedMemeIDs[winningMemeID]; emit MemeIDRemoved(winningMemeID); } lastReleaseTime = currentTime; // Update the last release time resetVotes(); // Reset the voting state } // Function to determine the winning meme ID based on votes function determineWinningMemeID() internal view returns (bytes32) { bytes32 winningMemeID; uint256 highestVoteCount = 0; bool tie = false; for (uint256 i = 0; i < uniqueMemeIDs.length; i++) { bytes32 memeID = uniqueMemeIDs[i]; uint256 memeVotes = memeVoteCount[memeID]; if (memeVotes > highestVoteCount) { highestVoteCount = memeVotes; winningMemeID = memeID; tie = false; } else if (memeVotes == highestVoteCount) { tie = true; } } return tie ? bytes32(0) : winningMemeID; // Return 0 if there's a tie, otherwise return the winning meme ID } // Function to distribute tokens to voters function distributeTokensToVoters(uint256 amount) internal { uint256 voterCount = votes.length; uint256 amountPerVoter = amount / voterCount; for (uint256 i = 0; i < voterCount; i++) { super._transfer(address(this), votes[i].voter, amountPerVoter); // Transfer tokens to each voter } } // Function to reset the voting state function resetVotes() internal { // Reset hasVoted mapping for all voters for (uint256 i = 0; i < votes.length; i++) { hasVoted[votes[i].voter] = false; } // Clear the votes array delete votes; // Clear the uniqueMemeIDs array and memeVoteCount mapping for (uint256 i = 0; i < uniqueMemeIDs.length; i++) { delete memeVoteCount[uniqueMemeIDs[i]]; delete memeToCandidate[uniqueMemeIDs[i]]; } delete uniqueMemeIDs; } // Function to get the current votes function getVotes() public view returns (Vote[] memory) { return votes; // Return the array of votes } // Function to get the total tokens left in the contract function totalTokensLeft() public view returns (uint256) { return INITIAL_SUPPLY - totalReleasedTokens; // Return the remaining tokens } // Override transfer function to disable direct transfers from the contract function transfer(address recipient, uint256 amount) public override returns (bool) { require(msg.sender != address(this), "Direct transfers from the contract are disabled"); return super.transfer(recipient, amount); } // Override transferFrom function to disable direct transfers from the contract function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { require(sender != address(this), "Direct transfers from the contract are disabled"); return super.transferFrom(sender, recipient, amount); } }