diff --git a/truffle-contracts/contracts/GitFundedGrant.sol b/truffle-contracts/contracts/GitFundedGrant.sol index 3f44938..2dc7b55 100644 --- a/truffle-contracts/contracts/GitFundedGrant.sol +++ b/truffle-contracts/contracts/GitFundedGrant.sol @@ -69,7 +69,7 @@ contract GitFundedGrant { */ enum Role { ADMIN, - MEMBER, + SHAREHOLDER, CONTRIBUTOR } @@ -146,7 +146,7 @@ contract GitFundedGrant { Expense memory expense = Expense(_title, _amount, 0, msg.sender, ExpenseStatus.PENDING,0); expenses.push(expense); - dao.submitProposal(applicant,sharesRequested,lootRequested,tributeOffered,tributeToken,paymentRequested,paymentToken,details); + dao.submitProposal(sharesRequested,tributeOffered,tributeToken,paymentRequested,paymentToken,details); } @@ -169,9 +169,6 @@ contract GitFundedGrant { expenses[expenseIndex].recipient.transfer(amount); } - function sponsorExpense (uint expenseIndex) public { - expenses[expenseIndex].proposalIndex = dao.sponsorProposal(expenseIndex); - } // TODO: Merge this logic with the acceptExpense by overloading the function diff --git a/truffle-contracts/contracts/GitFundedGrantFactory.sol b/truffle-contracts/contracts/GitFundedGrantFactory.sol index d53fd80..18b6cd8 100644 --- a/truffle-contracts/contracts/GitFundedGrantFactory.sol +++ b/truffle-contracts/contracts/GitFundedGrantFactory.sol @@ -40,8 +40,6 @@ contract GitFundedGrantFactory { bountyAddress, tokenAddress ); - // bytes32 label=keccak256(abi.encode(title)); - // bytes32 label1=keccak256("testRepo title"); esr.createName(label,msg.sender); projects[msg.sender].push(address(project)); diff --git a/truffle-contracts/contracts/dao/DAOFactory.sol b/truffle-contracts/contracts/dao/DAOFactory.sol index abdb2af..0939309 100644 --- a/truffle-contracts/contracts/dao/DAOFactory.sol +++ b/truffle-contracts/contracts/dao/DAOFactory.sol @@ -20,7 +20,7 @@ contract DAOFactory { function newDAO(address _admin) public returns (address) { - Governance dao = new Governance(_admin, [tokenAddress], 17280, 35, 35, 35, 70, 10, 3, 1); + Governance dao = new Governance(_admin, [tokenAddress], 17280, 35); daos[msg.sender].push(address(dao)); daoCount += 1; diff --git a/truffle-contracts/contracts/dao/Governance.sol b/truffle-contracts/contracts/dao/Governance.sol index 0cd5546..9e4ed61 100644 --- a/truffle-contracts/contracts/dao/Governance.sol +++ b/truffle-contracts/contracts/dao/Governance.sol @@ -15,37 +15,23 @@ contract Governance is ReentrancyGuard { uint256 public periodDuration; // default = 17280 = 4.8 hours in seconds (5 periods per day) uint256 public votingPeriodLength; // default = 35 periods (7 days) - uint256 public gracePeriodLength; // default = 35 periods (7 days) - uint256 public emergencyProcessingWait; // default = 35 periods (7 days) - //uint256 public bailoutWait; // default = 70 periods (14 days) - 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 - IERC20 public depositToken; // deposit token contract reference; default = wETH GuildBank public guildBank; // guild bank contract reference // HARD-CODED LIMITS // These numbers are quite arbitrary; they are small enough to avoid overflows when doing calculations // with periods or shares, yet big enough to not limit reasonable use cases. uint256 constant MAX_VOTING_PERIOD_LENGTH = 10**18; // maximum length of voting period - uint256 constant MAX_GRACE_PERIOD_LENGTH = 10**18; // maximum length of grace period - // uint256 constant MAX_BAILOUT_WAIT = 10**18; // maximum # periods after a jailed member can be ragekicked before they must be bailed out instead - // uint256 constant MAX_DILUTION_BOUND = 10**18; // maximum dilution bound - // uint256 constant MAX_NUMBER_OF_SHARES_AND_LOOT = 10**18; // maximum number of shares that can be minted uint256 constant MAX_NUMBER_OF_SHARES = 10**18; // maximum number of shares that can be minted // *************** // EVENTS // *************** - event SubmitProposal(uint256 proposalIndex, address indexed delegateKey, address indexed memberAddress, address indexed applicant, uint256 sharesRequested, uint256 lootRequested, uint256 tributeOffered, address tributeToken, uint256 paymentRequested, address paymentToken); - event SponsorProposal(address indexed delegateKey, address indexed memberAddress, uint256 proposalIndex, uint256 proposalQueueIndex, uint256 startingPeriod); - event SubmitVote(uint256 indexed proposalIndex, address indexed delegateKey, address indexed memberAddress, uint8 uintVote); - event ProcessProposal(uint256 indexed proposalIndex, uint256 indexed proposalId, bool didPass); - event Ragequit(address indexed memberAddress, uint256 sharesToBurn, uint256 lootToBurn); + event SubmitProposal(uint256 proposalId, address indexed memberAddress, uint256 sharesRequested, uint256 tributeOffered, address tributeToken, uint256 paymentRequested, address paymentToken); + event SubmitVote(uint256 indexed proposalId, address indexed memberAddress, uint8 uintVote); + event ProcessProposal(uint256 indexed proposalId, bool didPass); event CancelProposal(uint256 indexed proposalIndex, address applicantAddress); - event UpdateDelegateKey(address indexed memberAddress, address newDelegateKey); event SummonComplete(address indexed admin, uint256 shares); // ******************* @@ -53,11 +39,10 @@ contract Governance is ReentrancyGuard { // ******************* uint256 public proposalCount = 0; // total proposals submitted uint256 public totalShares = 0; // total shares across all members - //uint256 public totalLoot = 0; // total loot across all members uint256 public result; + uint256 public memberCount=0; - bool public emergencyWarning = false; // true if emergency processing has ever been triggered - uint256 public lastEmergencyProposalIndex = 0; // index of the last proposal which triggered emergency processing + // uint256 public lastEmergencyProposalIndex = 0; // index of the last proposal which triggered emergency processing enum Vote { Null, // default value, counted as abstention @@ -65,53 +50,42 @@ contract Governance is ReentrancyGuard { No } + enum Role { + Admin, + Shareholder, + Contributor + } + 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 - //uint256 loot; // the loot amount available to this member (combined with shares on ragequit) bool exists; // always true once a member has been created - uint256 highestIndexYesVote; // highest proposal index # on which the member voted YES - //uint256 jailed; // set to proposalIndex of a passing guild kick proposal for this member, prevents voting on and sponsoring proposals + Role role;//defines current role of the member } struct Proposal { - 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) address proposer; // the account that submitted the proposal (can be non-member) - address sponsor; // the member that sponsored the proposal (moving it into the queue) uint256 sharesRequested; // the # of shares the applicant is requesting - uint256 lootRequested; // the amount of loot the applicant is requesting uint256 tributeOffered; // amount of tokens offered as tribute - IERC20 tributeToken; // tribute token contract reference + IERC20 tributeToken; // tribute token contract reference uint256 paymentRequested; // amount of tokens requested as payment - IERC20 paymentToken; // payment token contract reference + IERC20 paymentToken; // payment token contract reference 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[6] flags; // [sponsored, processed, didPass, cancelled, whitelist, guildkick] + bool[3] flags; // [processed, didPass, cancelled] string details; // proposal details - could be IPFS hash, plaintext, or JSON - uint256 maxTotalSharesAndLootAtYesVote; // the maximum # of total shares encountered at a yes vote on this proposal mapping(address => Vote) votesByMember; // the votes on this proposal by each member } - // mapping(address => bool) public tokenWhitelist; IERC20[] public approvedTokens; - // mapping(address => bool) public proposedToWhitelist; - // mapping(address => bool) public proposedToKick; - mapping(address => Member) public members; - mapping(address => address) public memberAddressByDelegateKey; mapping(uint256 => Proposal) public proposals; - uint256[] public proposalQueue; - - // modifier onlyMember { - // require(members[msg.sender].shares > 0 || members[msg.sender].loot > 0, "not a member"); - // _; - // } modifier onlyMember { - require(members[msg.sender].shares > 0 , "not a member"); + require(members[msg.sender].exists == true , "not a member"); _; } @@ -120,44 +94,22 @@ contract Governance is ReentrancyGuard { _; } - modifier onlyDelegate { - require(members[memberAddressByDelegateKey[msg.sender]].shares > 0, "not a delegate"); - _; - } - constructor( address _admin, address[1] memory _approvedTokens, uint256 _periodDuration, - uint256 _votingPeriodLength, - uint256 _gracePeriodLength, - uint256 _emergencyProcessingWait, - uint256 _bailoutWait, - uint256 _proposalDeposit, - uint256 _dilutionBound, - uint256 _processingReward + uint256 _votingPeriodLength ) public { require(_admin != address(0), "admin cannot be 0"); require(_periodDuration > 0, "_periodDuration cannot be 0"); require(_votingPeriodLength > 0, "_votingPeriodLength cannot be 0"); require(_votingPeriodLength <= MAX_VOTING_PERIOD_LENGTH, "_votingPeriodLength exceeds limit"); - require(_gracePeriodLength <= MAX_GRACE_PERIOD_LENGTH, "_gracePeriodLength exceeds limit"); - require(_emergencyProcessingWait > 0, "_emergencyProcessingWait cannot be 0"); - // require(_bailoutWait > _emergencyProcessingWait, "_bailoutWait must be greater than _emergencyProcessingWait"); - // require(_bailoutWait <= MAX_BAILOUT_WAIT, "_bailoutWait exceeds limit"); - // require(_dilutionBound > 0, "_dilutionBound cannot be 0"); - // require(_dilutionBound <= MAX_DILUTION_BOUND, "_dilutionBound exceeds limit"); require(_approvedTokens.length > 0, "need at least one approved token"); - // require(_proposalDeposit >= _processingReward, "_proposalDeposit cannot be smaller than _processingReward"); admin = _admin; - depositToken = IERC20(_approvedTokens[0]); - for (uint256 i = 0; i < _approvedTokens.length; i++) { - require(_approvedTokens[i] != address(0), "_approvedToken cannot be 0"); - // require(!tokenWhitelist[_approvedTokens[i]], "duplicate approved token"); - //tokenWhitelist[_approvedTokens[i]] = true; + require(_approvedTokens[i] != address(0), "_approvedToken cannot be 0"); approvedTokens.push(IERC20(_approvedTokens[i])); } @@ -165,18 +117,11 @@ contract Governance is ReentrancyGuard { periodDuration = _periodDuration; votingPeriodLength = _votingPeriodLength; - gracePeriodLength = _gracePeriodLength; - emergencyProcessingWait = _emergencyProcessingWait; - // bailoutWait = _bailoutWait; - proposalDeposit = _proposalDeposit; - // dilutionBound = _dilutionBound; - // processingReward = _processingReward; summoningTime = now; - //members[admin] = Member(admin, 1, 0, true, 0, 0); - members[admin] = Member(admin, 1, true, 0); - memberAddressByDelegateKey[admin] = admin; + members[admin] = Member(admin, 1, true, Role.Admin); + memberCount++; totalShares = 1; emit SummonComplete(admin, 1); @@ -185,74 +130,49 @@ contract Governance is ReentrancyGuard { /***************** PROPOSAL FUNCTIONS *****************/ + function addContributor() public nonReentrant { + members[msg.sender]=Member(msg.sender,0,true,Role.Contributor); + memberCount++; + } + + function donate (uint256 tributeOffered, address tributeToken) public nonReentrant { + require(IERC20(tributeToken).transferFrom(msg.sender, address(guildBank), tributeOffered), "tribute token transfer failed"); + + } + + function submitProposal( - address applicant, uint256 sharesRequested, - uint256 lootRequested, uint256 tributeOffered, address tributeToken, uint256 paymentRequested, address paymentToken, string memory details - ) public nonReentrant returns (uint256 proposalId) { - //require(sharesRequested.add(lootRequested) <= MAX_NUMBER_OF_SHARES_AND_LOOT, "too many shares requested"); + ) public nonReentrant onlyMember returns (uint256 proposalId) { + require(sharesRequested <= MAX_NUMBER_OF_SHARES, "too many shares requested"); - // require(tokenWhitelist[tributeToken], "tributeToken is not whitelisted"); - // require(tokenWhitelist[paymentToken], "payment is not whitelisted"); - require(applicant != address(0), "applicant cannot be 0"); - // require(members[applicant].jailed == 0, "proposal applicant must not be jailed"); // collect tribute from proposer and store it in the Moloch until the proposal is processed - //require(IERC20(tributeToken).transferFrom(msg.sender, address(this), tributeOffered), "tribute token transfer failed"); + // require(IERC20(tributeToken).transferFrom(msg.sender, address(this), tributeOffered), "tribute token transfer failed"); - bool[6] memory flags; // [sponsored, processed, didPass, cancelled, whitelist, guildkick] + bool[3] memory flags; // [processed, didPass, cancelled] - _submitProposal(applicant, sharesRequested, lootRequested, tributeOffered, tributeToken, paymentRequested, paymentToken, details, flags); + _submitProposal(sharesRequested, tributeOffered, tributeToken, paymentRequested, paymentToken, details, flags); return proposalCount - 1; // return proposalId - contracts calling submit might want it } - // function submitWhitelistProposal(address tokenToWhitelist, string memory details) public nonReentrant returns (uint256 proposalId) { - // require(tokenToWhitelist != address(0), "must provide token address"); - // require(!tokenWhitelist[tokenToWhitelist], "cannot already have whitelisted the token"); - - // bool[6] memory flags; // [sponsored, processed, didPass, cancelled, whitelist, guildkick] - // flags[4] = true; - - // _submitProposal(address(0), 0, 0, 0, tokenToWhitelist, 0, address(0), details, flags); - // return proposalCount - 1; - // } - - // function submitGuildKickProposal(address memberToKick, string memory details) public nonReentrant returns (uint256 proposalId) { - // Member memory member = members[memberToKick]; - - // require(member.shares > 0 || member.loot > 0, "member must have at least one share or one loot"); - // require(memberToKick != admin, "the admin may not be kicked"); - // require(members[memberToKick].jailed == 0, "member must not already be jailed"); - - // bool[6] memory flags; // [sponsored, processed, didPass, cancelled, whitelist, guildkick] - // flags[5] = true; - - // _submitProposal(memberToKick, 0, 0, 0, address(0), 0, address(0), details, flags); - // return proposalCount - 1; - // } - function _submitProposal( - address applicant, uint256 sharesRequested, - uint256 lootRequested, uint256 tributeOffered, address tributeToken, uint256 paymentRequested, address paymentToken, string memory details, - bool[6] memory flags + bool[3] memory flags ) internal { Proposal memory proposal = Proposal({ - applicant : applicant, proposer : msg.sender, - sponsor : address(0), sharesRequested : sharesRequested, - lootRequested : lootRequested, tributeOffered : tributeOffered, tributeToken : IERC20(tributeToken), paymentRequested : paymentRequested, @@ -261,396 +181,137 @@ contract Governance is ReentrancyGuard { yesVotes : 0, noVotes : 0, flags : flags, - details : details, - maxTotalSharesAndLootAtYesVote : 0 + details : details }); proposals[proposalCount] = proposal; - address memberAddress = memberAddressByDelegateKey[msg.sender]; - emit SubmitProposal(proposalCount, msg.sender, memberAddress, applicant, sharesRequested, lootRequested, tributeOffered, tributeToken, paymentRequested, paymentToken); + emit SubmitProposal(proposalCount, msg.sender, sharesRequested, tributeOffered, tributeToken, paymentRequested, paymentToken); proposalCount += 1; } - function sponsorProposal(uint256 proposalId) public nonReentrant onlyDelegate returns (uint256) { - // collect proposal deposit from sponsor and store it in the Moloch until the proposal is processed - //require(depositToken.transferFrom(msg.sender, address(this), proposalDeposit), "proposal deposit token transfer failed"); + function submitVote(uint256 proposalId, uint8 uintVote) public nonReentrant onlyShareholder { + Member storage member = members[msg.sender]; + require (member.role == Role.Shareholder || member.role == Role.Admin, "Contributors cannot vote"); + + require(proposalId < proposalCount, "proposal does not exist"); Proposal storage proposal = proposals[proposalId]; - require(proposal.proposer != address(0), 'proposal must have been proposed'); - require(!proposal.flags[0], "proposal has already been sponsored"); - require(!proposal.flags[3], "proposal has been cancelled"); - // require(members[proposal.applicant].jailed == 0, "proposal applicant must not be jailed"); - - // // whitelist proposal - // if (proposal.flags[4]) { - // require(!tokenWhitelist[address(proposal.tributeToken)], "cannot already have whitelisted the token"); - // require(!proposedToWhitelist[address(proposal.tributeToken)], 'already proposed to whitelist'); - // proposedToWhitelist[address(proposal.tributeToken)] = true; - - // // guild kick proposal - // } else if (proposal.flags[5]) { - // require(!proposedToKick[proposal.applicant], 'already proposed to kick'); - // proposedToKick[proposal.applicant] = true; - // } - - // compute startingPeriod for proposal - uint256 startingPeriod = max( - getCurrentPeriod(), - proposalQueue.length == 0 ? 0 : proposals[proposalQueue[proposalQueue.length.sub(1)]].startingPeriod - ).add(1); - - proposal.startingPeriod = startingPeriod; - - address memberAddress = memberAddressByDelegateKey[msg.sender]; - proposal.sponsor = memberAddress; - - proposal.flags[0] = true; - - // append proposal to the queue - proposalQueue.push(proposalId); - emit SponsorProposal(msg.sender, memberAddress, proposalId, proposalQueue.length.sub(1), startingPeriod); - return proposalQueue.length.sub(1); - } - - function submitVote(uint256 proposalIndex, uint8 uintVote) public nonReentrant onlyDelegate { - address memberAddress = memberAddressByDelegateKey[msg.sender]; - Member storage member = members[memberAddress]; - - require(proposalIndex < proposalQueue.length, "proposal does not exist"); - Proposal storage proposal = proposals[proposalQueue[proposalIndex]]; + require (proposal.flags[2]==false,"Proposal has been cancelled"); + require(uintVote < 3, "must be less than 3"); Vote vote = Vote(uintVote); - //require(getCurrentPeriod() >= proposal.startingPeriod, "voting period has not started"); + require(getCurrentPeriod() >= proposal.startingPeriod, "voting period has not started"); require(!hasVotingPeriodExpired(proposal.startingPeriod), "proposal voting period has expired"); - require(proposal.votesByMember[memberAddress] == Vote.Null, "member has already voted"); + require(proposal.votesByMember[msg.sender] == Vote.Null, "member has already voted"); require(vote == Vote.Yes || vote == Vote.No, "vote must be either Yes or No"); - proposal.votesByMember[memberAddress] = vote; + proposal.votesByMember[msg.sender] = vote; if (vote == Vote.Yes) { proposal.yesVotes = proposal.yesVotes.add(member.shares); - // set highest index (latest) yes vote - must be processed for member to ragequit - if (proposalIndex > member.highestIndexYesVote) { - member.highestIndexYesVote = proposalIndex; - } - - // set maximum of total shares encountered at a yes vote - used to bound dilution for yes voters - // if (totalShares.add(totalLoot) > proposal.maxTotalSharesAndLootAtYesVote) { - // proposal.maxTotalSharesAndLootAtYesVote = totalShares.add(totalLoot); - // } - } else if (vote == Vote.No) { proposal.noVotes = proposal.noVotes.add(member.shares); } - emit SubmitVote(proposalIndex, msg.sender, memberAddress, uintVote); + emit SubmitVote(proposalId, msg.sender, uintVote); } - function processProposal(uint256 proposalIndex) public nonReentrant { - _validateProposalForProcessing(proposalIndex); + function processProposal(uint256 proposalId) public nonReentrant { + _validateProposalForProcessing(proposalId); - uint256 proposalId = proposalQueue[proposalIndex]; Proposal storage proposal = proposals[proposalId]; - //require(!proposal.flags[4] && !proposal.flags[5], "must be a standard proposal"); + proposal.flags[0] = true; // processed - proposal.flags[1] = true; // processed + bool didPass = _didPass(proposalId); - (bool didPass, bool emergencyProcessing) = _didPass(proposalIndex); - - // Make the proposal fail if the new total number of shares and loot exceeds the limit - // if (totalShares.add(totalLoot).add(proposal.sharesRequested).add(proposal.lootRequested) > MAX_NUMBER_OF_SHARES_AND_LOOT) { - // didPass = false; - // } if (totalShares.add(proposal.sharesRequested) > MAX_NUMBER_OF_SHARES) { didPass = false; } - // Make the proposal fail if it is requesting more tokens as payment than the available guild bank balance - // if (!emergencyProcessing && proposal.paymentToken != IERC20(0) && proposal.paymentRequested > proposal.paymentToken.balanceOf(address(guildBank))) { - // didPass = false; - // } + //Make the proposal fail if it is requesting more tokens as payment than the available guild bank balance + if (proposal.paymentToken != IERC20(0) && proposal.paymentRequested > proposal.paymentToken.balanceOf(address(guildBank))) { + didPass = false; + } // PROPOSAL PASSED if (didPass) { - result=1; - proposal.flags[2] = true; // didPass - - // if the applicant is already a member, add to their existing shares & loot - if (members[proposal.applicant].exists) { - members[proposal.applicant].shares = members[proposal.applicant].shares.add(proposal.sharesRequested); - //members[proposal.applicant].loot = members[proposal.applicant].loot.add(proposal.lootRequested); - - // the applicant is a new member, create a new record for them - } else { - // if the applicant address is already taken by a member's delegateKey, reset it to their member address - if (members[memberAddressByDelegateKey[proposal.applicant]].exists) { - address memberToOverride = memberAddressByDelegateKey[proposal.applicant]; - memberAddressByDelegateKey[memberToOverride] = memberToOverride; - members[memberToOverride].delegateKey = memberToOverride; - } - - // use applicant address as delegateKey by default - members[proposal.applicant] = Member(proposal.applicant, proposal.sharesRequested, true, 0); - //members[proposal.applicant] = Member(proposal.applicant, proposal.sharesRequested, proposal.lootRequested, true, 0, 0); - memberAddressByDelegateKey[proposal.applicant] = proposal.applicant; - } + + result=1; + proposal.flags[1] = true; // didPass + + members[proposal.proposer].shares = members[proposal.proposer].shares.add(proposal.sharesRequested); + if(proposal.sharesRequested > 0) + { + members[proposal.proposer].role =Role.Shareholder; + } // mint new shares & loot totalShares = totalShares.add(proposal.sharesRequested); - // totalLoot = totalLoot.add(proposal.lootRequested); - // require( - // proposal.tributeToken.transfer(address(guildBank), proposal.tributeOffered), - // "token transfer to guild bank failed" - // ); - // require( - // guildBank.withdrawToken(proposal.paymentToken, proposal.applicant, proposal.paymentRequested), - // "token payment to applicant failed" - // ); + require( + proposal.tributeToken.transfer(address(guildBank), proposal.tributeOffered), + "token transfer to guild bank failed" + ); + + require( + guildBank.withdrawToken(proposal.paymentToken, proposal.proposer, proposal.paymentRequested), + "token payment to applicant failed" + ); // PROPOSAL FAILED } else { + result=2; - // return all tokens to the applicant (skip if emergency processing) - if (!emergencyProcessing) { - // require( - // proposal.tributeToken.transfer(proposal.proposer, proposal.tributeOffered), - // "failing vote token transfer failed" - // ); - } + // return all tokens to the applicant + require( + proposal.tributeToken.transfer(proposal.proposer, proposal.tributeOffered),"failing vote token transfer failed"); + } - //_returnDeposit(proposal.sponsor); - - emit ProcessProposal(proposalIndex, proposalId, didPass); + emit ProcessProposal(proposalId, didPass); } -// function processWhitelistProposal(uint256 proposalIndex) public nonReentrant { -// _validateProposalForProcessing(proposalIndex); -// -// uint256 proposalId = proposalQueue[proposalIndex]; -// Proposal storage proposal = proposals[proposalId]; -// -// require(proposal.flags[4], "must be a whitelist proposal"); -// -// proposal.flags[1] = true; // processed -// -// (bool didPass,) = _didPass(proposalIndex); -// -// if (didPass) { -// proposal.flags[2] = true; // didPass -// -// tokenWhitelist[address(proposal.tributeToken)] = true; -// approvedTokens.push(proposal.tributeToken); -// } -// -// proposedToWhitelist[address(proposal.tributeToken)] = false; -// -// _returnDeposit(proposal.sponsor); -// -// emit ProcessProposal(proposalIndex, proposalId, didPass); -// } - -// function processGuildKickProposal(uint256 proposalIndex) public nonReentrant { -// _validateProposalForProcessing(proposalIndex); -// -// uint256 proposalId = proposalQueue[proposalIndex]; -// Proposal storage proposal = proposals[proposalId]; -// -// require(proposal.flags[5], "must be a guild kick proposal"); -// -// proposal.flags[1] = true; // processed -// -// (bool didPass,) = _didPass(proposalIndex); -// -// if (didPass) { -// proposal.flags[2] = true; // didPass -// Member storage member = members[proposal.applicant]; -// member.jailed = proposalIndex; -// -// // transfer shares to loot -// member.loot = member.loot.add(member.shares); -// totalShares = totalShares.sub(member.shares); -// totalLoot = totalLoot.add(member.shares); -// member.shares = 0; // revoke all shares -// } -// -// proposedToKick[proposal.applicant] = false; -// -// _returnDeposit(proposal.sponsor); -// -// emit ProcessProposal(proposalIndex, proposalId, didPass); -// } - - function _didPass(uint256 proposalIndex) internal returns (bool didPass, bool emergencyProcessing) { - Proposal memory proposal = proposals[proposalQueue[proposalIndex]]; + + function _didPass(uint256 proposalId) internal returns (bool didPass) { + Proposal memory proposal = proposals[proposalId]; didPass = proposal.yesVotes > proposal.noVotes; - // Make the proposal fail (and skip returning tribute) if emergencyProcessingWait is exceeded - emergencyProcessing = false; - // if (getCurrentPeriod() >= proposal.startingPeriod.add(votingPeriodLength).add(gracePeriodLength).add(emergencyProcessingWait)) { - // emergencyWarning = true; - // lastEmergencyProposalIndex = proposalIndex; - // emergencyProcessing = true; - // didPass = false; - // } - - // // Make the proposal fail if it was past its grace period during the last emergency processing and it is not a guild kick proposal - // if (emergencyWarning) { - // if (proposal.startingPeriod <= proposals[proposalQueue[lastEmergencyProposalIndex]].startingPeriod.add(emergencyProcessingWait) && !proposal.flags[5]) { - // didPass = false; - // } - // } - - // // Make the proposal fail if the dilutionBound is exceeded - // if ((totalShares.add(totalLoot)).mul(dilutionBound) < proposal.maxTotalSharesAndLootAtYesVote) { - // didPass = false; - // } - - // // Make the proposal fail if the applicant is jailed - // // - for standard proposals, we don't want the applicant to get any shares/loot/payment - // // - 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 - // if (members[proposal.applicant].jailed != 0) { - // didPass = false; - // } - - return (didPass, emergencyProcessing); + return didPass; } - function _validateProposalForProcessing(uint256 proposalIndex) internal view { - require(proposalIndex < proposalQueue.length, "proposal does not exist"); - Proposal memory proposal = proposals[proposalQueue[proposalIndex]]; + function _validateProposalForProcessing(uint256 proposalId) internal view { + require(proposalId < proposalCount, "proposal does not exist"); + Proposal memory proposal = proposals[proposalId]; - //require(getCurrentPeriod() >= proposal.startingPeriod.add(votingPeriodLength).add(gracePeriodLength), "proposal is not ready to be processed"); - require(proposal.flags[1] == false, "proposal has already been processed"); - require(proposalIndex == 0 || proposals[proposalQueue[proposalIndex.sub(1)]].flags[1], "previous proposal must be processed"); + require(getCurrentPeriod() >= proposal.startingPeriod.add(votingPeriodLength), "proposal is not ready to be processed"); + require(proposal.flags[0] == false, "proposal has already been processed"); + require (proposal.flags[2]==false,"Proposal has been cancelled"); + require(proposalId == 0 || proposals[proposalId.sub(1)].flags[0], "previous proposal must be processed"); } - // function _returnDeposit(address sponsor) internal { - // require( - // depositToken.transfer(msg.sender, processingReward), - // "failed to send processing reward to msg.sender" - // ); - - // require( - // depositToken.transfer(sponsor, proposalDeposit.sub(processingReward)), - // "failed to return proposal deposit to sponsor" - // ); - // } - - // function ragequit(uint256 sharesToBurn, uint256 lootToBurn) public nonReentrant onlyMember { - // _ragequit(msg.sender, sharesToBurn, lootToBurn, approvedTokens); - // } - - // function safeRagequit(uint256 sharesToBurn, uint256 lootToBurn, IERC20[] memory tokenList) public nonReentrant onlyMember { - // // all tokens in tokenList must be in the tokenWhitelist - // for (uint256 i=0; i < tokenList.length; i++) { - // //require(tokenWhitelist[address(tokenList[i])], "token must be whitelisted"); - - // if (i > 0) { - // require(tokenList[i] > tokenList[i - 1], "token list must be unique and in ascending order"); - // } - // } - - // _ragequit(msg.sender, sharesToBurn, lootToBurn, tokenList); - // } - - - // function _ragequit(address memberAddress, uint256 sharesToBurn, uint256 lootToBurn, IERC20[] memory tokenList) internal { - // //uint256 initialTotalSharesAndLoot = totalShares.add(totalLoot); - // uint256 initialTotalSharesAndLoot = totalShares.add(0); - - // Member storage member = members[memberAddress]; - - // require(member.shares >= sharesToBurn, "insufficient shares"); - // //require(member.loot >= lootToBurn, "insufficient loot"); - - // require(canRagequit(member.highestIndexYesVote), "cannot ragequit until highest index proposal member voted YES on is processed"); - - // //uint256 sharesAndLootToBurn = sharesToBurn.add(lootToBurn); - - // // burn shares and loot - // member.shares = member.shares.sub(sharesToBurn); - // //member.loot = member.loot.sub(lootToBurn); - // totalShares = totalShares.sub(sharesToBurn); - // //totalLoot = totalLoot.sub(lootToBurn); - - // // instruct guildBank to transfer fair share of tokens to the ragequitter - // // require( - // // guildBank.withdraw(memberAddress, sharesAndLootToBurn, initialTotalSharesAndLoot, tokenList), - // // "withdrawal of tokens from guildBank failed" - // // ); - // require( - // guildBank.withdraw(memberAddress, sharesToBurn, initialTotalSharesAndLoot, tokenList), - // "withdrawal of tokens from guildBank failed" - // ); - - // emit Ragequit(msg.sender, sharesToBurn, lootToBurn); - // } - -// function ragekick(address memberToKick) public nonReentrant { -// Member storage member = members[memberToKick]; -// -// require(member.jailed != 0, "member must be in jail"); -// require(member.loot > 0, "member must have some loot"); // note - should be impossible for jailed member to have shares -// require(canRagequit(member.highestIndexYesVote), "cannot ragequit until highest index proposal member voted YES on is processed"); -// require(!canBailout(memberToKick), "bailoutWait has passed, member must be bailed out"); -// -// _ragequit(memberToKick, 0, member.loot, approvedTokens); -// } -// -// function bailout(address memberToBail) public nonReentrant { -// Member storage member = members[memberToBail]; -// -// require(member.jailed != 0, "member must be in jail"); -// require(member.loot > 0, "member must have some loot"); // note - should be impossible for jailed member to have shares -// require(canBailout(memberToBail), "cannot bailout yet"); -// -// members[admin].loot = members[admin].loot.add(member.loot); -// member.loot = 0; -// } - - // function cancelProposal(uint256 proposalId) public nonReentrant { - // Proposal storage proposal = proposals[proposalId]; - // require(!proposal.flags[0], "proposal has already been sponsored"); - // require(!proposal.flags[3], "proposal has already been cancelled"); - // require(msg.sender == proposal.proposer, "solely the proposer can cancel"); - - // proposal.flags[3] = true; // cancelled - - // require( - // proposal.tributeToken.transfer(proposal.proposer, proposal.tributeOffered), - // "failed to return tribute to proposer" - // ); - - // emit CancelProposal(proposalId, msg.sender); - // } - - // function updateDelegateKey(address newDelegateKey) public nonReentrant onlyShareholder { - // require(newDelegateKey != address(0), "newDelegateKey cannot be 0"); - - // // skip checks if member is setting the delegate key to theisr member address - // if (newDelegateKey != msg.sender) { - // require(!members[newDelegateKey].exists, "cannot overwrite existing members"); - // require(!members[memberAddressByDelegateKey[newDelegateKey]].exists, "cannot overwrite existing delegate keys"); - // } - - // Member storage member = members[msg.sender]; - // memberAddressByDelegateKey[member.delegateKey] = address(0); - // memberAddressByDelegateKey[newDelegateKey] = msg.sender; - // member.delegateKey = newDelegateKey; - - // emit UpdateDelegateKey(msg.sender, newDelegateKey); - // } + function cancelProposal(uint256 proposalId) public nonReentrant { + Proposal storage proposal = proposals[proposalId]; + require(!proposal.flags[2], "proposal has already been cancelled"); + require(msg.sender == proposal.proposer, "solely the proposer can cancel"); + + proposal.flags[2] = true; // cancelled + + // require( + // proposal.tributeToken.transfer(proposal.proposer, proposal.tributeOffered), + // "failed to return tribute to proposer" + // ); + + emit CancelProposal(proposalId, msg.sender); + } /*************** GETTER FUNCTIONS @@ -663,41 +324,18 @@ contract Governance is ReentrancyGuard { return now.sub(summoningTime).div(periodDuration); } - function getProposalQueueLength() public view returns (uint256) { - return proposalQueue.length; - } - - function getProposalFlags(uint256 proposalId) public view returns (bool[6] memory) { + function getProposalFlags(uint256 proposalId) public view returns (bool[3] memory) { return proposals[proposalId].flags; } - // can only ragequit if the latest proposal you voted YES on has been processed - function canRagequit(uint256 highestIndexYesVote) public view returns (bool) { - require(highestIndexYesVote < proposalQueue.length, "proposal does not exist"); - return proposals[proposalQueue[highestIndexYesVote]].flags[1]; - } - -// function canBailout(address memberToBail) public view returns (bool) { -// Member memory member = members[memberToBail]; -// -// // get the starting period of the proposal to start the bailout wait from -// // - either the guild kick proposal or the member's highest index yes vote -// uint256 bailoutWaitStartingPeriod = member.highestIndexYesVote > member.jailed -// ? proposals[proposalQueue[member.highestIndexYesVote]].startingPeriod -// : proposals[proposalQueue[member.jailed]].startingPeriod; -// -// // bailout wait starts after proposal grace period ends -// return getCurrentPeriod() >= bailoutWaitStartingPeriod.add(votingPeriodLength).add(gracePeriodLength).add(bailoutWait); -// } -// function hasVotingPeriodExpired(uint256 startingPeriod) public view returns (bool) { return getCurrentPeriod() >= startingPeriod.add(votingPeriodLength); } - function getMemberProposalVote(address memberAddress, uint256 proposalIndex) public view returns (Vote) { + function getMemberProposalVote(address memberAddress, uint256 proposalId) public view returns (Vote) { require(members[memberAddress].exists, "member does not exist"); - require(proposalIndex < proposalQueue.length, "proposal does not exist"); - return proposals[proposalQueue[proposalIndex]].votesByMember[memberAddress]; + require(proposalId < proposalCount, "proposal does not exist"); + return proposals[proposalId].votesByMember[memberAddress]; } function getProposal () public view returns(uint256){ return proposalCount; @@ -705,4 +343,8 @@ contract Governance is ReentrancyGuard { function getResult () public view returns(uint256){ return result;//checks proposal passed or not } + function getMembers () public view returns(uint256) { + return memberCount; + } + } diff --git a/truffle-contracts/test/governance/dao.test.js b/truffle-contracts/test/governance/dao.test.js index 6e9565f..023e941 100644 --- a/truffle-contracts/test/governance/dao.test.js +++ b/truffle-contracts/test/governance/dao.test.js @@ -6,14 +6,14 @@ const keccak256 = require('js-sha3').keccak_256; const ENSSubdomainRegistrar = artifacts.require('./ens/ENSSubdomainRegistrar.sol'); const DAOFactory = artifacts.require('./dao/DAOFactory.sol') -const Moloch = artifacts.require('./dao/Moloch'); +const Moloch = artifacts.require('./dao/Governance'); const GuildBank = artifacts.require('./dao/GuildBank'); const Token = artifacts.require('./dao/Token'); const Submitter = artifacts.require('./dao/Submitter'); const deploymentConfig = { - 'PERIOD_DURATION_IN_SECONDS': 17280, - 'VOTING_DURATON_IN_PERIODS': 35, + 'PERIOD_DURATION_IN_SECONDS': 5, + 'VOTING_DURATON_IN_PERIODS': 2, 'GRACE_DURATON_IN_PERIODS': 35, 'EMERGENCY_PROCESSING_WAIT_IN_PERIODS': 70, 'BAILOUT_WAIT_IN_PERIODS': 75, @@ -44,6 +44,10 @@ async function forceMine () { return ethereum.send('evm_mine', []) } +async function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + contract('DAOContract', (accounts) => { @@ -79,13 +83,7 @@ contract('DAOContract', (accounts) => { summoner, [tokenAlpha.address], deploymentConfig.PERIOD_DURATION_IN_SECONDS, - deploymentConfig.VOTING_DURATON_IN_PERIODS, - deploymentConfig.GRACE_DURATON_IN_PERIODS, - deploymentConfig.EMERGENCY_PROCESSING_WAIT_IN_PERIODS, - deploymentConfig.BAILOUT_WAIT_IN_PERIODS, - deploymentConfig.PROPOSAL_DEPOSIT, - deploymentConfig.DILUTION_BOUND, - deploymentConfig.PROCESSING_REWARD, + deploymentConfig.VOTING_DURATON_IN_PERIODS ); contract = await GitFundedGrantFactory.deployed(); @@ -98,6 +96,8 @@ contract('DAOContract', (accounts) => { const proposalCount = await moloch.proposalCount(); + + }); beforeEach(async () => { @@ -171,37 +171,89 @@ contract('DAOContract', (accounts) => { // console.log(await projectInstance.getProposalQueueLength()); // //console.log(await projectInstance.proposalCount()); // }); + it('Member Count', async () => { + let bef = await daoInstance.getMembers(); + await daoInstance.addContributor({from: accounts[1]}); + await daoInstance.addContributor({from: accounts[2]}); + await daoInstance.addContributor({from: accounts[3]}); + let aft = await daoInstance.getMembers(); + assert.equal(parseInt(bef),1); + assert.equal(parseInt(aft),4); + }); + it('check submit proposal', async () => { let bef = await daoInstance.getProposal(); - await daoInstance.submitProposal(accounts[1],10,20,40,tokenAlpha.address,20,tokenAlpha.address,"112"); - await daoInstance.submitProposal(accounts[1],10,20,40,tokenAlpha.address,20,tokenAlpha.address,"112"); + await daoInstance.addContributor({from: accounts[1]}); + await daoInstance.addContributor({from: accounts[2]}); + await daoInstance.addContributor({from: accounts[3]}); + + await daoInstance.submitProposal(10,40,tokenAlpha.address,20,tokenAlpha.address,"112",{from: accounts[1]}); + await daoInstance.submitProposal(15,20,tokenAlpha.address,25,tokenAlpha.address,"122",{from: accounts[2]}); + await daoInstance.submitProposal(15,20,tokenAlpha.address,25,tokenAlpha.address,"122",{from: accounts[3]}); + let aft = await daoInstance.getProposal(); - assert.equal(parseInt(bef),parseInt(aft)-2); + assert.equal(parseInt(bef),parseInt(aft)-3); }); - it('check sponsor proposal', async () => { + + // it('check vote and process proposal', async () => { + // let bef=await daoInstance.getProposal(); + // await daoInstance.addContributor({from: accounts[1]}); + // await daoInstance.addContributor({from: accounts[2]}); + // let id=await daoInstance.submitProposal(10,40,tokenAlpha.address,20,tokenAlpha.address,"112",{from: accounts[1]}); + // let aft=await daoInstance.getProposal(); + // //console.log(id); + // await daoInstance.submitVote(0,2,{from: accounts[0]}); + // await sleep(13000); + // // sleep(13000).then(() => { + // // daoInstance.processProposal(0); + + // // }); + + // await daoInstance.processProposal(0); + // let res=await daoInstance.getResult(); + // assert.equal(parseInt(res),2); + // assert.equal(parseInt(bef),parseInt(aft)-1); + // }); + + it('check vote', async () => { + let bef=await daoInstance.getProposal(); - await daoInstance.submitProposal(accounts[1],10,20,40,tokenAlpha.address,20,tokenAlpha.address,"112"); - await daoInstance.sponsorProposal(bef); + await daoInstance.addContributor({from: accounts[1]}); + await daoInstance.addContributor({from: accounts[2]}); + + let id=await daoInstance.submitProposal(10,40,tokenAlpha.address,20,tokenAlpha.address,"112",{from: accounts[1]}); let aft=await daoInstance.getProposal(); - let ajk=await daoInstance.getProposalQueueLength(); + + await sleep(13000); + + await daoInstance.submitVote(0,1,{from: accounts[0]}); + //await daoInstance.submitVote(0,1,{from: accounts[1]}); + + let vot=await daoInstance.getMemberProposalVote(accounts[0],0); + let vot1=await daoInstance.getMemberProposalVote(accounts[1],0); + assert.equal(parseInt(bef),parseInt(aft)-1); - assert.equal(parseInt(ajk),1); }); - it('check vote and process proposal', async () => { - let bef=await daoInstance.getProposal(); - let bjk=await daoInstance.getProposalQueueLength(); - await daoInstance.submitProposal(accounts[1],10,20,40,tokenAlpha.address,20,tokenAlpha.address,"112"); - await daoInstance.sponsorProposal(bef); - let aft=await daoInstance.getProposal(); - let ajk=await daoInstance.getProposalQueueLength(); - await daoInstance.submitVote(0,2); - await daoInstance.processProposal(0); - let res=await daoInstance.getResult(); - assert.equal(parseInt(res),2); - assert.equal(parseInt(bef),parseInt(aft)-1); - assert.equal(parseInt(ajk),parseInt(bjk)+1); + + it('check cancel proposal', async () => { + let bef = await daoInstance.getProposal(); + await daoInstance.addContributor({from: accounts[1]}); + + await daoInstance.submitProposal(10,40,tokenAlpha.address,20,tokenAlpha.address,"112",{from: accounts[1]}); + await daoInstance.submitProposal(15,20,tokenAlpha.address,25,tokenAlpha.address,"122",{from: accounts[2]}); + + let aft = await daoInstance.getProposal(); + + await daoInstance.cancelProposal(parseInt(aft-2),{from: accounts[1]}); + + //await daoInstance.submitVote(parseInt(aft-2),1,{from: accounts[0]}); + await daoInstance.submitVote(parseInt(aft-1),1,{from: accounts[0]}); + + assert.equal(parseInt(bef),parseInt(aft)-2); }); + + }); });