Skip to content

Only join #780

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 7 commits into from
Aug 8, 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
114 changes: 15 additions & 99 deletions contracts/schemes/JoinAndQuit.sol → contracts/schemes/Join.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@ import "./CommonInterface.sol";
/**
* @title A scheme for join in a dao.
* - A member can be proposed to join in by sending a min amount of fee.
* - A member can ask to quite (RageQuit) a dao on any time.
* - A member can donate to a dao.
*/
contract JoinAndQuit is
contract Join is
VotingMachineCallbacks,
ProposalExecuteInterface,
CommonInterface {
using SafeMath for uint;
using SafeERC20 for IERC20;
using StringUtil for string;

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

event JoinInProposal(
address indexed _avatar,
Expand All @@ -34,18 +33,6 @@ contract JoinAndQuit is
address indexed _avatar
);

event RageQuit(
address indexed _avatar,
address indexed _rageQuitter,
uint256 indexed _refund
);

event Refund(
address indexed _avatar,
address indexed _beneficiary,
uint256 indexed _refund
);

event RedeemReputation(
address indexed _avatar,
bytes32 indexed _proposalId,
Expand All @@ -59,22 +46,15 @@ contract JoinAndQuit is
uint256 funding;
}

struct MemberFund {
MemeberState state;
bool rageQuit;
uint256 funding;
}

mapping(bytes32=>Proposal) public proposals;
mapping(address=>MemberFund) public fundings;
mapping(address=>MemberState) public membersState;

IERC20 public fundingToken;
uint256 public minFeeToJoin;
uint256 public memberReputation;
uint256 public fundingGoal;
uint256 public fundingGoalDeadline;
uint256 public totalDonation;
bool public rageQuitEnable;

/**
* @dev initialize
Expand All @@ -89,7 +69,6 @@ contract JoinAndQuit is
if this param is zero so the repution will be allocated proportional to the fee paid
* @param _fundingGoal the funding goal
* @param _fundingGoalDeadline the funding goal deadline
* @param _rageQuitEnable rageQuit enabling flag
*/
function initialize(
Avatar _avatar,
Expand All @@ -101,8 +80,7 @@ contract JoinAndQuit is
uint256 _minFeeToJoin,
uint256 _memberReputation,
uint256 _fundingGoal,
uint256 _fundingGoalDeadline,
bool _rageQuitEnable
uint256 _fundingGoalDeadline
)
external
{
Expand All @@ -112,7 +90,6 @@ contract JoinAndQuit is
memberReputation = _memberReputation;
fundingGoal = _fundingGoal;
fundingGoalDeadline = _fundingGoalDeadline;
rageQuitEnable = _rageQuitEnable;
}

/**
Expand All @@ -127,13 +104,12 @@ contract JoinAndQuit is
returns(bool) {
Proposal memory proposal = proposals[_proposalId];
require(proposal.proposedMember != address(0), "not a valid proposal");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe give a more specific error message here?

require(fundings[proposal.proposedMember].state == MemeberState.Candidate, "proposal already been executed");
require(membersState[proposal.proposedMember] == MemberState.Candidate, "member is not a cadidate");

bool success;
// Check if vote was successful:
if ((_decision == 1) && (avatar.nativeReputation().balanceOf(proposal.proposedMember) == 0)) {
fundings[proposal.proposedMember].state = MemeberState.Accepted;
fundings[proposal.proposedMember].funding = proposal.funding;
membersState[proposal.proposedMember] = MemberState.Accepted;
totalDonation = totalDonation.add(proposal.funding);
if (fundingToken == IERC20(0)) {
// solhint-disable-next-line
Expand All @@ -145,7 +121,7 @@ contract JoinAndQuit is
//this should be called/check after the transfer to the avatar.
setFundingGoalReachedFlag();
} else {
fundings[proposal.proposedMember].state = MemeberState.Rejected;
membersState[proposal.proposedMember] = MemberState.Rejected;
if (fundingToken == IERC20(0)) {
// solhint-disable-next-line
(success, ) = proposal.proposedMember.call{value:proposal.funding}("");
Expand Down Expand Up @@ -174,11 +150,11 @@ contract JoinAndQuit is
returns(bytes32)
{
address proposer = msg.sender;
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].state = MemeberState.Candidate;
require(membersState[proposer] != MemberState.Candidate, "proposer is already a candidate");
require(membersState[proposer] != MemberState.Accepted, "proposer is accepted and not redeemed yet");
require(avatar.nativeReputation().balanceOf(proposer) == 0, "proposer is already a member");
require(_feeAmount >= minFeeToJoin, "_feeAmount should be >= than the minFeeToJoin");
membersState[proposer] = MemberState.Candidate;
if (fundingToken == IERC20(0)) {
require(_feeAmount == msg.value, "ETH received should match the _feeAmount");
} else {
Expand Down Expand Up @@ -212,11 +188,11 @@ contract JoinAndQuit is
function redeemReputation(bytes32 _proposalId) public returns(uint256 reputation) {
Proposal memory proposal = proposals[_proposalId];
require(proposal.proposedMember != address(0), "no member to redeem");
require(!fundings[proposal.proposedMember].rageQuit, "member already rageQuit");
require(fundings[proposal.proposedMember].state == MemeberState.Accepted, "member not accepeted");
require(membersState[proposal.proposedMember] == MemberState.Accepted, "member not accepted");
//set proposal proposedMember to zero to prevent reentrancy attack.
proposals[_proposalId].proposedMember = address(0);
fundings[proposal.proposedMember].state = MemeberState.ReputationRedeemed;
proposals[_proposalId].proposedMember = address(0);
membersState[proposal.proposedMember] = MemberState.ReputationRedeemed;
if (memberReputation == 0) {
reputation = proposal.funding;
} else {
Expand All @@ -228,49 +204,6 @@ contract JoinAndQuit is
emit RedeemReputation(address(avatar), _proposalId, proposal.proposedMember, reputation);
}

/**
* @dev refund refund donator if the the funding goal did not reached till the funding goal deadline.
* @return refundAmount the refund amount
*/
function refund() public returns(uint256 refundAmount) {
// solhint-disable-next-line not-rely-on-time
require(now > fundingGoalDeadline, "can refund only after fundingGoalDeadline");
require(
(avatar.db(FUNDED_BEFORE_DEADLINE_KEY).hashCompareWithLengthCheck(FUNDED_BEFORE_DEADLINE_VALUE) == false),
"can refund only if funding goal not reached");
require(fundings[msg.sender].funding > 0, "no funds to refund");
refundAmount = fundings[msg.sender].funding;
fundings[msg.sender].funding = 0;
sendToBeneficiary(refundAmount, msg.sender);
emit Refund(address(avatar), msg.sender, refundAmount);
}

/**
* @dev rageQuit quit from the dao.
* can be done on any time
* REFUND = USER_DONATION * CURRENT_DAO_BALANCE / TOTAL_DONATIONS
* @return refundAmount the refund amount
*/
function rageQuit() public returns(uint256 refundAmount) {
require(rageQuitEnable, "RageQuit disabled");
require(fundings[msg.sender].funding > 0, "no fund to RageQuit");
uint256 userDonation = fundings[msg.sender].funding;
fundings[msg.sender].funding = 0;
fundings[msg.sender].rageQuit = true;
if (fundingToken == IERC20(0)) {
refundAmount = userDonation.mul(address(avatar.vault()).balance).div(totalDonation);
} else {
refundAmount = userDonation.mul(fundingToken.balanceOf(address(avatar))).div(totalDonation);
}
totalDonation = totalDonation.sub(userDonation);
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);
}

