Skip to content

Instantly share code, notes, and snippets.

@z0r0z
Created January 9, 2022 19:43
Show Gist options
  • Select an option

  • Save z0r0z/f981a3ec956458445b26acc13e840715 to your computer and use it in GitHub Desktop.

Select an option

Save z0r0z/f981a3ec956458445b26acc13e840715 to your computer and use it in GitHub Desktop.
programmatically enforce membership doesn't exceed 100 investment club limit
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.4;
/// @notice Modern and gas-optimized ERC-20 + EIP-2612 implementation with COMP-style governance and pausing.
/// @author Modified from RariCapital (https://github.com/Rari-Capital/solmate/blob/main/src/erc20/ERC20.sol)
/// License-Identifier: AGPL-3.0-only
abstract contract KaliDAOtoken {
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
event PauseFlipped(bool paused);
/*///////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error NoArrayParity();
error Paused();
error SignatureExpired();
error NullAddress();
error InvalidNonce();
error NotDetermined();
error InvalidSignature();
error Uint32max();
error Uint96max();
/*///////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public constant decimals = 18;
/*///////////////////////////////////////////////////////////////
ERC-20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*///////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
bytes32 public constant PERMIT_TYPEHASH =
keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');
uint256 internal INITIAL_CHAIN_ID;
bytes32 internal INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*///////////////////////////////////////////////////////////////
DAO STORAGE
//////////////////////////////////////////////////////////////*/
bool public paused;
bytes32 public constant DELEGATION_TYPEHASH =
keccak256('Delegation(address delegatee,uint256 nonce,uint256 expiry)');
mapping(address => address) internal _delegates;
mapping(address => mapping(uint256 => Checkpoint)) public checkpoints;
mapping(address => uint256) public numCheckpoints;
struct Checkpoint {
uint32 fromTimestamp;
uint96 votes;
}
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
function _init(
string memory name_,
string memory symbol_,
bool paused_,
address[] memory voters_,
uint256[] memory shares_
) internal virtual {
if (voters_.length != shares_.length) revert NoArrayParity();
name = name_;
symbol = symbol_;
paused = paused_;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator();
// cannot realistically overflow on human timescales
unchecked {
for (uint256 i; i < voters_.length; i++) {
_mint(voters_[i], shares_[i]);
}
}
}
/*///////////////////////////////////////////////////////////////
ERC-20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public notPaused virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// cannot overflow because the sum of all user
// balances can't exceed the max uint256 value
unchecked {
balanceOf[to] += amount;
}
_moveDelegates(delegates(msg.sender), delegates(to), amount);
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public notPaused virtual returns (bool) {
if (allowance[from][msg.sender] != type(uint256).max)
allowance[from][msg.sender] -= amount;
balanceOf[from] -= amount;
// cannot overflow because the sum of all user
// balances can't exceed the max uint256 value
unchecked {
balanceOf[to] += amount;
}
_moveDelegates(delegates(from), delegates(to), amount);
emit Transfer(from, to, amount);
return true;
}
/*///////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (block.timestamp > deadline) revert SignatureExpired();
// cannot realistically overflow on human timescales
unchecked {
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR(),
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
if (recoveredAddress == address(0) || recoveredAddress != owner) revert InvalidSignature();
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator();
}
function _computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256('1'),
block.chainid,
address(this)
)
);
}
/*///////////////////////////////////////////////////////////////
DAO LOGIC
//////////////////////////////////////////////////////////////*/
modifier notPaused() {
if (paused) revert Paused();
_;
}
function delegates(address delegator) public view virtual returns (address) {
address current = _delegates[delegator];
return current == address(0) ? delegator : current;
}
function getCurrentVotes(address account) public view virtual returns (uint256) {
// this is safe from underflow because decrement only occurs if `nCheckpoints` is positive
unchecked {
uint256 nCheckpoints = numCheckpoints[account];
return nCheckpoints != 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
}
}
function delegate(address delegatee) public virtual {
_delegate(msg.sender, delegatee);
}
function delegateBySig(
address delegatee,
uint256 nonce,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (block.timestamp > deadline) revert SignatureExpired();
bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, deadline));
bytes32 digest = keccak256(abi.encodePacked('\x19\x01', DOMAIN_SEPARATOR(), structHash));
address signatory = ecrecover(digest, v, r, s);
if (signatory == address(0)) revert NullAddress();
// cannot realistically overflow on human timescales
unchecked {
if (nonce != nonces[signatory]++) revert InvalidNonce();
}
_delegate(signatory, delegatee);
}
function getPriorVotes(address account, uint256 timestamp) public view virtual returns (uint96) {
if (block.timestamp <= timestamp) revert NotDetermined();
uint256 nCheckpoints = numCheckpoints[account];
if (nCheckpoints == 0) return 0;
// this is safe from underflow because decrement only occurs if `nCheckpoints` is positive
unchecked {
if (checkpoints[account][nCheckpoints - 1].fromTimestamp <= timestamp)
return checkpoints[account][nCheckpoints - 1].votes;
if (checkpoints[account][0].fromTimestamp > timestamp) return 0;
uint256 lower;
// this is safe from underflow because decrement only occurs if `nCheckpoints` is positive
uint256 upper = nCheckpoints - 1;
while (upper > lower) {
// this is safe from underflow because `upper` ceiling is provided
uint256 center = upper - (upper - lower) / 2;
Checkpoint memory cp = checkpoints[account][center];
if (cp.fromTimestamp == timestamp) {
return cp.votes;
} else if (cp.fromTimestamp < timestamp) {
lower = center;
} else {
upper = center - 1;
}
}
return checkpoints[account][lower].votes;
}
}
function _delegate(address delegator, address delegatee) internal virtual {
address currentDelegate = delegates(delegator);
_delegates[delegator] = delegatee;
_moveDelegates(currentDelegate, delegatee, balanceOf[delegator]);
emit DelegateChanged(delegator, currentDelegate, delegatee);
}
function _moveDelegates(
address srcRep,
address dstRep,
uint256 amount
) internal virtual {
if (srcRep != dstRep && amount != 0)
if (srcRep != address(0)) {
uint256 srcRepNum = numCheckpoints[srcRep];
uint256 srcRepOld = srcRepNum != 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
uint256 srcRepNew = srcRepOld - amount;
_writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
}
if (dstRep != address(0)) {
uint256 dstRepNum = numCheckpoints[dstRep];
uint256 dstRepOld = dstRepNum != 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
uint256 dstRepNew = dstRepOld + amount;
_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
}
}
function _writeCheckpoint(
address delegatee,
uint256 nCheckpoints,
uint256 oldVotes,
uint256 newVotes
) internal virtual {
unchecked {
// this is safe from underflow because decrement only occurs if `nCheckpoints` is positive
if (nCheckpoints != 0 && checkpoints[delegatee][nCheckpoints - 1].fromTimestamp == block.timestamp) {
checkpoints[delegatee][nCheckpoints - 1].votes = _safeCastTo96(newVotes);
} else {
checkpoints[delegatee][nCheckpoints] = Checkpoint(_safeCastTo32(block.timestamp), _safeCastTo96(newVotes));
// cannot realistically overflow on human timescales
numCheckpoints[delegatee] = nCheckpoints + 1;
}
}
emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
}
/*///////////////////////////////////////////////////////////////
MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// cannot overflow because the sum of all user
// balances can't exceed the max uint256 value
unchecked {
balanceOf[to] += amount;
}
_moveDelegates(address(0), delegates(to), amount);
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// cannot underflow because a user's balance
// will never be larger than the total supply
unchecked {
totalSupply -= amount;
}
_moveDelegates(delegates(from), address(0), amount);
emit Transfer(from, address(0), amount);
}
/*///////////////////////////////////////////////////////////////
PAUSE LOGIC
//////////////////////////////////////////////////////////////*/
function _flipPause() internal virtual {
paused = !paused;
emit PauseFlipped(paused);
}
/*///////////////////////////////////////////////////////////////
SAFECAST LOGIC
//////////////////////////////////////////////////////////////*/
function _safeCastTo32(uint256 x) internal pure virtual returns (uint32) {
if (x > type(uint32).max) revert Uint32max();
return uint32(x);
}
function _safeCastTo96(uint256 x) internal pure virtual returns (uint96) {
if (x > type(uint96).max) revert Uint96max();
return uint96(x);
}
}
/// @notice Helper utility that enables calling multiple local methods in a single call.
/// @author Modified from Uniswap (https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol)
abstract contract Multicall {
function multicall(bytes[] calldata data) public virtual returns (bytes[] memory results) {
results = new bytes[](data.length);
// cannot realistically overflow on human timescales
unchecked {
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
if (!success) {
if (result.length < 68) revert();
assembly {
result := add(result, 0x04)
}
revert(abi.decode(result, (string)));
}
results[i] = result;
}
}
}
}
/// @notice Helper utility for NFT 'safe' transfers.
abstract contract NFThelper {
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external pure returns (bytes4 sig) {
sig = 0x150b7a02; // 'onERC721Received(address,address,uint256,bytes)'
}
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
) external pure returns (bytes4 sig) {
sig = 0xf23a6e61; // 'onERC1155Received(address,address,uint256,uint256,bytes)'
}
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) external pure returns (bytes4 sig) {
sig = 0xbc197c81; // 'onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)'
}
}
/// @notice Gas-optimized reentrancy protection.
/// @author Modified from OpenZeppelin
/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
/// License-Identifier: MIT
abstract contract ReentrancyGuard {
error Reentrancy();
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private status = NOT_ENTERED;
modifier nonReentrant() {
if (status == ENTERED) revert Reentrancy();
status = ENTERED;
_;
status = NOT_ENTERED;
}
}
/// @notice Kali DAO membership extension interface.
interface IKaliDAOextension {
function setExtension(bytes calldata extensionData) external;
function callExtension(
address account,
uint256 amount,
bytes calldata extensionData
) external payable returns (bool mint, uint256 amountOut);
}
/// @notice Simple gas-optimized Kali DAO core module.
contract KaliDAOinvestmentClub is KaliDAOtoken, Multicall, NFThelper, ReentrancyGuard {
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event NewProposal(address indexed proposer, uint256 indexed proposal);
event ProposalCancelled(address indexed proposer, uint256 indexed proposal);
event ProposalSponsored(address indexed sponsor, uint256 indexed proposal);
event VoteCast(address indexed voter, uint256 indexed proposal, bool indexed approve);
event ProposalProcessed(uint256 indexed proposal, bool indexed didProposalPass);
/*///////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error Initialized();
error VotingPeriodBounds();
error QuorumMax();
error SupermajorityBounds();
error TypeBounds();
error NotProposer();
error Sponsored();
error NotMember();
error NotCurrentProposal();
error AlreadyVoted();
error NotVoteable();
error VotingNotEnded();
error PrevNotProcessed();
error VoterMax();
error NotExtension();
/*///////////////////////////////////////////////////////////////
DAO STORAGE
//////////////////////////////////////////////////////////////*/
string public docs;
uint256 private currentSponsoredProposal;
uint256 public proposalCount;
uint256 public voterCount;
uint32 public votingPeriod;
uint8 public quorum; // 1-100
uint8 public supermajority; // 1-100
bytes32 public constant VOTE_HASH =
keccak256('SignVote(address signer,uint256 proposal,bool approve)');
mapping(address => bool) public extensions;
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => ProposalState) public proposalStates;
mapping(ProposalType => VoteType) public proposalVoteTypes;
mapping(uint256 => mapping(address => bool)) public voted;
mapping(address => uint256) public lastYesVote;
enum ProposalType {
MINT, // add membership
BURN, // revoke membership
CALL, // call contracts
PERIOD, // set `votingPeriod`
QUORUM, // set `quorum`
SUPERMAJORITY, // set `supermajority`
TYPE, // set `VoteType` to `ProposalType`
PAUSE, // flip membership transferability
EXTENSION, // flip `extensions` whitelisting
ESCAPE, // delete pending proposal in case of revert
DOCS // amend org docs
}
enum VoteType {
SIMPLE_MAJORITY,
SIMPLE_MAJORITY_QUORUM_REQUIRED,
SUPERMAJORITY,
SUPERMAJORITY_QUORUM_REQUIRED
}
struct Proposal {
ProposalType proposalType;
string description;
address[] accounts; // member(s) being added/kicked; account(s) receiving payload
uint256[] amounts; // value(s) to be minted/burned/spent; gov setting [0]
bytes[] payloads; // data for CALL proposals
uint256 prevProposal;
uint96 yesVotes;
uint96 noVotes;
uint32 creationTime;
address proposer;
}
struct ProposalState {
bool passed;
bool processed;
}
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
function init(
string memory name_,
string memory symbol_,
string memory docs_,
bool,
address[] memory extensions_,
bytes[] memory extensionsData_,
address[] calldata voters_,
uint256[] calldata shares_,
uint32 votingPeriod_,
uint8[13] memory govSettings_
) public payable nonReentrant virtual {
if (extensions_.length != extensionsData_.length) revert NoArrayParity();
if (votingPeriod != 0) revert Initialized();
if (votingPeriod_ == 0 || votingPeriod_ > 365 days) revert VotingPeriodBounds();
if (govSettings_[0] > 100) revert QuorumMax();
if (govSettings_[1] <= 51 || govSettings_[1] > 100) revert SupermajorityBounds();
KaliDAOtoken._init(name_, symbol_, true, voters_, shares_);
if (extensions_.length != 0) {
// cannot realistically overflow on human timescales
unchecked {
for (uint256 i; i < extensions_.length; i++) {
extensions[extensions_[i]] = true;
if (extensionsData_[i].length != 0) IKaliDAOextension(extensions_[i])
.setExtension(extensionsData_[i]);
}
}
}
for (uint256 i; i < voters_.length; i++) {
voterCount++;
}
if (voterCount > 100) revert VoterMax();
docs = docs_;
votingPeriod = votingPeriod_;
quorum = govSettings_[0];
supermajority = govSettings_[1];
// set initial vote types
proposalVoteTypes[ProposalType.MINT] = VoteType(govSettings_[2]);
proposalVoteTypes[ProposalType.BURN] = VoteType(govSettings_[3]);
proposalVoteTypes[ProposalType.CALL] = VoteType(govSettings_[4]);
proposalVoteTypes[ProposalType.PERIOD] = VoteType(govSettings_[5]);
proposalVoteTypes[ProposalType.QUORUM] = VoteType(govSettings_[6]);
proposalVoteTypes[ProposalType.SUPERMAJORITY] = VoteType(govSettings_[7]);
proposalVoteTypes[ProposalType.TYPE] = VoteType(govSettings_[8]);
proposalVoteTypes[ProposalType.PAUSE] = VoteType(govSettings_[9]);
proposalVoteTypes[ProposalType.EXTENSION] = VoteType(govSettings_[10]);
proposalVoteTypes[ProposalType.ESCAPE] = VoteType(govSettings_[11]);
proposalVoteTypes[ProposalType.DOCS] = VoteType(govSettings_[12]);
}
/*///////////////////////////////////////////////////////////////
PROPOSAL LOGIC
//////////////////////////////////////////////////////////////*/
function getProposalArrays(uint256 proposal) public view virtual returns (
address[] memory accounts,
uint256[] memory amounts,
bytes[] memory payloads
) {
Proposal storage prop = proposals[proposal];
(accounts, amounts, payloads) = (prop.accounts, prop.amounts, prop.payloads);
}
function propose(
ProposalType proposalType,
string calldata description,
address[] calldata accounts,
uint256[] calldata amounts,
bytes[] calldata payloads
) public nonReentrant virtual returns (uint256 proposal) {
if (accounts.length != amounts.length || amounts.length != payloads.length) revert NoArrayParity();
if (proposalType == ProposalType.PERIOD) if (amounts[0] == 0 || amounts[0] > 365 days) revert VotingPeriodBounds();
if (proposalType == ProposalType.QUORUM) if (amounts[0] > 100) revert QuorumMax();
if (proposalType == ProposalType.SUPERMAJORITY) if (amounts[0] <= 51 || amounts[0] > 100) revert SupermajorityBounds();
if (proposalType == ProposalType.TYPE) if (amounts[0] > 10 || amounts[1] > 3 || amounts.length != 2) revert TypeBounds();
bool selfSponsor;
// if member or extension is making proposal, include sponsorship
if (balanceOf[msg.sender] != 0 || extensions[msg.sender]) selfSponsor = true;
// cannot realistically overflow on human timescales
unchecked {
proposalCount++;
}
proposal = proposalCount;
proposals[proposal] = Proposal({
proposalType: proposalType,
description: description,
accounts: accounts,
amounts: amounts,
payloads: payloads,
prevProposal: selfSponsor ? currentSponsoredProposal : 0,
yesVotes: 0,
noVotes: 0,
creationTime: selfSponsor ? _safeCastTo32(block.timestamp) : 0,
proposer: msg.sender
});
if (selfSponsor) currentSponsoredProposal = proposal;
emit NewProposal(msg.sender, proposal);
}
function cancelProposal(uint256 proposal) public nonReentrant virtual {
Proposal storage prop = proposals[proposal];
if (msg.sender != prop.proposer) revert NotProposer();
if (prop.creationTime != 0) revert Sponsored();
delete proposals[proposal];
emit ProposalCancelled(msg.sender, proposal);
}
function sponsorProposal(uint256 proposal) public nonReentrant virtual {
Proposal storage prop = proposals[proposal];
if (balanceOf[msg.sender] == 0) revert NotMember();
if (prop.proposer == address(0)) revert NotCurrentProposal();
if (prop.creationTime != 0) revert Sponsored();
prop.prevProposal = currentSponsoredProposal;
currentSponsoredProposal = proposal;
prop.creationTime = _safeCastTo32(block.timestamp);
emit ProposalSponsored(msg.sender, proposal);
}
function vote(uint256 proposal, bool approve) public nonReentrant virtual {
_vote(msg.sender, proposal, approve);
}
function voteBySig(
address signer,
uint256 proposal,
bool approve,
uint8 v,
bytes32 r,
bytes32 s
) public nonReentrant virtual {
bytes32 digest =
keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
VOTE_HASH,
signer,
proposal,
approve
)
)
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
if (recoveredAddress != signer) revert InvalidSignature();
_vote(signer, proposal, approve);
}
function _vote(
address signer,
uint256 proposal,
bool approve
) internal virtual {
Proposal storage prop = proposals[proposal];
if (balanceOf[signer] == 0) revert NotMember();
if (voted[proposal][signer]) revert AlreadyVoted();
// this is safe from overflow because `votingPeriod` is capped so it will not combine
// with unix time to exceed the max uint256 value
unchecked {
if (block.timestamp > prop.creationTime + votingPeriod) revert NotVoteable();
}
uint96 weight = getPriorVotes(signer, prop.creationTime);
// this is safe from overflow because `yesVotes` and `noVotes` are capped by `totalSupply`
// which is checked for overflow in `KaliDAOtoken` contract
unchecked {
if (approve) {
prop.yesVotes += weight;
lastYesVote[signer] = proposal;
} else {
prop.noVotes += weight;
}
}
voted[proposal][signer] = true;
emit VoteCast(signer, proposal, approve);
}
function processProposal(uint256 proposal) public nonReentrant virtual returns (
bool didProposalPass, bytes[] memory results
) {
Proposal storage prop = proposals[proposal];
VoteType voteType = proposalVoteTypes[prop.proposalType];
if (prop.creationTime == 0) revert NotCurrentProposal();
// this is safe from overflow because `votingPeriod` is capped so it will not combine
// with unix time to exceed the max uint256 value
unchecked {
if (block.timestamp <= prop.creationTime + votingPeriod) revert VotingNotEnded();
}
// skip previous proposal processing requirement in case of escape hatch
if (prop.proposalType != ProposalType.ESCAPE)
if (proposals[prop.prevProposal].creationTime != 0) revert PrevNotProcessed();
didProposalPass = _countVotes(voteType, prop.yesVotes, prop.noVotes);
if (didProposalPass) {
// cannot realistically overflow on human timescales
unchecked {
if (prop.proposalType == ProposalType.MINT)
for (uint256 i; i < prop.accounts.length; i++) {
_mint(prop.accounts[i], prop.amounts[i]);
if (balanceOf[prop.accounts[i]] == 0) voterCount++;
}
if (voterCount > 100) revert VoterMax();
if (prop.proposalType == ProposalType.BURN)
for (uint256 i; i < prop.accounts.length; i++) {
_burn(prop.accounts[i], prop.amounts[i]);
}
if (prop.proposalType == ProposalType.CALL)
for (uint256 i; i < prop.accounts.length; i++) {
results = new bytes[](prop.accounts.length);
(, bytes memory result) = prop.accounts[i].call{value: prop.amounts[i]}
(prop.payloads[i]);
results[i] = result;
}
// governance settings
if (prop.proposalType == ProposalType.PERIOD)
if (prop.amounts[0] != 0) votingPeriod = uint32(prop.amounts[0]);
if (prop.proposalType == ProposalType.QUORUM)
if (prop.amounts[0] != 0) quorum = uint8(prop.amounts[0]);
if (prop.proposalType == ProposalType.SUPERMAJORITY)
if (prop.amounts[0] != 0) supermajority = uint8(prop.amounts[0]);
if (prop.proposalType == ProposalType.TYPE)
proposalVoteTypes[ProposalType(prop.amounts[0])] = VoteType(prop.amounts[1]);
if (prop.proposalType == ProposalType.PAUSE)
_flipPause();
if (prop.proposalType == ProposalType.EXTENSION)
for (uint256 i; i < prop.accounts.length; i++) {
if (prop.amounts[i] != 0)
extensions[prop.accounts[i]] = !extensions[prop.accounts[i]];
if (prop.payloads[i].length != 0) IKaliDAOextension(prop.accounts[i])
.setExtension(prop.payloads[i]);
}
if (prop.proposalType == ProposalType.ESCAPE)
delete proposals[prop.amounts[0]];
if (prop.proposalType == ProposalType.DOCS)
docs = prop.description;
proposalStates[proposal].passed = true;
}
}
delete proposals[proposal];
proposalStates[proposal].processed = true;
emit ProposalProcessed(proposal, didProposalPass);
}
function _countVotes(
VoteType voteType,
uint256 yesVotes,
uint256 noVotes
) internal view virtual returns (bool didProposalPass) {
// fail proposal if no participation
if (yesVotes == 0 && noVotes == 0) return false;
// rule out any failed quorums
if (voteType == VoteType.SIMPLE_MAJORITY_QUORUM_REQUIRED || voteType == VoteType.SUPERMAJORITY_QUORUM_REQUIRED) {
uint256 minVotes = (totalSupply * quorum) / 100;
// this is safe from overflow because `yesVotes` and `noVotes`
// supply are checked in `KaliDAOtoken` contract
unchecked {
uint256 votes = yesVotes + noVotes;
if (votes < minVotes) return false;
}
}
// simple majority check
if (voteType == VoteType.SIMPLE_MAJORITY || voteType == VoteType.SIMPLE_MAJORITY_QUORUM_REQUIRED) {
if (yesVotes > noVotes) return true;
// supermajority check
} else {
// example: 7 yes, 2 no, supermajority = 66
// ((7+2) * 66) / 100 = 5.94; 7 yes will pass
uint256 minYes = ((yesVotes + noVotes) * supermajority) / 100;
if (yesVotes >= minYes) return true;
}
}
/*///////////////////////////////////////////////////////////////
UTILITIES
//////////////////////////////////////////////////////////////*/
receive() external payable virtual {}
function callExtension(
address extension,
uint256 amount,
bytes calldata extensionData
) public payable nonReentrant virtual returns (bool mint, uint256 amountOut) {
if (!extensions[extension] && !extensions[msg.sender]) revert NotExtension();
address account;
if (extensions[msg.sender]) {
account = extension;
amountOut = amount;
mint = abi.decode(extensionData, (bool));
} else {
account = msg.sender;
(mint, amountOut) = IKaliDAOextension(extension).callExtension{value: msg.value}
(msg.sender, amount, extensionData);
}
if (mint) {
if (amountOut != 0) _mint(account, amountOut);
} else {
if (amountOut != 0) _burn(account, amount);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment