Created
November 5, 2021 08:09
-
-
Save z0r0z/af939f3b89fd26b9a7e65cb71b3b5a68 to your computer and use it in GitHub Desktop.
minimalist voting module that plugs into voting token
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: GPL-3.0-or-later | |
pragma solidity >=0.8.0; | |
/// @notice Vote Token interface. | |
interface IVoteToken { | |
function balanceOf(address account) external view returns (uint256); | |
function getPriorVotes(address account, uint256 timestamp) external view returns (uint256); | |
function mint(address to, uint256 amount) external; | |
function burn(address from, uint256 amount) external; | |
} | |
/// @notice Minimalist DAO core module. | |
contract LiteDAO { | |
event NewProposal(uint256 proposalId); | |
event ProposalProcessed(uint256 proposalId); | |
uint256 public proposalCount; | |
uint256 public votingPeriod; | |
IVoteToken public voteToken; | |
mapping(uint256 => Proposal) public proposals; | |
enum ProposalType { | |
MINT, | |
BURN, | |
SPEND, | |
CALL | |
} | |
struct Proposal { | |
ProposalType proposalType; | |
string description; | |
address account; // member being added/kicked; account to send money; or account receiving loot | |
address asset; // asset considered for payment | |
uint256 amount; // value to be minted/burned/spent | |
bytes payload; // data for CALL proposals | |
uint256 yesVotes; | |
uint256 noVotes; | |
uint256 creationTime; | |
uint256 deadline; | |
} | |
/*/////////////////////////////////////////////////////////////// | |
CONSTRUCTOR | |
//////////////////////////////////////////////////////////////*/ | |
constructor(IVoteToken voteToken_, uint256 votingPeriod_) { | |
voteToken = voteToken_; | |
votingPeriod = votingPeriod_; | |
} | |
/*/////////////////////////////////////////////////////////////// | |
PROPOSAL LOGIC | |
//////////////////////////////////////////////////////////////*/ | |
modifier onlyTokenHolders() { | |
require(voteToken.balanceOf(msg.sender) > 0, "NOT_TOKEN_HOLDER"); | |
_; | |
} | |
function propose(ProposalType proposalType, string memory description, address account, address asset, uint256 amount, bytes memory payload) external onlyTokenHolders { | |
Proposal memory proposal = Proposal({ | |
proposalType: proposalType, | |
description: description, | |
account: account, | |
asset: asset, | |
amount: amount, | |
payload: payload, | |
yesVotes: 0, | |
noVotes: 0, | |
creationTime: block.timestamp, | |
deadline: block.timestamp + (votingPeriod * 1 days) | |
}); | |
proposals[proposalCount] = proposal; | |
emit NewProposal(proposalCount); | |
proposalCount++; | |
} | |
function vote(uint256 proposal, bool approve) external onlyTokenHolders { | |
Proposal storage prop = proposals[proposal]; | |
require(prop.deadline <= block.timestamp, "VOTING_ENDED"); | |
uint256 weight = voteToken.getPriorVotes(msg.sender, prop.creationTime); | |
if (approve) { | |
prop.yesVotes += weight; | |
} else { | |
prop.noVotes += weight; | |
} | |
} | |
function processProposal(uint256 proposal) external onlyTokenHolders { | |
Proposal storage prop = proposals[proposal]; | |
require(block.timestamp > prop.deadline, "VOTING_NOT_ENDED"); | |
require(prop.deadline != 0, "VOTE_EXECUTED"); | |
address account = prop.account; | |
if (prop.proposalType == ProposalType.MINT) { | |
voteToken.mint(prop.account, prop.amount); | |
} | |
if (prop.proposalType == ProposalType.BURN) { | |
voteToken.burn(prop.account, prop.amount); | |
} | |
if (prop.proposalType == ProposalType.SPEND) { | |
safeTransfer(prop.asset, prop.account, prop.amount); | |
} | |
if (prop.proposalType == ProposalType.CALL) { | |
account.call{value: prop.amount}(prop.payload); | |
} | |
delete proposals[proposal]; | |
emit ProposalProcessed(proposal); | |
} | |
/*/////////////////////////////////////////////////////////////// | |
INTERNAL HELPER LOGIC | |
//////////////////////////////////////////////////////////////*/ | |
function safeTransfer( | |
address token, | |
address to, | |
uint256 amount | |
) internal { | |
bool callStatus; | |
assembly { | |
// We'll use 4 + 32 * 2 bytes. | |
let callDataLength := 68 | |
// Get a pointer to some free memory. | |
let freeMemoryPointer := mload(0x40) | |
// Update the free memory pointer for safety. | |
mstore(0x40, add(freeMemoryPointer, callDataLength)) | |
// Write the abi-encoded calldata to memory piece by piece: | |
mstore(freeMemoryPointer, shl(224, 0xa9059cbb)) // Properly shift and append the function selector for approve(address,uint256) | |
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument. | |
mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value. | |
// Call the token and store if it succeeded or not. | |
callStatus := call(gas(), token, 0, freeMemoryPointer, callDataLength, 0, 0) | |
} | |
require(didLastOptionalReturnCallSucceed(callStatus), "TRANSFER_FAILED"); | |
} | |
function didLastOptionalReturnCallSucceed(bool callStatus) private pure returns (bool success) { | |
assembly { | |
// Get how many bytes the call returned. | |
let returnDataSize := returndatasize() | |
// If the call reverted: | |
if iszero(callStatus) { | |
// Copy the revert message into memory. | |
returndatacopy(0, 0, returnDataSize) | |
// Revert with the same message. | |
revert(0, returnDataSize) | |
} | |
switch returnDataSize | |
case 32 { | |
// Copy the return data into memory. | |
returndatacopy(0, 0, returnDataSize) | |
// Set success to whether it returned true. | |
success := iszero(iszero(mload(0))) | |
} | |
case 0 { | |
// There was no return data. | |
success := 1 | |
} | |
default { | |
// It returned some malformed input. | |
success := 0 | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment