Skip to content

Instantly share code, notes, and snippets.

@e00dan
Created June 29, 2022 10:05
Show Gist options
  • Save e00dan/5191646c71f067c3c9487a88f26f20e6 to your computer and use it in GitHub Desktop.
Save e00dan/5191646c71f067c3c9487a88f26f20e6 to your computer and use it in GitHub Desktop.
{
"language": "Solidity",
"sources": {
"contracts/ERC20.sol": {
"content": "pragma solidity ^0.5.2;\n\nimport \"./IERC20.sol\";\nimport \"./SafeMath.sol\";\n\n/**\n * @title Standard ERC20 token\n *\n * @dev Implementation of the basic standard token.\n * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md\n * Originally based on code by FirstBlood:\n * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol\n *\n * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for\n * all accounts just by listening to said events. Note that this isn't required by the specification, and other\n * compliant implementations may not do it.\n */\ncontract ERC20 is IERC20 {\n using SafeMath for uint256;\n\n mapping (address => uint256) private _balances;\n\n mapping (address => mapping (address => uint256)) private _allowed;\n\n uint256 private _totalSupply;\n\n /**\n * @dev Total number of tokens in existence\n */\n function totalSupply() public view returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev Gets the balance of the specified address.\n * @param owner The address to query the balance of.\n * @return An uint256 representing the amount owned by the passed address.\n */\n function balanceOf(address owner) public view returns (uint256) {\n return _balances[owner];\n }\n\n /**\n * @dev Function to check the amount of tokens that an owner allowed to a spender.\n * @param owner address The address which owns the funds.\n * @param spender address The address which will spend the funds.\n * @return A uint256 specifying the amount of tokens still available for the spender.\n */\n function allowance(address owner, address spender) public view returns (uint256) {\n return _allowed[owner][spender];\n }\n\n /**\n * @dev Transfer token for a specified address\n * @param to The address to transfer to.\n * @param value The amount to be transferred.\n */\n function transfer(address to, uint256 value) public returns (bool) {\n _transfer(msg.sender, to, value);\n return true;\n }\n\n /**\n * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.\n * Beware that changing an allowance with this method brings the risk that someone may use both the old\n * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this\n * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n */\n function approve(address spender, uint256 value) public returns (bool) {\n _approve(msg.sender, spender, value);\n return true;\n }\n\n /**\n * @dev Transfer tokens from one address to another.\n * Note that while this function emits an Approval event, this is not required as per the specification,\n * and other compliant implementations may not emit the event.\n * @param from address The address which you want to send tokens from\n * @param to address The address which you want to transfer to\n * @param value uint256 the amount of tokens to be transferred\n */\n function transferFrom(address from, address to, uint256 value) public returns (bool) {\n _transfer(from, to, value);\n _approve(from, msg.sender, _allowed[from][msg.sender].sub(value));\n return true;\n }\n\n /**\n * @dev Increase the amount of tokens that an owner allowed to a spender.\n * approve should be called when allowed_[_spender] == 0. To increment\n * allowed value is better to use this function to avoid 2 calls (and wait until\n * the first transaction is mined)\n * From MonolithDAO Token.sol\n * Emits an Approval event.\n * @param spender The address which will spend the funds.\n * @param addedValue The amount of tokens to increase the allowance by.\n */\n function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {\n _approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue));\n return true;\n }\n\n /**\n * @dev Decrease the amount of tokens that an owner allowed to a spender.\n * approve should be called when allowed_[_spender] == 0. To decrement\n * allowed value is better to use this function to avoid 2 calls (and wait until\n * the first transaction is mined)\n * From MonolithDAO Token.sol\n * Emits an Approval event.\n * @param spender The address which will spend the funds.\n * @param subtractedValue The amount of tokens to decrease the allowance by.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {\n _approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue));\n return true;\n }\n\n /**\n * @dev Transfer token for a specified addresses\n * @param from The address to transfer from.\n * @param to The address to transfer to.\n * @param value The amount to be transferred.\n */\n function _transfer(address from, address to, uint256 value) internal {\n require(to != address(0));\n\n _balances[from] = _balances[from].sub(value);\n _balances[to] = _balances[to].add(value);\n emit Transfer(from, to, value);\n }\n\n /**\n * @dev Internal function that mints an amount of the token and assigns it to\n * an account. This encapsulates the modification of balances such that the\n * proper events are emitted.\n * @param account The account that will receive the created tokens.\n * @param value The amount that will be created.\n */\n function _mint(address account, uint256 value) internal {\n require(account != address(0));\n\n _totalSupply = _totalSupply.add(value);\n _balances[account] = _balances[account].add(value);\n emit Transfer(address(0), account, value);\n }\n\n /**\n * @dev Internal function that burns an amount of the token of a given\n * account.\n * @param account The account whose tokens will be burnt.\n * @param value The amount that will be burnt.\n */\n function _burn(address account, uint256 value) internal {\n require(account != address(0));\n\n _totalSupply = _totalSupply.sub(value);\n _balances[account] = _balances[account].sub(value);\n emit Transfer(account, address(0), value);\n }\n\n /**\n * @dev Approve an address to spend another addresses' tokens.\n * @param owner The address that owns the tokens.\n * @param spender The address that will spend the tokens.\n * @param value The number of tokens that can be spent.\n */\n function _approve(address owner, address spender, uint256 value) internal {\n require(spender != address(0));\n require(owner != address(0));\n\n _allowed[owner][spender] = value;\n emit Approval(owner, spender, value);\n }\n\n /**\n * @dev Internal function that burns an amount of the token of a given\n * account, deducting from the sender's allowance for said account. Uses the\n * internal burn function.\n * Emits an Approval event (reflecting the reduced allowance).\n * @param account The account whose tokens will be burnt.\n * @param value The amount that will be burnt.\n */\n function _burnFrom(address account, uint256 value) internal {\n _burn(account, value);\n _approve(account, msg.sender, _allowed[account][msg.sender].sub(value));\n }\n}\n"
},
"contracts/IERC20.sol": {
"content": "pragma solidity ^0.5.2;\n\ninterface IERC20 {\n function transfer(address to, uint256 value) external returns (bool);\n\n function approve(address spender, uint256 value) external returns (bool);\n\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n\n function totalSupply() external view returns (uint256);\n\n function balanceOf(address who) external view returns (uint256);\n\n function allowance(address owner, address spender) external view returns (uint256);\n\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n"
},
"contracts/SafeMath.sol": {
"content": "pragma solidity ^0.5.2;\n\nlibrary SafeMath {\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n if (a == 0) {\n return 0;\n }\n\n uint256 c = a * b;\n require(c / a == b);\n\n return c;\n }\n\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\n\n require(b > 0);\n uint256 c = a / b;\n\n return c;\n }\n\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n require(b <= a);\n uint256 c = a - b;\n\n return c;\n }\n\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 c = a + b;\n require(c >= a);\n\n return c;\n }\n}\n"
},
"contracts/Token.sol": {
"content": "pragma solidity ^0.5.2;\n\nimport \"./ERC20.sol\";\n\ncontract Token is ERC20 {\n string private _name;\n string private _symbol;\n\n constructor(string memory name_, string memory symbol_, uint256 supply) public {\n _name = name_;\n _symbol = symbol_;\n _mint(msg.sender, supply);\n }\n\n function name() public view returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view returns (string memory) {\n return _symbol;\n }\n\n function decimals() public view returns (uint8) {\n return 18;\n }\n}\n"
},
"contracts/Moloch.sol": {
"content": "pragma solidity 0.5.3;\n\nimport \"./SafeMath.sol\";\nimport \"./IERC20.sol\";\nimport \"./ReentrancyGuard.sol\";\n\ncontract Moloch is ReentrancyGuard {\n using SafeMath for uint256;\n\n /***************\n GLOBAL CONSTANTS\n ***************/\n uint256 public periodDuration; // default = 17280 = 4.8 hours in seconds (5 periods per day)\n uint256 public votingPeriodLength; // default = 35 periods (7 days)\n uint256 public gracePeriodLength; // default = 35 periods (7 days)\n uint256 public proposalDeposit; // default = 10 ETH (~$1,000 worth of ETH at contract deployment)\n uint256 public dilutionBound; // default = 3 - maximum multiplier a YES voter will be obligated to pay in case of mass ragequit\n uint256 public processingReward; // default = 0.1 - amount of ETH to give to whoever processes a proposal\n uint256 public summoningTime; // needed to determine the current period\n\n address public depositToken; // deposit token contract reference; default = wETH\n\n // HARD-CODED LIMITS\n // These numbers are quite arbitrary; they are small enough to avoid overflows when doing calculations\n // with periods or shares, yet big enough to not limit reasonable use cases.\n uint256 constant MAX_VOTING_PERIOD_LENGTH = 10**18; // maximum length of voting period\n uint256 constant MAX_GRACE_PERIOD_LENGTH = 10**18; // maximum length of grace period\n uint256 constant MAX_DILUTION_BOUND = 10**18; // maximum dilution bound\n uint256 constant MAX_NUMBER_OF_SHARES_AND_LOOT = 10**18; // maximum number of shares that can be minted\n uint256 constant MAX_TOKEN_WHITELIST_COUNT = 400; // maximum number of whitelisted tokens\n uint256 constant MAX_TOKEN_GUILDBANK_COUNT = 200; // maximum number of tokens with non-zero balance in guildbank\n\n // ***************\n // EVENTS\n // ***************\n event SummonComplete(address indexed summoner, address[] tokens, uint256 summoningTime, uint256 periodDuration, uint256 votingPeriodLength, uint256 gracePeriodLength, uint256 proposalDeposit, uint256 dilutionBound, uint256 processingReward);\n event SubmitProposal(address indexed applicant, uint256 sharesRequested, uint256 lootRequested, uint256 tributeOffered, address tributeToken, uint256 paymentRequested, address paymentToken, string details, bool[6] flags, uint256 proposalId, address indexed delegateKey, address indexed memberAddress);\n event SponsorProposal(address indexed delegateKey, address indexed memberAddress, uint256 proposalId, uint256 proposalIndex, uint256 startingPeriod);\n event SubmitVote(uint256 proposalId, uint256 indexed proposalIndex, address indexed delegateKey, address indexed memberAddress, uint8 uintVote);\n event ProcessProposal(uint256 indexed proposalIndex, uint256 indexed proposalId, bool didPass);\n event ProcessWhitelistProposal(uint256 indexed proposalIndex, uint256 indexed proposalId, bool didPass);\n event ProcessGuildKickProposal(uint256 indexed proposalIndex, uint256 indexed proposalId, bool didPass);\n event Ragequit(address indexed memberAddress, uint256 sharesToBurn, uint256 lootToBurn);\n event TokensCollected(address indexed token, uint256 amountToCollect);\n event CancelProposal(uint256 indexed proposalId, address applicantAddress);\n event UpdateDelegateKey(address indexed memberAddress, address newDelegateKey);\n event Withdraw(address indexed memberAddress, address token, uint256 amount);\n\n // *******************\n // INTERNAL ACCOUNTING\n // *******************\n uint256 public proposalCount = 0; // total proposals submitted\n uint256 public totalShares = 0; // total shares across all members\n uint256 public totalLoot = 0; // total loot across all members\n\n uint256 public totalGuildBankTokens = 0; // total tokens with non-zero balance in guild bank\n\n address public constant GUILD = address(0xdead);\n address public constant ESCROW = address(0xbeef);\n address public constant TOTAL = address(0xbabe);\n mapping (address => mapping(address => uint256)) public userTokenBalances; // userTokenBalances[userAddress][tokenAddress]\n\n enum Vote {\n Null, // default value, counted as abstention\n Yes,\n No\n }\n\n struct Member {\n address delegateKey; // the key responsible for submitting proposals and voting - defaults to member address unless updated\n uint256 shares; // the # of voting shares assigned to this member\n uint256 loot; // the loot amount available to this member (combined with shares on ragequit)\n bool exists; // always true once a member has been created\n uint256 highestIndexYesVote; // highest proposal index # on which the member voted YES\n uint256 jailed; // set to proposalIndex of a passing guild kick proposal for this member, prevents voting on and sponsoring proposals\n }\n\n struct Proposal {\n address applicant; // the applicant who wishes to become a member - this key will be used for withdrawals (doubles as guild kick target for gkick proposals)\n address proposer; // the account that submitted the proposal (can be non-member)\n address sponsor; // the member that sponsored the proposal (moving it into the queue)\n uint256 sharesRequested; // the # of shares the applicant is requesting\n uint256 lootRequested; // the amount of loot the applicant is requesting\n uint256 tributeOffered; // amount of tokens offered as tribute\n address tributeToken; // tribute token contract reference\n uint256 paymentRequested; // amount of tokens requested as payment\n address paymentToken; // payment token contract reference\n uint256 startingPeriod; // the period in which voting can start for this proposal\n uint256 yesVotes; // the total number of YES votes for this proposal\n uint256 noVotes; // the total number of NO votes for this proposal\n bool[6] flags; // [sponsored, processed, didPass, cancelled, whitelist, guildkick]\n string details; // proposal details - could be IPFS hash, plaintext, or JSON\n uint256 maxTotalSharesAndLootAtYesVote; // the maximum # of total shares encountered at a yes vote on this proposal\n mapping(address => Vote) votesByMember; // the votes on this proposal by each member\n }\n\n mapping(address => bool) public tokenWhitelist;\n address[] public approvedTokens;\n\n mapping(address => bool) public proposedToWhitelist;\n mapping(address => bool) public proposedToKick;\n\n mapping(address => Member) public members;\n mapping(address => address) public memberAddressByDelegateKey;\n\n mapping(uint256 => Proposal) public proposals;\n\n uint256[] public proposalQueue;\n\n modifier onlyMember {\n require(members[msg.sender].shares > 0 || members[msg.sender].loot > 0, \"not a member\");\n _;\n }\n\n modifier onlyShareholder {\n require(members[msg.sender].shares > 0, \"not a shareholder\");\n _;\n }\n\n modifier onlyDelegate {\n require(members[memberAddressByDelegateKey[msg.sender]].shares > 0, \"not a delegate\");\n _;\n }\n\n constructor(\n address _summoner,\n address[] memory _approvedTokens,\n uint256 _periodDuration,\n uint256 _votingPeriodLength,\n uint256 _gracePeriodLength,\n uint256 _proposalDeposit,\n uint256 _dilutionBound,\n uint256 _processingReward\n ) public {\n require(_summoner != address(0), \"summoner cannot be 0\");\n require(_periodDuration > 0, \"_periodDuration cannot be 0\");\n require(_votingPeriodLength > 0, \"_votingPeriodLength cannot be 0\");\n require(_votingPeriodLength <= MAX_VOTING_PERIOD_LENGTH, \"_votingPeriodLength exceeds limit\");\n require(_gracePeriodLength <= MAX_GRACE_PERIOD_LENGTH, \"_gracePeriodLength exceeds limit\");\n require(_dilutionBound > 0, \"_dilutionBound cannot be 0\");\n require(_dilutionBound <= MAX_DILUTION_BOUND, \"_dilutionBound exceeds limit\");\n require(_approvedTokens.length > 0, \"need at least one approved token\");\n require(_approvedTokens.length <= MAX_TOKEN_WHITELIST_COUNT, \"too many tokens\");\n require(_proposalDeposit >= _processingReward, \"_proposalDeposit cannot be smaller than _processingReward\");\n \n depositToken = _approvedTokens[0];\n // NOTE: move event up here, avoid stack too deep if too many approved tokens\n emit SummonComplete(_summoner, _approvedTokens, now, _periodDuration, _votingPeriodLength, _gracePeriodLength, _proposalDeposit, _dilutionBound, _processingReward);\n\n\n for (uint256 i = 0; i < _approvedTokens.length; i++) {\n require(_approvedTokens[i] != address(0), \"_approvedToken cannot be 0\");\n require(!tokenWhitelist[_approvedTokens[i]], \"duplicate approved token\");\n tokenWhitelist[_approvedTokens[i]] = true;\n approvedTokens.push(_approvedTokens[i]);\n }\n\n periodDuration = _periodDuration;\n votingPeriodLength = _votingPeriodLength;\n gracePeriodLength = _gracePeriodLength;\n proposalDeposit = _proposalDeposit;\n dilutionBound = _dilutionBound;\n processingReward = _processingReward;\n\n summoningTime = now;\n\n members[_summoner] = Member(_summoner, 1, 0, true, 0, 0);\n memberAddressByDelegateKey[_summoner] = _summoner;\n totalShares = 1;\n \n }\n\n /*****************\n PROPOSAL FUNCTIONS\n *****************/\n function submitProposal(\n address applicant,\n uint256 sharesRequested,\n uint256 lootRequested,\n uint256 tributeOffered,\n address tributeToken,\n uint256 paymentRequested,\n address paymentToken,\n string memory details\n ) public nonReentrant returns (uint256 proposalId) {\n require(sharesRequested.add(lootRequested) <= MAX_NUMBER_OF_SHARES_AND_LOOT, \"too many shares requested\");\n require(tokenWhitelist[tributeToken], \"tributeToken is not whitelisted\");\n require(tokenWhitelist[paymentToken], \"payment is not whitelisted\");\n require(applicant != address(0), \"applicant cannot be 0\");\n require(applicant != GUILD && applicant != ESCROW && applicant != TOTAL, \"applicant address cannot be reserved\");\n require(members[applicant].jailed == 0, \"proposal applicant must not be jailed\");\n\n if (tributeOffered > 0 && userTokenBalances[GUILD][tributeToken] == 0) {\n require(totalGuildBankTokens < MAX_TOKEN_GUILDBANK_COUNT, 'cannot submit more tribute proposals for new tokens - guildbank is full');\n }\n\n // collect tribute from proposer and store it in the Moloch until the proposal is processed\n require(IERC20(tributeToken).transferFrom(msg.sender, address(this), tributeOffered), \"tribute token transfer failed\");\n unsafeAddToBalance(ESCROW, tributeToken, tributeOffered);\n\n bool[6] memory flags; // [sponsored, processed, didPass, cancelled, whitelist, guildkick]\n\n _submitProposal(applicant, sharesRequested, lootRequested, tributeOffered, tributeToken, paymentRequested, paymentToken, details, flags);\n return proposalCount - 1; // return proposalId - contracts calling submit might want it\n }\n\n function submitWhitelistProposal(address tokenToWhitelist, string memory details) public nonReentrant returns (uint256 proposalId) {\n require(tokenToWhitelist != address(0), \"must provide token address\");\n require(!tokenWhitelist[tokenToWhitelist], \"cannot already have whitelisted the token\");\n require(approvedTokens.length < MAX_TOKEN_WHITELIST_COUNT, \"cannot submit more whitelist proposals\");\n\n bool[6] memory flags; // [sponsored, processed, didPass, cancelled, whitelist, guildkick]\n flags[4] = true; // whitelist\n\n _submitProposal(address(0), 0, 0, 0, tokenToWhitelist, 0, address(0), details, flags);\n return proposalCount - 1;\n }\n\n function submitGuildKickProposal(address memberToKick, string memory details) public nonReentrant returns (uint256 proposalId) {\n Member memory member = members[memberToKick];\n\n require(member.shares > 0 || member.loot > 0, \"member must have at least one share or one loot\");\n require(members[memberToKick].jailed == 0, \"member must not already be jailed\");\n\n bool[6] memory flags; // [sponsored, processed, didPass, cancelled, whitelist, guildkick]\n flags[5] = true; // guild kick\n\n _submitProposal(memberToKick, 0, 0, 0, address(0), 0, address(0), details, flags);\n return proposalCount - 1;\n }\n\n function _submitProposal(\n address applicant,\n uint256 sharesRequested,\n uint256 lootRequested,\n uint256 tributeOffered,\n address tributeToken,\n uint256 paymentRequested,\n address paymentToken,\n string memory details,\n bool[6] memory flags\n ) internal {\n Proposal memory proposal = Proposal({\n applicant : applicant,\n proposer : msg.sender,\n sponsor : address(0),\n sharesRequested : sharesRequested,\n lootRequested : lootRequested,\n tributeOffered : tributeOffered,\n tributeToken : tributeToken,\n paymentRequested : paymentRequested,\n paymentToken : paymentToken,\n startingPeriod : 0,\n yesVotes : 0,\n noVotes : 0,\n flags : flags,\n details : details,\n maxTotalSharesAndLootAtYesVote : 0\n });\n\n proposals[proposalCount] = proposal;\n address memberAddress = memberAddressByDelegateKey[msg.sender];\n // NOTE: argument order matters, avoid stack too deep\n emit SubmitProposal(applicant, sharesRequested, lootRequested, tributeOffered, tributeToken, paymentRequested, paymentToken, details, flags, proposalCount, msg.sender, memberAddress);\n proposalCount += 1;\n }\n\n function sponsorProposal(uint256 proposalId) public nonReentrant onlyDelegate {\n // collect proposal deposit from sponsor and store it in the Moloch until the proposal is processed\n require(IERC20(depositToken).transferFrom(msg.sender, address(this), proposalDeposit), \"proposal deposit token transfer failed\");\n unsafeAddToBalance(ESCROW, depositToken, proposalDeposit);\n\n Proposal storage proposal = proposals[proposalId];\n\n require(proposal.proposer != address(0), 'proposal must have been proposed');\n require(!proposal.flags[0], \"proposal has already been sponsored\");\n require(!proposal.flags[3], \"proposal has been cancelled\");\n require(members[proposal.applicant].jailed == 0, \"proposal applicant must not be jailed\");\n\n if (proposal.tributeOffered > 0 && userTokenBalances[GUILD][proposal.tributeToken] == 0) {\n require(totalGuildBankTokens < MAX_TOKEN_GUILDBANK_COUNT, 'cannot sponsor more tribute proposals for new tokens - guildbank is full');\n }\n\n // whitelist proposal\n if (proposal.flags[4]) {\n require(!tokenWhitelist[address(proposal.tributeToken)], \"cannot already have whitelisted the token\");\n require(!proposedToWhitelist[address(proposal.tributeToken)], 'already proposed to whitelist');\n require(approvedTokens.length < MAX_TOKEN_WHITELIST_COUNT, \"cannot sponsor more whitelist proposals\");\n proposedToWhitelist[address(proposal.tributeToken)] = true;\n\n // guild kick proposal\n } else if (proposal.flags[5]) {\n require(!proposedToKick[proposal.applicant], 'already proposed to kick');\n proposedToKick[proposal.applicant] = true;\n }\n\n // compute startingPeriod for proposal\n uint256 startingPeriod = max(\n getCurrentPeriod(),\n proposalQueue.length == 0 ? 0 : proposals[proposalQueue[proposalQueue.length.sub(1)]].startingPeriod\n ).add(1);\n\n proposal.startingPeriod = startingPeriod;\n\n address memberAddress = memberAddressByDelegateKey[msg.sender];\n proposal.sponsor = memberAddress;\n\n proposal.flags[0] = true; // sponsored\n\n // append proposal to the queue\n proposalQueue.push(proposalId);\n \n emit SponsorProposal(msg.sender, memberAddress, proposalId, proposalQueue.length.sub(1), startingPeriod);\n }\n\n // NOTE: In MolochV2 proposalIndex !== proposalId\n function submitVote(uint256 proposalIndex, uint8 uintVote) public nonReentrant onlyDelegate {\n address memberAddress = memberAddressByDelegateKey[msg.sender];\n Member storage member = members[memberAddress];\n\n require(proposalIndex < proposalQueue.length, \"proposal does not exist\");\n Proposal storage proposal = proposals[proposalQueue[proposalIndex]];\n\n require(uintVote < 3, \"must be less than 3\");\n Vote vote = Vote(uintVote);\n\n require(getCurrentPeriod() >= proposal.startingPeriod, \"voting period has not started\");\n require(!hasVotingPeriodExpired(proposal.startingPeriod), \"proposal voting period has expired\");\n require(proposal.votesByMember[memberAddress] == Vote.Null, \"member has already voted\");\n require(vote == Vote.Yes || vote == Vote.No, \"vote must be either Yes or No\");\n\n proposal.votesByMember[memberAddress] = vote;\n\n if (vote == Vote.Yes) {\n proposal.yesVotes = proposal.yesVotes.add(member.shares);\n\n // set highest index (latest) yes vote - must be processed for member to ragequit\n if (proposalIndex > member.highestIndexYesVote) {\n member.highestIndexYesVote = proposalIndex;\n }\n\n // set maximum of total shares encountered at a yes vote - used to bound dilution for yes voters\n if (totalShares.add(totalLoot) > proposal.maxTotalSharesAndLootAtYesVote) {\n proposal.maxTotalSharesAndLootAtYesVote = totalShares.add(totalLoot);\n }\n\n } else if (vote == Vote.No) {\n proposal.noVotes = proposal.noVotes.add(member.shares);\n }\n \n // NOTE: subgraph indexes by proposalId not proposalIndex since proposalIndex isn't set untill it's been sponsored but proposal is created on submission\n emit SubmitVote(proposalQueue[proposalIndex], proposalIndex, msg.sender, memberAddress, uintVote);\n }\n\n function processProposal(uint256 proposalIndex) public nonReentrant {\n _validateProposalForProcessing(proposalIndex);\n\n uint256 proposalId = proposalQueue[proposalIndex];\n Proposal storage proposal = proposals[proposalId];\n\n require(!proposal.flags[4] && !proposal.flags[5], \"must be a standard proposal\");\n\n proposal.flags[1] = true; // processed\n\n bool didPass = _didPass(proposalIndex);\n\n // Make the proposal fail if the new total number of shares and loot exceeds the limit\n if (totalShares.add(totalLoot).add(proposal.sharesRequested).add(proposal.lootRequested) > MAX_NUMBER_OF_SHARES_AND_LOOT) {\n didPass = false;\n }\n\n // Make the proposal fail if it is requesting more tokens as payment than the available guild bank balance\n if (proposal.paymentRequested > userTokenBalances[GUILD][proposal.paymentToken]) {\n didPass = false;\n }\n\n // Make the proposal fail if it would result in too many tokens with non-zero balance in guild bank\n if (proposal.tributeOffered > 0 && userTokenBalances[GUILD][proposal.tributeToken] == 0 && totalGuildBankTokens >= MAX_TOKEN_GUILDBANK_COUNT) {\n didPass = false;\n }\n\n // PROPOSAL PASSED\n if (didPass) {\n proposal.flags[2] = true; // didPass\n\n // if the applicant is already a member, add to their existing shares & loot\n if (members[proposal.applicant].exists) {\n members[proposal.applicant].shares = members[proposal.applicant].shares.add(proposal.sharesRequested);\n members[proposal.applicant].loot = members[proposal.applicant].loot.add(proposal.lootRequested);\n\n // the applicant is a new member, create a new record for them\n } else {\n // if the applicant address is already taken by a member's delegateKey, reset it to their member address\n if (members[memberAddressByDelegateKey[proposal.applicant]].exists) {\n address memberToOverride = memberAddressByDelegateKey[proposal.applicant];\n memberAddressByDelegateKey[memberToOverride] = memberToOverride;\n members[memberToOverride].delegateKey = memberToOverride;\n }\n\n // use applicant address as delegateKey by default\n members[proposal.applicant] = Member(proposal.applicant, proposal.sharesRequested, proposal.lootRequested, true, 0, 0);\n memberAddressByDelegateKey[proposal.applicant] = proposal.applicant;\n }\n\n // mint new shares & loot\n totalShares = totalShares.add(proposal.sharesRequested);\n totalLoot = totalLoot.add(proposal.lootRequested);\n\n // if the proposal tribute is the first tokens of its kind to make it into the guild bank, increment total guild bank tokens\n if (userTokenBalances[GUILD][proposal.tributeToken] == 0 && proposal.tributeOffered > 0) {\n totalGuildBankTokens += 1;\n }\n\n unsafeInternalTransfer(ESCROW, GUILD, proposal.tributeToken, proposal.tributeOffered);\n unsafeInternalTransfer(GUILD, proposal.applicant, proposal.paymentToken, proposal.paymentRequested);\n\n // if the proposal spends 100% of guild bank balance for a token, decrement total guild bank tokens\n if (userTokenBalances[GUILD][proposal.paymentToken] == 0 && proposal.paymentRequested > 0) {\n totalGuildBankTokens -= 1;\n }\n\n // PROPOSAL FAILED\n } else {\n // return all tokens to the proposer (not the applicant, because funds come from proposer)\n unsafeInternalTransfer(ESCROW, proposal.proposer, proposal.tributeToken, proposal.tributeOffered);\n }\n\n _returnDeposit(proposal.sponsor);\n\n emit ProcessProposal(proposalIndex, proposalId, didPass);\n }\n\n function processWhitelistProposal(uint256 proposalIndex) public nonReentrant {\n _validateProposalForProcessing(proposalIndex);\n\n uint256 proposalId = proposalQueue[proposalIndex];\n Proposal storage proposal = proposals[proposalId];\n\n require(proposal.flags[4], \"must be a whitelist proposal\");\n\n proposal.flags[1] = true; // processed\n\n bool didPass = _didPass(proposalIndex);\n\n if (approvedTokens.length >= MAX_TOKEN_WHITELIST_COUNT) {\n didPass = false;\n }\n\n if (didPass) {\n proposal.flags[2] = true; // didPass\n\n tokenWhitelist[address(proposal.tributeToken)] = true;\n approvedTokens.push(proposal.tributeToken);\n }\n\n proposedToWhitelist[address(proposal.tributeToken)] = false;\n\n _returnDeposit(proposal.sponsor);\n\n emit ProcessWhitelistProposal(proposalIndex, proposalId, didPass);\n }\n\n function processGuildKickProposal(uint256 proposalIndex) public nonReentrant {\n _validateProposalForProcessing(proposalIndex);\n\n uint256 proposalId = proposalQueue[proposalIndex];\n Proposal storage proposal = proposals[proposalId];\n\n require(proposal.flags[5], \"must be a guild kick proposal\");\n\n proposal.flags[1] = true; // processed\n\n bool didPass = _didPass(proposalIndex);\n\n if (didPass) {\n proposal.flags[2] = true; // didPass\n Member storage member = members[proposal.applicant];\n member.jailed = proposalIndex;\n\n // transfer shares to loot\n member.loot = member.loot.add(member.shares);\n totalShares = totalShares.sub(member.shares);\n totalLoot = totalLoot.add(member.shares);\n member.shares = 0; // revoke all shares\n }\n\n proposedToKick[proposal.applicant] = false;\n\n _returnDeposit(proposal.sponsor);\n\n emit ProcessGuildKickProposal(proposalIndex, proposalId, didPass);\n }\n\n function _didPass(uint256 proposalIndex) internal returns (bool didPass) {\n Proposal memory proposal = proposals[proposalQueue[proposalIndex]];\n\n didPass = proposal.yesVotes > proposal.noVotes;\n\n // Make the proposal fail if the dilutionBound is exceeded\n if ((totalShares.add(totalLoot)).mul(dilutionBound) < proposal.maxTotalSharesAndLootAtYesVote) {\n didPass = false;\n }\n\n // Make the proposal fail if the applicant is jailed\n // - for standard proposals, we don't want the applicant to get any shares/loot/payment\n // - for guild kick proposals, we should never be able to propose to kick a jailed member (or have two kick proposals active), so it doesn't matter\n if (members[proposal.applicant].jailed != 0) {\n didPass = false;\n }\n\n return didPass;\n }\n\n function _validateProposalForProcessing(uint256 proposalIndex) internal view {\n require(proposalIndex < proposalQueue.length, \"proposal does not exist\");\n Proposal memory proposal = proposals[proposalQueue[proposalIndex]];\n\n require(getCurrentPeriod() >= proposal.startingPeriod.add(votingPeriodLength).add(gracePeriodLength), \"proposal is not ready to be processed\");\n require(proposal.flags[1] == false, \"proposal has already been processed\");\n require(proposalIndex == 0 || proposals[proposalQueue[proposalIndex.sub(1)]].flags[1], \"previous proposal must be processed\");\n }\n\n function _returnDeposit(address sponsor) internal {\n unsafeInternalTransfer(ESCROW, msg.sender, depositToken, processingReward);\n unsafeInternalTransfer(ESCROW, sponsor, depositToken, proposalDeposit.sub(processingReward));\n }\n\n function ragequit(uint256 sharesToBurn, uint256 lootToBurn) public nonReentrant onlyMember {\n _ragequit(msg.sender, sharesToBurn, lootToBurn);\n }\n\n function _ragequit(address memberAddress, uint256 sharesToBurn, uint256 lootToBurn) internal {\n uint256 initialTotalSharesAndLoot = totalShares.add(totalLoot);\n\n Member storage member = members[memberAddress];\n\n require(member.shares >= sharesToBurn, \"insufficient shares\");\n require(member.loot >= lootToBurn, \"insufficient loot\");\n\n require(canRagequit(member.highestIndexYesVote), \"cannot ragequit until highest index proposal member voted YES on is processed\");\n\n uint256 sharesAndLootToBurn = sharesToBurn.add(lootToBurn);\n\n // burn shares and loot\n member.shares = member.shares.sub(sharesToBurn);\n member.loot = member.loot.sub(lootToBurn);\n totalShares = totalShares.sub(sharesToBurn);\n totalLoot = totalLoot.sub(lootToBurn);\n\n for (uint256 i = 0; i < approvedTokens.length; i++) {\n uint256 amountToRagequit = fairShare(userTokenBalances[GUILD][approvedTokens[i]], sharesAndLootToBurn, initialTotalSharesAndLoot);\n if (amountToRagequit > 0) { // gas optimization to allow a higher maximum token limit\n // deliberately not using safemath here to keep overflows from preventing the function execution (which would break ragekicks)\n // if a token overflows, it is because the supply was artificially inflated to oblivion, so we probably don't care about it anyways\n userTokenBalances[GUILD][approvedTokens[i]] -= amountToRagequit;\n userTokenBalances[memberAddress][approvedTokens[i]] += amountToRagequit;\n }\n }\n\n emit Ragequit(msg.sender, sharesToBurn, lootToBurn);\n }\n\n function ragekick(address memberToKick) public nonReentrant {\n Member storage member = members[memberToKick];\n\n require(member.jailed != 0, \"member must be in jail\");\n require(member.loot > 0, \"member must have some loot\"); // note - should be impossible for jailed member to have shares\n require(canRagequit(member.highestIndexYesVote), \"cannot ragequit until highest index proposal member voted YES on is processed\");\n\n _ragequit(memberToKick, 0, member.loot);\n }\n\n function withdrawBalance(address token, uint256 amount) public nonReentrant {\n _withdrawBalance(token, amount);\n }\n\n function withdrawBalances(address[] memory tokens, uint256[] memory amounts, bool max) public nonReentrant {\n require(tokens.length == amounts.length, \"tokens and amounts arrays must be matching lengths\");\n\n for (uint256 i=0; i < tokens.length; i++) {\n uint256 withdrawAmount = amounts[i];\n if (max) { // withdraw the maximum balance\n withdrawAmount = userTokenBalances[msg.sender][tokens[i]];\n }\n\n _withdrawBalance(tokens[i], withdrawAmount);\n }\n }\n \n function _withdrawBalance(address token, uint256 amount) internal {\n require(userTokenBalances[msg.sender][token] >= amount, \"insufficient balance\");\n unsafeSubtractFromBalance(msg.sender, token, amount);\n require(IERC20(token).transfer(msg.sender, amount), \"transfer failed\");\n emit Withdraw(msg.sender, token, amount);\n }\n\n function collectTokens(address token) public onlyDelegate nonReentrant {\n uint256 amountToCollect = IERC20(token).balanceOf(address(this)).sub(userTokenBalances[TOTAL][token]);\n // only collect if 1) there are tokens to collect 2) token is whitelisted 3) token has non-zero balance\n require(amountToCollect > 0, 'no tokens to collect');\n require(tokenWhitelist[token], 'token to collect must be whitelisted');\n require(userTokenBalances[GUILD][token] > 0, 'token to collect must have non-zero guild bank balance');\n \n unsafeAddToBalance(GUILD, token, amountToCollect);\n emit TokensCollected(token, amountToCollect);\n }\n\n // NOTE: requires that delegate key which sent the original proposal cancels, msg.sender == proposal.proposer\n function cancelProposal(uint256 proposalId) public nonReentrant {\n Proposal storage proposal = proposals[proposalId];\n require(!proposal.flags[0], \"proposal has already been sponsored\");\n require(!proposal.flags[3], \"proposal has already been cancelled\");\n require(msg.sender == proposal.proposer, \"solely the proposer can cancel\");\n\n proposal.flags[3] = true; // cancelled\n \n unsafeInternalTransfer(ESCROW, proposal.proposer, proposal.tributeToken, proposal.tributeOffered);\n emit CancelProposal(proposalId, msg.sender);\n }\n\n function updateDelegateKey(address newDelegateKey) public nonReentrant onlyShareholder {\n require(newDelegateKey != address(0), \"newDelegateKey cannot be 0\");\n\n // skip checks if member is setting the delegate key to their member address\n if (newDelegateKey != msg.sender) {\n require(!members[newDelegateKey].exists, \"cannot overwrite existing members\");\n require(!members[memberAddressByDelegateKey[newDelegateKey]].exists, \"cannot overwrite existing delegate keys\");\n }\n\n Member storage member = members[msg.sender];\n memberAddressByDelegateKey[member.delegateKey] = address(0);\n memberAddressByDelegateKey[newDelegateKey] = msg.sender;\n member.delegateKey = newDelegateKey;\n\n emit UpdateDelegateKey(msg.sender, newDelegateKey);\n }\n\n // can only ragequit if the latest proposal you voted YES on has been processed\n function canRagequit(uint256 highestIndexYesVote) public view returns (bool) {\n require(highestIndexYesVote < proposalQueue.length, \"proposal does not exist\");\n return proposals[proposalQueue[highestIndexYesVote]].flags[1];\n }\n\n function hasVotingPeriodExpired(uint256 startingPeriod) public view returns (bool) {\n return getCurrentPeriod() >= startingPeriod.add(votingPeriodLength);\n }\n\n /***************\n GETTER FUNCTIONS\n ***************/\n\n function max(uint256 x, uint256 y) internal pure returns (uint256) {\n return x >= y ? x : y;\n }\n\n function getCurrentPeriod() public view returns (uint256) {\n return now.sub(summoningTime).div(periodDuration);\n }\n\n function getProposalQueueLength() public view returns (uint256) {\n return proposalQueue.length;\n }\n\n function getProposalFlags(uint256 proposalId) public view returns (bool[6] memory) {\n return proposals[proposalId].flags;\n }\n\n function getUserTokenBalance(address user, address token) public view returns (uint256) {\n return userTokenBalances[user][token];\n }\n\n function getMemberProposalVote(address memberAddress, uint256 proposalIndex) public view returns (Vote) {\n require(members[memberAddress].exists, \"member does not exist\");\n require(proposalIndex < proposalQueue.length, \"proposal does not exist\");\n return proposals[proposalQueue[proposalIndex]].votesByMember[memberAddress];\n }\n\n function getTokenCount() public view returns (uint256) {\n return approvedTokens.length;\n }\n\n /***************\n HELPER FUNCTIONS\n ***************/\n function unsafeAddToBalance(address user, address token, uint256 amount) internal {\n userTokenBalances[user][token] += amount;\n userTokenBalances[TOTAL][token] += amount;\n }\n\n function unsafeSubtractFromBalance(address user, address token, uint256 amount) internal {\n userTokenBalances[user][token] -= amount;\n userTokenBalances[TOTAL][token] -= amount;\n }\n\n function unsafeInternalTransfer(address from, address to, address token, uint256 amount) internal {\n unsafeSubtractFromBalance(from, token, amount);\n unsafeAddToBalance(to, token, amount);\n }\n\n function fairShare(uint256 balance, uint256 shares, uint256 totalShares) internal pure returns (uint256) {\n require(totalShares != 0);\n\n if (balance == 0) { return 0; }\n\n uint256 prod = balance * shares;\n\n if (prod / balance == shares) { // no overflow in multiplication above?\n return prod / totalShares;\n }\n\n return (balance / totalShares) * shares;\n }\n}\n"
},
"contracts/ReentrancyGuard.sol": {
"content": "pragma solidity ^0.5.0;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * _Since v2.5.0:_ this module is now much more gas efficient, given net gas\n * metering changes introduced in the Istanbul hardfork.\n */\ncontract ReentrancyGuard {\n bool private _notEntered;\n\n constructor () internal {\n // Storing an initial non-zero value makes deployment a bit more\n // expensive, but in exchange the refund on every call to nonReentrant\n // will be lower in amount. Since refunds are capped to a percetange of\n // the total transaction's gas, it is best to keep them low in cases\n // like this one, to increase the likelihood of the full refund coming\n // into effect.\n _notEntered = true;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and make it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n // On the first call to nonReentrant, _notEntered will be true\n require(_notEntered, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _notEntered = false;\n\n _;\n\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _notEntered = true;\n }\n}\n"
},
"contracts/test-helpers/Submitter.sol": {
"content": "// helper for testing moloch.submitProposal return value\n\npragma solidity 0.5.3;\n\nimport \"../Moloch.sol\";\n\ncontract Submitter {\n\n event Submit(uint256 proposalId);\n\n Moloch public moloch; // moloch contract reference\n\n constructor(address molochAddress) public {\n moloch = Moloch(molochAddress);\n }\n\n function submitProposal(\n address applicant,\n uint256 sharesRequested,\n uint256 lootRequested,\n uint256 tributeOffered,\n address tributeToken,\n uint256 paymentRequested,\n address paymentToken,\n string memory details\n ) public {\n uint256 proposalId = moloch.submitProposal(\n applicant,\n sharesRequested,\n lootRequested,\n tributeOffered,\n tributeToken,\n paymentRequested,\n paymentToken,\n details\n );\n\n emit Submit(proposalId);\n }\n\n function submitWhitelistProposal(\n address tokenToWhitelist,\n string memory details\n ) public {\n uint256 proposalId = moloch.submitWhitelistProposal(\n tokenToWhitelist,\n details\n );\n\n emit Submit(proposalId);\n }\n\n function submitGuildKickProposal(\n address memberToKick,\n string memory details\n ) public {\n uint256 proposalId = moloch.submitGuildKickProposal(\n memberToKick,\n details\n );\n\n emit Submit(proposalId);\n }\n}\n"
},
"contracts/MolochSummoner.sol": {
"content": "pragma solidity 0.5.3;\n\nimport \"./Moloch.sol\";\n\ncontract MolochSummoner {\n\n Moloch private M;\n\n address[] public Molochs;\n\n event Summoned(address indexed M, address indexed _summoner);\n\n function summonMoloch(\n address _summoner,\n address[] memory _approvedTokens,\n uint256 _periodDuration,\n uint256 _votingPeriodLength,\n uint256 _gracePeriodLength,\n uint256 _proposalDeposit,\n uint256 _dilutionBound,\n uint256 _processingReward) public {\n\n M = new Moloch(\n _summoner,\n _approvedTokens,\n _periodDuration,\n _votingPeriodLength,\n _gracePeriodLength,\n _proposalDeposit,\n _dilutionBound,\n _processingReward);\n\n Molochs.push(address(M));\n\n emit Summoned(address(M), _summoner);\n\n }\n\n function getMolochCount() public view returns (uint256 MolochCount) {\n return Molochs.length;\n }\n}\n"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
],
"": [
"ast"
]
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment