Skip to content

Instantly share code, notes, and snippets.

@z0r0z
Created November 20, 2021 07:56
Show Gist options
  • Select an option

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

Select an option

Save z0r0z/2ef6f93c771613f309f0225a7e625a1e to your computer and use it in GitHub Desktop.
tribute to master chef and community of weirdos
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation with COMP-style governance,
/// @author Adapted from RariCapital, https://github.com/Rari-Capital/solmate/blob/main/src/erc20/ERC20.sol,
/// License-Identifier: AGPL-3.0-only.
contract NomiToken {
/*///////////////////////////////////////////////////////////////
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);
/*///////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public constant name = "Yomi Token";
string public constant symbol = "YOMI";
uint8 public constant decimals = 18;
/*///////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*///////////////////////////////////////////////////////////////
DAO STORAGE
//////////////////////////////////////////////////////////////*/
bytes32 public constant DELEGATION_TYPEHASH = keccak256('Delegation(address delegatee,uint256 nonce,uint256 expiry)');
mapping(address => address) private _delegates;
mapping(address => mapping(uint256 => Checkpoint)) public checkpoints;
mapping(address => uint256) public numCheckpoints;
struct Checkpoint {
uint32 fromTimestamp;
uint224 votes;
}
/*///////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
bytes32 public constant PERMIT_TYPEHASH =
keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor() {
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator();
}
/*///////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
// this is safe from overflow because the sum of all user
// balances can't exceed 'type(uint256).max'
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
) external returns (bool) {
if (allowance[from][msg.sender] != type(uint256).max) {
allowance[from][msg.sender] -= amount;
}
balanceOf[from] -= amount;
// this is safe from overflow because the sum of all user
// balances can't exceed 'type(uint256).max'
unchecked {
balanceOf[to] += amount;
}
_moveDelegates(delegates(from), delegates(to), amount);
emit Transfer(from, to, amount);
return true;
}
/*///////////////////////////////////////////////////////////////
DAO LOGIC
//////////////////////////////////////////////////////////////*/
function delegates(address delegator) public view returns (address delegatee) {
address current = _delegates[delegator];
delegatee = current == address(0) ? delegator : current;
}
function getCurrentVotes(address account) external view returns (uint256 votes) {
// this is safe from underflow because decrement only occurs if `nCheckpoints` is positive
unchecked {
uint256 nCheckpoints = numCheckpoints[account];
votes = nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
}
}
function delegate(address delegatee) external {
_delegate(msg.sender, delegatee);
}
function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external {
bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry));
bytes32 digest = keccak256(abi.encodePacked('\x19\x01', DOMAIN_SEPARATOR(), structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), 'ZERO_ADDRESS');
// this is reasonably safe from overflow because incrementing `nonces` beyond
// 'type(uint256).max' is exceedingly unlikely compared to optimization benefits
unchecked {
require(nonce == nonces[signatory]++, 'INVALID_NONCE');
}
require(block.timestamp <= expiry, 'SIGNATURE_EXPIRED');
_delegate(signatory, delegatee);
}
function getPriorVotes(address account, uint256 timestamp) public view returns (uint256 votes) {
require(block.timestamp > timestamp, 'NOT_YET_DETERMINED');
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 {
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 {
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 {
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 = safeCastTo224(newVotes);
} else {
checkpoints[delegatee][nCheckpoints] = Checkpoint(safeCastTo32(block.timestamp), safeCastTo224(newVotes));
// this is reasonably safe from overflow because incrementing `nCheckpoints` beyond
// 'type(uint256).max' is exceedingly unlikely compared to optimization benefits
numCheckpoints[delegatee] = nCheckpoints + 1;
}
}
emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
}
/*///////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function _computeDomainSeparator() internal view returns (bytes32 domainSeparator) {
domainSeparator = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
block.chainid,
address(this)
)
);
}
function DOMAIN_SEPARATOR() public view returns (bytes32 domainSeparator) {
domainSeparator = block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator();
}
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(block.timestamp <= deadline, 'PERMIT_DEADLINE_EXPIRED');
// this is reasonably safe from overflow because incrementing `nonces` beyond
// 'type(uint256).max' is exceedingly unlikely compared to optimization benefits
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);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'INVALID_PERMIT_SIGNATURE');
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
/*///////////////////////////////////////////////////////////////
MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal {
totalSupply += amount;
// this is safe because the sum of all user
// balances can't exceed 'type(uint256).max'
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal {
balanceOf[from] -= amount;
// this is safe because a user won't ever
// have a balance larger than `totalSupply`
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
/*///////////////////////////////////////////////////////////////
SAFECAST LOGIC
//////////////////////////////////////////////////////////////*/
function safeCastTo32(uint256 x) internal pure returns (uint32 y) {
require(x <= type(uint32).max);
y = uint32(x);
}
function safeCastTo224(uint256 x) internal pure returns (uint224 y) {
require(x <= type(uint224).max);
y = uint224(x);
}
}
/// @notice Brief interface for entering and exiting SushiBar (xSUSHI).
interface ISushiBarBasic {
function balanceOf(address account) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function enter(uint256 amount) external;
function leave(uint256 share) external;
}
/// @notice Interface for depositing into & withdrawing from BentoBox vault.
interface IBentoBoxBasic {
function deposit(
address token_,
address from,
address to,
uint256 amount,
uint256 share
) external payable returns (uint256 amountOut, uint256 shareOut);
function withdraw(
address token_,
address from,
address to,
uint256 amount,
uint256 share
) external returns (uint256 amountOut, uint256 shareOut);
}
/*============================
WELCOME TO THE PARTY (飲み会)!
============================*/
contract NomiDAO is NomiToken {
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event NewProposal(uint256 indexed proposal);
event VoteCast(address indexed voter, uint256 indexed proposal, bool indexed approve);
event ProposalProcessed(uint256 indexed proposal);
/*///////////////////////////////////////////////////////////////
DAO STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public proposalCount;
uint224 private constant votingPeriod = 432000; // 5 days in seconds
uint32 private constant quorum = 15; // %
ISushiBarBasic private constant sushiToken = ISushiBarBasic(0x6B3595068778DD592e39A122f4f5a5cF09C90fE2); // SUSHI token contract
ISushiBarBasic private constant sushiBar = ISushiBarBasic(0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272); // xSUSHI staking contract for SUSHI
IBentoBoxBasic private constant bento = IBentoBoxBasic(0xF5BCE5077908a1b7370B9ae04AdC565EBd643966); // BentoBox vault contract
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public voted;
struct Proposal {
address proposer;
string description;
address[] account; // account(s) receiving SUSHI
uint256[] amount; // SUSHI amounts(s)
uint256 bentoSum;
uint224 yesVotes;
uint224 noVotes;
uint32 creationTime;
}
/// @dev reentrancy guard
uint256 unlocked = 1;
modifier nonReentrant() {
require(unlocked == 1, "LOCKED");
unlocked = 2;
_;
unlocked = 1;
}
constructor() {
sushiToken.approve(address(sushiBar), type(uint256).max);
}
/*///////////////////////////////////////////////////////////////
DAO JOINDER
//////////////////////////////////////////////////////////////*/
function join(uint256 amount, bool sushi) external nonReentrant returns (uint256 shares) { // if sushi 'true', `sushiToken` in
if (sushi) {
sushiToken.transferFrom(msg.sender, address(this), amount);
sushiBar.enter(amount);
(, shares) = bento.deposit(address(sushiBar), address(this), address(this), sushiBar.balanceOf(address(this)), 0);
} else {
sushiBar.transferFrom(msg.sender, address(bento), amount);
(, shares) = bento.deposit(address(sushiBar), address(bento), address(this), amount, 0);
}
_mint(msg.sender, shares);
_moveDelegates(address(0), delegates(msg.sender), shares);
}
function exit(uint256 shares, bool sushi) external nonReentrant returns (uint256 amount) { // if sushi 'true', `sushiToken` out
_burn(msg.sender, shares);
_moveDelegates(delegates(msg.sender), address(0), shares);
if (sushi) {
(amount, ) = bento.withdraw(address(sushiBar), address(this), address(this), 0, shares);
sushiBar.leave(amount);
sushiToken.transfer(msg.sender, sushiToken.balanceOf(address(this)));
} else {
(amount, ) = bento.withdraw(address(sushiBar), address(this), msg.sender, 0, shares);
}
}
/*///////////////////////////////////////////////////////////////
PROPOSAL LOGIC
//////////////////////////////////////////////////////////////*/
modifier onlyTokenHolders() {
require(balanceOf[msg.sender] > 0, 'NOT_TOKEN_HOLDER');
_;
}
function propose(
string calldata description,
address[] calldata account,
uint256[] calldata amount
) external nonReentrant onlyTokenHolders returns (uint256 bentoSum) {
require(account.length == amount.length, 'NO_ARRAY_PARITY');
require(account.length <= 10, 'ARRAY_MAX');
// tally up `sum` from `amount` array
uint256 sum;
unchecked {
for (uint256 i = 0; i < amount.length; i++) {
sum += amount[i];
}
}
// 5% deposit of requested sushi
uint256 deposit = sum / 20;
sushiToken.transferFrom(msg.sender, address(this), deposit);
sushiBar.enter(deposit);
(, bentoSum) = bento.deposit(address(sushiBar), address(this), address(this), sushiBar.balanceOf(address(this)), 0);
uint256 proposal = proposalCount;
proposals[proposal] = Proposal({
proposer: msg.sender,
description: description,
account: account,
amount: amount,
bentoSum: bentoSum,
yesVotes: 0,
noVotes: 0,
creationTime: safeCastTo32(block.timestamp)
});
// this is reasonably safe from overflow because incrementing `proposalCount` beyond
// 'type(uint256).max' is exceedingly unlikely compared to optimization benefits
unchecked {
proposalCount++;
}
emit NewProposal(proposal);
}
function vote(uint256 proposal, bool approve) external nonReentrant onlyTokenHolders {
require(!voted[proposal][msg.sender], 'ALREADY_VOTED');
Proposal storage prop = proposals[proposal];
// this is safe from overflow because `votingPeriod` is capped so it will not combine
// with unix time to exceed 'type(uint256).max'
unchecked {
require(block.timestamp <= prop.creationTime + votingPeriod, 'VOTING_ENDED');
}
uint224 weight = uint224(getPriorVotes(msg.sender, prop.creationTime));
// this is safe from overflow because `yesVotes` and `noVotes` are capped by `totalSupply`
// which is checked for overflow in `YomiToken` contract
unchecked {
if (approve) {
prop.yesVotes += weight;
} else {
prop.noVotes += weight;
}
}
voted[proposal][msg.sender] = true;
emit VoteCast(msg.sender, proposal, approve);
}
function processProposal(uint256 proposal) external nonReentrant {
Proposal storage prop = proposals[proposal];
// this is safe from overflow because `votingPeriod` is capped so it will not combine
// with unix time to exceed 'type(uint256).max'
unchecked {
require(block.timestamp > prop.creationTime + votingPeriod, 'VOTING_NOT_ENDED');
}
uint256 minVotes = (totalSupply * quorum) / 100;
// this is safe from overflow because `yesVotes` and `noVotes` are capped by `totalSupply`
// which is checked for overflow in `LiteDAOtoken` contract
unchecked {
uint224 votes = prop.yesVotes + prop.noVotes;
require(votes >= minVotes, 'QUORUM_REQUIRED');
}
if (prop.yesVotes > prop.noVotes) {
// this is reasonably safe from overflow because incrementing `i` loop beyond
// 'type(uint256).max' is exceedingly unlikely compared to optimization benefits
unchecked {
for (uint256 i; i < prop.account.length; i++) {
sushiToken.transfer(prop.account[i], prop.amount[i]);
}
}
}
delete proposals[proposal];
emit ProposalProcessed(proposal);
}
receive() external payable {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment