Skip to content

Joinandquit fix2 #779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions contracts/schemes/JoinAndQuit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ contract JoinAndQuit is
using SafeERC20 for IERC20;
using StringUtil for string;

enum MemeberState { None, Candidate, Accepted, Rejected, ReputationRedeemed }

event JoinInProposal(
address indexed _avatar,
bytes32 indexed _proposalId,
Expand Down Expand Up @@ -55,12 +57,10 @@ contract JoinAndQuit is
struct Proposal {
address proposedMember;
uint256 funding;
bool executed;
bool accepted;
}

struct MemberFund {
bool candidate;
MemeberState state;
bool rageQuit;
uint256 funding;
}
Expand Down Expand Up @@ -127,13 +127,12 @@ contract JoinAndQuit is
returns(bool) {
Proposal memory proposal = proposals[_proposalId];
require(proposal.proposedMember != address(0), "not a valid proposal");
require(proposal.executed == false, "proposal already been executed");
proposals[_proposalId].executed = true;
require(fundings[proposal.proposedMember].state == MemeberState.Candidate, "proposal already been executed");

bool success;
// Check if vote was successful:
if ((_decision == 1) && (avatar.nativeReputation().balanceOf(proposal.proposedMember) == 0)) {
proposals[_proposalId].accepted = true;
fundings[proposal.proposedMember].state = MemeberState.Accepted;
fundings[proposal.proposedMember].funding = proposal.funding;
totalDonation = totalDonation.add(proposal.funding);
if (fundingToken == IERC20(0)) {
Expand All @@ -146,15 +145,16 @@ contract JoinAndQuit is
//this should be called/check after the transfer to the avatar.
setFundingGoalReachedFlag();
} else {
fundings[proposal.proposedMember].state = MemeberState.Rejected;
if (fundingToken == IERC20(0)) {
// solhint-disable-next-line
(success, ) = proposal.proposedMember.call{value:proposal.funding}("");
require(success, "sendEther to avatar failed");
require(success, "sendEther back to candidate failed");
} else {
fundingToken.safeTransfer(proposal.proposedMember, proposal.funding);
}
}
fundings[proposal.proposedMember].candidate = false;

emit ProposalExecuted(address(avatar), _proposalId, _decision);
return true;
}
Expand All @@ -174,22 +174,21 @@ contract JoinAndQuit is
returns(bytes32)
{
address proposer = msg.sender;
require(!fundings[proposer].candidate, "already a candidate");
require(fundings[proposer].state != MemeberState.Candidate, "already a candidate");
require(fundings[proposer].state != MemeberState.Accepted, "accepted and not redeemed yet");
require(avatar.nativeReputation().balanceOf(proposer) == 0, "already a member");
require(_feeAmount >= minFeeToJoin, "_feeAmount should be >= then the minFeeToJoin");
fundings[proposer].candidate = true;
fundings[proposer].state = MemeberState.Candidate;
if (fundingToken == IERC20(0)) {
require(_feeAmount == msg.value, "ETH received shoul match the _feeAmount");
require(_feeAmount == msg.value, "ETH received should match the _feeAmount");
} else {
fundingToken.safeTransferFrom(proposer, address(this), _feeAmount);
}
bytes32 proposalId = votingMachine.propose(2, voteParamsHash, proposer, address(avatar));

Proposal memory proposal = Proposal({
executed: false,
proposedMember: proposer,
funding : _feeAmount,
accepted: false
funding : _feeAmount
});
proposals[proposalId] = proposal;

Expand All @@ -211,22 +210,22 @@ contract JoinAndQuit is
* @return reputation the redeemed reputation.
*/
function redeemReputation(bytes32 _proposalId) public returns(uint256 reputation) {
Proposal memory _proposal = proposals[_proposalId];
Proposal storage proposal = proposals[_proposalId];
Proposal memory proposal = proposals[_proposalId];
require(proposal.proposedMember != address(0), "no member to redeem");
require(!fundings[proposal.proposedMember].rageQuit, "member already rageQuit");
require(proposal.accepted == true, " proposal not accepted");
require(fundings[proposal.proposedMember].state == MemeberState.Accepted, "member not accepeted");
//set proposal proposedMember to zero to prevent reentrancy attack.
proposal.proposedMember = address(0);
proposals[_proposalId].proposedMember = address(0);
fundings[proposal.proposedMember].state = MemeberState.ReputationRedeemed;
if (memberReputation == 0) {
reputation = _proposal.funding;
reputation = proposal.funding;
} else {
reputation = memberReputation;
}
require(
Controller(
avatar.owner()).mintReputation(reputation, _proposal.proposedMember), "failed to mint reputation");
emit RedeemReputation(address(avatar), _proposalId, _proposal.proposedMember, reputation);
avatar.owner()).mintReputation(reputation, proposal.proposedMember), "failed to mint reputation");
emit RedeemReputation(address(avatar), _proposalId, proposal.proposedMember, reputation);
}

/**
Expand Down Expand Up @@ -264,11 +263,11 @@ contract JoinAndQuit is
refundAmount = userDonation.mul(fundingToken.balanceOf(address(avatar))).div(totalDonation);
}
totalDonation = totalDonation.sub(userDonation);
sendToBeneficiary(refundAmount, msg.sender);
uint256 msgSenderReputation = avatar.nativeReputation().balanceOf(msg.sender);
require(
Controller(
avatar.owner()).burnReputation(msgSenderReputation, msg.sender));
sendToBeneficiary(refundAmount, msg.sender);
emit RageQuit(address(avatar), msg.sender, refundAmount);
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/schemes/ReputationTokenTrade.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.6.10;
pragma solidity ^0.6.12;
// SPDX-License-Identifier: GPL-3.0

import "../votingMachines/VotingMachineCallbacks.sol";
Expand Down
25 changes: 17 additions & 8 deletions test/joinandquit.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@ contract('JoinAndQuit', accounts => {
//Vote with reputation to trigger execution
var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1);
await testSetup.joinAndQuitParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]});
var proposal = await testSetup.joinAndQuit.proposals(proposalId);
assert.equal(proposal.executed,true);
var funding = await testSetup.joinAndQuit.fundings(accounts[3]);
assert.equal(funding.state,2);
assert.equal(await testSetup.standardTokenMock.balanceOf(testSetup.org.avatar.address),testSetup.minFeeToJoin);
assert.equal(await testSetup.standardTokenMock.balanceOf(testSetup.joinAndQuit.address),0);
assert.equal((await testSetup.joinAndQuit.fundings(accounts[3])).funding,testSetup.minFeeToJoin);
Expand All @@ -291,8 +291,8 @@ contract('JoinAndQuit', accounts => {
//Vote with reputation to trigger execution
var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1);
await testSetup.joinAndQuitParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]});
var proposal = await testSetup.joinAndQuit.proposals(proposalId);
assert.equal(proposal.executed,true);
var funding = await testSetup.joinAndQuit.fundings(accounts[3]);
assert.equal(funding.state,2);
assert.equal(await avatarBalance(testSetup),testSetup.minFeeToJoin);
assert.equal(await web3.eth.getBalance(testSetup.joinAndQuit.address),0);
assert.equal((await testSetup.joinAndQuit.fundings(accounts[3])).funding,testSetup.minFeeToJoin);
Expand All @@ -309,8 +309,8 @@ contract('JoinAndQuit', accounts => {
//Vote with reputation to trigger execution
var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1);
await testSetup.joinAndQuitParams.votingMachine.absoluteVote.vote(proposalId,2,0,helpers.NULL_ADDRESS,{from:accounts[2]});
var proposal = await testSetup.joinAndQuit.proposals(proposalId);
assert.equal(proposal.executed,true);
var funding = await testSetup.joinAndQuit.fundings(accounts[3]);
assert.equal(funding.state,3);
assert.equal(await testSetup.standardTokenMock.balanceOf(testSetup.org.avatar.address),0);
assert.equal(await testSetup.standardTokenMock.balanceOf(testSetup.joinAndQuit.address),0);
assert.equal((await testSetup.joinAndQuit.fundings(accounts[3])).funding,0);
Expand All @@ -329,8 +329,8 @@ contract('JoinAndQuit', accounts => {
var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1);
var balanceBefore = await web3.eth.getBalance(accounts[3]);
await testSetup.joinAndQuitParams.votingMachine.absoluteVote.vote(proposalId,2,0,helpers.NULL_ADDRESS,{from:accounts[2]});
var proposal = await testSetup.joinAndQuit.proposals(proposalId);
assert.equal(proposal.executed,true);
var funding = await testSetup.joinAndQuit.fundings(accounts[3]);
assert.equal(funding.state,3);
assert.equal(await avatarBalance(testSetup),0);
assert.equal(await web3.eth.getBalance(testSetup.joinAndQuit.address),0);
assert.equal((await testSetup.joinAndQuit.fundings(accounts[3])).funding,0);
Expand All @@ -352,6 +352,15 @@ contract('JoinAndQuit', accounts => {
//Vote with reputation to trigger execution
var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1);
await testSetup.joinAndQuitParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]});
try {
await testSetup.joinAndQuit.proposeToJoin(
"description-hash",
testSetup.minFeeToJoin,
{from:accounts[3]});
assert(false, 'accepted candidate which not redeemed yet cannt be proposed again');
} catch (ex) {
helpers.assertVMException(ex);
}
tx = await testSetup.joinAndQuit.redeemReputation(proposalId);
assert.equal(tx.logs[0].event, "RedeemReputation");
assert.equal(tx.logs[0].args._amount, testSetup.memberReputation);
Expand Down