/**
* @dev setFundingGoalReachedFlag check if funding goal reached.
*/
Expand All @@ -294,21 +227,4 @@ contract JoinAndQuit is
}
}

/**
* @dev sendToBeneficiary send amount of eth or token to beneficiary
* @param _amount the amount to send
* @param _beneficiary the beneficiary
*/
function sendToBeneficiary(uint256 _amount, address payable _beneficiary) private {
if (fundingToken == IERC20(0)) {
require(
Controller(
avatar.owner()).sendEther(_amount, _beneficiary), "send ether failed");
} else {
require(
Controller(
avatar.owner()).externalTokenTransfer(fundingToken, _beneficiary, _amount), "send token failed");
}
}

}
22 changes: 11 additions & 11 deletions contracts/utils/Redeemer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.6.12;
import "../schemes/ContributionReward.sol";
import "../schemes/ContributionRewardExt.sol";
import "../schemes/FundingRequest.sol";
import "../schemes/JoinAndQuit.sol";
import "../schemes/Join.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol";


Expand Down Expand Up @@ -125,11 +125,11 @@ contract Redeemer {
* @dev helper to redeem rewards for a proposal
* It calls execute on the proposal if it is not yet executed.
* It tries to redeem reputation and stake from the GenesisProtocol.
* It tries to redeem proposal rewards from the JoinAndQuit scheme.
* It tries to redeem proposal rewards from the Join scheme.
* This function does not emit events.
* A client should listen to GenesisProtocol and JoinAndQuit redemption events
* A client should listen to GenesisProtocol and Join redemption events
* to monitor redemption operations.
* @param _joinAndQuit joinAndQuit scheme
* @param _join join scheme
* @param _genesisProtocol genesisProtocol
* @param _proposalId the ID of the voting in the voting machine
* @param _beneficiary beneficiary
Expand All @@ -145,9 +145,9 @@ contract Redeemer {
* @return winningVote
* 1 - executed or closed and the winning vote is YES
* 2 - executed or closed and the winning vote is NO
* @return joinAndQuitReputationReward Reputation - from JoinAndQuit reputation reward
* @return joinReputationReward Reputation - from Join reputation reward
*/
function redeemJoinAndQuit(JoinAndQuit _joinAndQuit,
function redeemJoin(Join _join,
GenesisProtocol _genesisProtocol,
bytes32 _proposalId,
address _beneficiary)
Expand All @@ -156,14 +156,14 @@ contract Redeemer {
uint[2] memory gpDaoBountyReward,
bool executed,
uint256 winningVote,
uint256 joinAndQuitReputationReward
uint256 joinReputationReward
)
{
bool callJoinAndQuit;
(gpRewards, gpDaoBountyReward, executed, winningVote, callJoinAndQuit) =
bool callJoin;
(gpRewards, gpDaoBountyReward, executed, winningVote, callJoin) =
genesisProtocolRedeem(_genesisProtocol, _proposalId, _beneficiary);
if (callJoinAndQuit) {
joinAndQuitReputationReward = _joinAndQuit.redeemReputation(_proposalId);
if (callJoin) {
joinReputationReward = _join.redeemReputation(_proposalId);
}
}

Expand Down
Loading