Skip to content

Instantly share code, notes, and snippets.

@rhlsthrm
Created February 8, 2019 19:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rhlsthrm/78ff27c0df02d418e780d83f8c134fc4 to your computer and use it in GitHub Desktop.
Save rhlsthrm/78ff27c0df02d418e780d83f8c134fc4 to your computer and use it in GitHub Desktop.
pragma solidity 0.5.3;
// produced by the Solididy File Flattener (c) David Appleton 2018
// contact : dave@akomba.com
// released under Apache 2.0 licence
// input /Users/rahul/Desktop/connext/connext/moloch-monorepo/packages/moloch/contracts/Moloch.sol
// flattened : Friday, 08-Feb-19 19:41:10 UTC
library SafeMath {
/**
* @dev Multiplies two unsigned integers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two unsigned integers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
/**
* @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @return the address of the owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner());
_;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract ERC20 is IERC20 {
using SafeMath for uint256;
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowed;
uint256 private _totalSupply;
/**
* @dev Total number of tokens in existence
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) public view returns (uint256) {
return _balances[owner];
}
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @param owner address The address which owns the funds.
* @param spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _allowed[owner][spender];
}
/**
* @dev Transfer token for a specified address
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function transfer(address to, uint256 value) public returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
*/
function approve(address spender, uint256 value) public returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
/**
* @dev Transfer tokens from one address to another.
* Note that while this function emits an Approval event, this is not required as per the specification,
* and other compliant implementations may not emit the event.
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 the amount of tokens to be transferred
*/
function transferFrom(address from, address to, uint256 value) public returns (bool) {
_transfer(from, to, value);
_approve(from, msg.sender, _allowed[from][msg.sender].sub(value));
return true;
}
/**
* @dev Increase the amount of tokens that an owner allowed to a spender.
* approve should be called when allowed_[_spender] == 0. To increment
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* Emits an Approval event.
* @param spender The address which will spend the funds.
* @param addedValue The amount of tokens to increase the allowance by.
*/
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue));
return true;
}
/**
* @dev Decrease the amount of tokens that an owner allowed to a spender.
* approve should be called when allowed_[_spender] == 0. To decrement
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* Emits an Approval event.
* @param spender The address which will spend the funds.
* @param subtractedValue The amount of tokens to decrease the allowance by.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
_approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue));
return true;
}
/**
* @dev Transfer token for a specified addresses
* @param from The address to transfer from.
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function _transfer(address from, address to, uint256 value) internal {
require(to != address(0));
_balances[from] = _balances[from].sub(value);
_balances[to] = _balances[to].add(value);
emit Transfer(from, to, value);
}
/**
* @dev Internal function that mints an amount of the token and assigns it to
* an account. This encapsulates the modification of balances such that the
* proper events are emitted.
* @param account The account that will receive the created tokens.
* @param value The amount that will be created.
*/
function _mint(address account, uint256 value) internal {
require(account != address(0));
_totalSupply = _totalSupply.add(value);
_balances[account] = _balances[account].add(value);
emit Transfer(address(0), account, value);
}
/**
* @dev Internal function that burns an amount of the token of a given
* account.
* @param account The account whose tokens will be burnt.
* @param value The amount that will be burnt.
*/
function _burn(address account, uint256 value) internal {
require(account != address(0));
_totalSupply = _totalSupply.sub(value);
_balances[account] = _balances[account].sub(value);
emit Transfer(account, address(0), value);
}
/**
* @dev Approve an address to spend another addresses' tokens.
* @param owner The address that owns the tokens.
* @param spender The address that will spend the tokens.
* @param value The number of tokens that can be spent.
*/
function _approve(address owner, address spender, uint256 value) internal {
require(spender != address(0));
require(owner != address(0));
_allowed[owner][spender] = value;
emit Approval(owner, spender, value);
}
/**
* @dev Internal function that burns an amount of the token of a given
* account, deducting from the sender's allowance for said account. Uses the
* internal burn function.
* Emits an Approval event (reflecting the reduced allowance).
* @param account The account whose tokens will be burnt.
* @param value The amount that will be burnt.
*/
function _burnFrom(address account, uint256 value) internal {
_burn(account, value);
_approve(account, msg.sender, _allowed[account][msg.sender].sub(value));
}
}
contract GuildBank is Ownable {
using SafeMath for uint256;
ERC20 public approvedToken; // approved token contract reference
event Deposit(uint256 amount);
event Withdrawal(address indexed receiver, uint256 amount);
constructor(address approvedTokenAddress) public {
approvedToken = ERC20(approvedTokenAddress);
}
function deposit(uint256 amount) public onlyOwner returns (bool) {
emit Deposit(amount);
return approvedToken.transferFrom(msg.sender, address(this), amount);
}
function withdraw(address receiver, uint256 shares, uint256 totalShares) public onlyOwner returns (bool) {
uint256 amount = approvedToken.balanceOf(address(this)).mul(shares).div(totalShares);
emit Withdrawal(receiver, amount);
return approvedToken.transfer(receiver, amount);
}
}
contract Moloch {
using SafeMath for uint256;
/***************
GLOBAL CONSTANTS
***************/
uint256 public periodDuration; // default = 17280 = 4.8 hours in seconds (5 periods per day)
uint256 public votingPeriodLength; // default = 7 periods
uint256 public gracePeriodLength; // default = 7 periods
uint256 public proposalDeposit; // default = 10 ETH (~$1,000 worth of ETH at contract deployment)
uint256 public dilutionBound; // default = 3 - maximum multiplier a YES voter will be obligated to pay in case of mass ragequit
uint256 public processingReward; // default = 0.1 - amount of ETH to give to whoever processes a proposal
uint256 public summoningTime; // needed to determine the current period
ERC20 public approvedToken; // approved token contract reference; default = wETH
GuildBank public guildBank; // guild bank contract reference
/***************
EVENTS
***************/
event SubmitProposal(uint256 indexed index, address indexed applicant, address indexed memberAddress);
event ProcessProposal(uint256 indexed index, address indexed applicant, address indexed proposer, bool didPass, uint256 shares);
event SubmitVote(address indexed sender, address indexed memberAddress, uint256 indexed proposalIndex, uint8 uintVote);
event Ragequit(address indexed memberAddress, uint256 sharesToBurn);
/******************
INTERNAL ACCOUNTING
******************/
uint256 public currentPeriod = 0; // the current period number
uint256 public pendingProposals = 0; // the # of proposals waiting to be voted on
uint256 public totalShares = 0; // total voting shares across all members
enum Vote {
Null, // default value, counted as abstention
Yes,
No
}
struct Member {
address delegateKey; // the key responsible for submitting proposals and voting - defaults to member address unless updated
uint256 shares; // the # of voting shares assigned to this member
bool isActive; // always true once a member has been created
uint256 highestIndexYesVote; // highest proposal index # on which the member voted YES
}
struct Proposal {
address proposer; // the member who submitted the proposal
address applicant; // the applicant who wishes to become a member - this key will be used for withdrawals
uint256 sharesRequested; // the # of voting shares the applicant is requesting
uint256 startingPeriod; // the period in which voting can start for this proposal
uint256 yesVotes; // the total number of YES votes for this proposal
uint256 noVotes; // the total number of NO votes for this proposal
bool processed; // true only if the proposal has been processed
uint256 tokenTribute; // amount of tokens offered as tribute
string details; // proposal details - could be IPFS hash, plaintext, or JSON
uint256 totalSharesAtLastVote; // the total # of shares at the time of the last vote on this proposal
mapping (address => Vote) votesByMember; // the votes on this proposal by each member
}
mapping (address => bool) public isApplicant; // stores the applicant address while a proposal is active (prevents this address from being overwritten)
mapping (address => Member) public members;
mapping (address => address) public memberAddressByDelegateKey;
Proposal[] public proposalQueue;
/********
MODIFIERS
********/
modifier onlyMember {
require(members[msg.sender].shares > 0, "Moloch::onlyMember - not a member");
_;
}
modifier onlyDelegate {
require(members[memberAddressByDelegateKey[msg.sender]].shares > 0, "Moloch::onlyDelegate - not a member");
_;
}
/********
FUNCTIONS
********/
constructor(
address summoner,
address _approvedToken,
uint256 _periodDuration,
uint256 _votingPeriodLength,
uint256 _gracePeriodLength,
uint256 _proposalDeposit,
uint256 _dilutionBound,
uint256 _processingReward
) public {
require(summoner != address(0), "Moloch::constructor - summoner cannot be 0");
require(_approvedToken != address(0), "Moloch::constructor - _approvedToken cannot be 0");
require(_periodDuration > 0, "Moloch::constructor - _periodDuration cannot be 0");
require(_votingPeriodLength > 0, "Moloch::constructor - _votingPeriodLength cannot be 0");
approvedToken = ERC20(_approvedToken);
guildBank = new GuildBank(_approvedToken);
periodDuration = _periodDuration;
votingPeriodLength = _votingPeriodLength;
gracePeriodLength = _gracePeriodLength;
proposalDeposit = _proposalDeposit;
dilutionBound = _dilutionBound;
processingReward = _processingReward;
summoningTime = now;
members[summoner] = Member(summoner, 1, true, 0);
memberAddressByDelegateKey[summoner] = summoner;
totalShares = totalShares.add(1);
}
function updatePeriod() public {
uint256 newCurrentPeriod = now.sub(summoningTime).div(periodDuration);
if (newCurrentPeriod > currentPeriod) {
uint256 periodsElapsed = newCurrentPeriod.sub(currentPeriod);
currentPeriod = newCurrentPeriod;
pendingProposals = pendingProposals > periodsElapsed ? pendingProposals.sub(periodsElapsed) : 0;
}
}
/*****************
PROPOSAL FUNCTIONS
*****************/
function submitProposal(
address applicant,
uint256 tokenTribute,
uint256 sharesRequested,
string memory details
)
public
onlyDelegate
{
updatePeriod();
address memberAddress = memberAddressByDelegateKey[msg.sender];
// collect proposal deposit from proposer and store it in the Moloch until the proposal is processed
require(approvedToken.transferFrom(msg.sender, address(this), proposalDeposit), "Moloch::submitProposal - proposal deposit token transfer failed");
// collect tribute from applicant and store it in the Moloch until the proposal is processed
require(approvedToken.transferFrom(applicant, address(this), tokenTribute), "Moloch::submitProposal - tribute token transfer failed");
pendingProposals = pendingProposals.add(1);
uint256 startingPeriod = currentPeriod.add(pendingProposals);
// create proposal ...
Proposal memory proposal = Proposal({
proposer: memberAddress,
applicant: applicant,
sharesRequested: sharesRequested,
startingPeriod: startingPeriod,
yesVotes: 0,
noVotes: 0,
processed: false,
tokenTribute: tokenTribute,
details: details,
totalSharesAtLastVote: totalShares
});
// ... and append it to the queue
proposalQueue.push(proposal);
// save the applicant address (to prevent delegate keys from overwriting it)
isApplicant[proposal.applicant] = true;
uint256 proposalIndex = proposalQueue.length.sub(1);
emit SubmitProposal(proposalIndex, applicant, memberAddress);
}
function submitVote(uint256 proposalIndex, uint8 uintVote) public onlyDelegate {
updatePeriod();
address memberAddress = memberAddressByDelegateKey[msg.sender];
Member storage member = members[memberAddress];
Proposal storage proposal = proposalQueue[proposalIndex];
Vote vote = Vote(uintVote);
require(proposal.startingPeriod > 0, "Moloch::submitVote - proposal does not exist");
require(currentPeriod >= proposal.startingPeriod, "Moloch::submitVote - voting period has not started");
require(!hasVotingPeriodExpired(proposal.startingPeriod), "Moloch::submitVote - proposal voting period has expired");
require(proposal.votesByMember[memberAddress] == Vote.Null, "Moloch::submitVote - member has already voted on this proposal");
require(vote == Vote.Yes || vote == Vote.No, "Moloch::submitVote - vote must be either Yes or No");
// store vote
proposal.votesByMember[memberAddress] = vote;
// count vote
if (vote == Vote.Yes) {
proposal.yesVotes = proposal.yesVotes.add(member.shares);
if (proposalIndex > member.highestIndexYesVote) {
member.highestIndexYesVote = proposalIndex;
}
} else if (vote == Vote.No) {
proposal.noVotes = proposal.noVotes.add(member.shares);
}
// set total shares on proposal - used to bound dilution for yes voters
proposal.totalSharesAtLastVote = totalShares;
emit SubmitVote(msg.sender, memberAddress, proposalIndex, uintVote);
}
function processProposal(uint256 proposalIndex) public {
updatePeriod();
Proposal storage proposal = proposalQueue[proposalIndex];
require(proposal.startingPeriod > 0, "Moloch::processProposal - proposal does not exist");
require(currentPeriod.sub(proposal.startingPeriod) > votingPeriodLength.add(gracePeriodLength), "Moloch::processProposal - proposal is not ready to be processed");
require(proposal.processed == false, "Moloch::processProposal - proposal has already been processed");
require(proposalIndex == 0 || proposalQueue[proposalIndex.sub(1)].processed, "Moloch::processProposal - previous proposal must be processed");
proposal.processed = true;
bool didPass = proposal.yesVotes > proposal.noVotes;
// Make the proposal fail if the dilutionBound is exceeded
if (totalShares * dilutionBound < proposal.totalSharesAtLastVote) {
didPass = false;
}
// PROPOSAL PASSED
if (didPass) {
// if the proposer is already a member, add to their existing voting shares
if (members[proposal.applicant].isActive) {
members[proposal.applicant].shares = members[proposal.applicant].shares.add(proposal.sharesRequested);
// the applicant is a new member, create a new record for them
} else {
// use applicant address as delegateKey by default
members[proposal.applicant] = Member(proposal.applicant, proposal.sharesRequested, true, 0);
memberAddressByDelegateKey[proposal.applicant] = proposal.applicant;
}
// mint new voting shares
totalShares = totalShares.add(proposal.sharesRequested);
// transfer tokens to guild bank
require(
approvedToken.approve(address(guildBank), proposal.tokenTribute),
"Moloch::processProposal - approval of token transfer to guild bank failed"
);
require(
guildBank.deposit(proposal.tokenTribute),
"Moloch::processProposal - passing vote token transfer failed"
);
// PROPOSAL FAILED
} else {
// return all tokens to the applicant
require(
approvedToken.transfer(proposal.applicant, proposal.tokenTribute),
"Moloch::processProposal - failing vote token transfer failed"
);
}
// send msg.sender the processingReward
require(
approvedToken.transfer(msg.sender, processingReward),
"Moloch::processProposal - failed to send processing reward to msg.sender"
);
// return deposit to proposer (subtract processing reward)
require(
approvedToken.transfer(proposal.proposer, proposalDeposit.sub(processingReward)),
"Moloch::processProposal - failed to return proposal deposit to proposer"
);
// remove the isApplicant entry for the applicant
isApplicant[proposal.applicant] = false;
emit ProcessProposal(
proposalIndex,
proposal.applicant,
proposal.proposer,
didPass,
proposal.sharesRequested
);
}
function ragequit(uint256 sharesToBurn) public onlyMember {
updatePeriod();
uint256 initialTotalShares = totalShares;
Member storage member = members[msg.sender];
require(member.shares >= sharesToBurn, "Moloch::ragequit - insufficient voting shares");
require(canRagequit(member.highestIndexYesVote), "Moloch::ragequit - can't ragequit until highest index proposal member voted YES on is processed or the vote fails");
// burn voting shares
member.shares = member.shares.sub(sharesToBurn);
totalShares = totalShares.sub(sharesToBurn);
// instruct guildBank to transfer fair share of tokens to the receiver
require(
guildBank.withdraw(msg.sender, sharesToBurn, initialTotalShares),
"Moloch::ragequit - withdrawal of tokens from guildBank failed"
);
emit Ragequit(msg.sender, sharesToBurn);
}
function updateDelegateKey(address newDelegateKey) public onlyMember {
// skip checks if member is setting the delegate key to their member address
if (newDelegateKey != msg.sender) {
require(!members[newDelegateKey].isActive, "Moloch::updateDelegateKey - can't overwrite existing members");
require(!members[memberAddressByDelegateKey[newDelegateKey]].isActive, "Moloch::updateDelegateKey - can't overwrite existing delegate keys");
require(!isApplicant[newDelegateKey], "Moloch::updateDelegateKey - can't overwrite existing applicants");
}
Member storage member = members[msg.sender];
memberAddressByDelegateKey[member.delegateKey] = address(0);
memberAddressByDelegateKey[newDelegateKey] = msg.sender;
member.delegateKey = newDelegateKey;
}
/***************
GETTER FUNCTIONS
***************/
// can only ragequit if the latest proposal you voted YES on has either been processed OR voting has expired and it didn't pass
function canRagequit(uint256 highestIndexYesVote) public view returns (bool) {
Proposal memory proposal = proposalQueue[highestIndexYesVote];
return proposal.processed || (hasVotingPeriodExpired(proposal.startingPeriod) && proposal.noVotes >= proposal.yesVotes);
}
function hasVotingPeriodExpired(uint256 startingPeriod) public view returns (bool) {
return currentPeriod.sub(startingPeriod) >= votingPeriodLength;
}
function getMemberProposalVote(address memberAddress, uint256 proposalIndex) public view returns (Vote) {
return proposalQueue[proposalIndex].votesByMember[memberAddress];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment