Skip to content

add refund #748

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 3 commits into from
May 9, 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
57 changes: 46 additions & 11 deletions contracts/schemes/JoinAndQuit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ contract JoinAndQuit is
uint256 indexed _refund
);

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

event RedeemReputation(
address indexed _avatar,
bytes32 indexed _proposalId,
Expand Down Expand Up @@ -216,35 +222,47 @@ 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 refund the refund amount
* @return refundAmount the refund amount
*/
function rageQuit() public returns(uint256 refund) {
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)) {
refund = userDonation.mul(address(avatar.vault()).balance).div(totalDonation);
require(
Controller(
avatar.owner()).sendEther(refund, msg.sender), "send ether failed");
refundAmount = userDonation.mul(address(avatar.vault()).balance).div(totalDonation);
} else {
refund = userDonation.mul(fundingToken.balanceOf(address(avatar))).div(totalDonation);
require(
Controller(
avatar.owner()).externalTokenTransfer(fundingToken, msg.sender, refund), "send token failed");
refundAmount = userDonation.mul(fundingToken.balanceOf(address(avatar))).div(totalDonation);
}
sendToBeneficiary(refundAmount, msg.sender);
uint256 msgSenderReputation = avatar.nativeReputation().balanceOf(msg.sender);
require(
Controller(
avatar.owner()).burnReputation(msgSenderReputation, msg.sender));
totalDonation = totalDonation.sub(userDonation);
emit RageQuit(address(avatar), msg.sender, refund);
emit RageQuit(address(avatar), msg.sender, refundAmount);
}

/**
Expand All @@ -270,4 +288,21 @@ 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");
}
}

}
86 changes: 86 additions & 0 deletions test/joinandquit.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,4 +642,90 @@ contract('JoinAndQuit', accounts => {
}
});

it("refund", async function() {
var testSetup = await setup(accounts);
await testSetup.standardTokenMock.approve(testSetup.joinAndQuit.address,testSetup.minFeeToJoin,{from:accounts[3]});
var donatorBalance = await testSetup.standardTokenMock.balanceOf(accounts[3]);
var tx = await testSetup.joinAndQuit.proposeToJoin(
"description-hash",
testSetup.minFeeToJoin,
{from:accounts[3]});

//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]});
assert.equal(await testSetup.standardTokenMock.balanceOf(testSetup.org.avatar.address),testSetup.minFeeToJoin);
assert.equal((await testSetup.joinAndQuit.fundings(accounts[3])).funding,testSetup.minFeeToJoin);
try {
await testSetup.joinAndQuit.refund({from:accounts[3]});
assert(false, 'cannot refund before deadline');
} catch (ex) {
helpers.assertVMException(ex);
}
await helpers.increaseTime(testSetup.fundingGoalDeadline);
tx = await testSetup.joinAndQuit.refund({from:accounts[3]});
assert.equal(tx.logs.length, 1);
assert.equal(tx.logs[0].event, "Refund");
assert.equal(tx.logs[0].args._avatar, testSetup.org.avatar.address);
assert.equal(tx.logs[0].args._beneficiary, accounts[3]);
assert.equal(tx.logs[0].args._refund, testSetup.minFeeToJoin);
assert.equal((await testSetup.standardTokenMock.balanceOf(accounts[3])).toString(),donatorBalance.toString());
});

it("refund - cannot if funding goal reached.", async function() {
var testSetup = await setup(accounts);
await testSetup.standardTokenMock.approve(testSetup.joinAndQuit.address,testSetup.fundingGoal+1,{from:accounts[3]});
var tx = await testSetup.joinAndQuit.proposeToJoin(
"description-hash",
testSetup.fundingGoal+1,
{from:accounts[3]});

//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]});

await helpers.increaseTime(testSetup.fundingGoalDeadline);
try {
await testSetup.joinAndQuit.refund({from:accounts[3]});
assert(false, 'cannot if funding goal reached');
} catch (ex) {
helpers.assertVMException(ex);
}
});

it("refund with eth", async function() {
var testSetup = await setup(accounts,true);
var tx = await testSetup.joinAndQuit.proposeToJoin(
"description-hash",
testSetup.minFeeToJoin,
{from:accounts[3],value:testSetup.minFeeToJoin});

//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]});
assert.equal((await testSetup.joinAndQuit.fundings(accounts[3])).funding,testSetup.minFeeToJoin);
try {
await testSetup.joinAndQuit.refund({from:accounts[3]});
assert(false, 'cannot refund before deadline');
} catch (ex) {
helpers.assertVMException(ex);
}
await helpers.increaseTime(testSetup.fundingGoalDeadline);
var balanceBefore = await avatarBalance(testSetup);
tx = await testSetup.joinAndQuit.refund({from:accounts[3]});
assert.equal(tx.logs.length, 1);
assert.equal(tx.logs[0].event, "Refund");
assert.equal(tx.logs[0].args._avatar, testSetup.org.avatar.address);
assert.equal(tx.logs[0].args._beneficiary, accounts[3]);
assert.equal(tx.logs[0].args._refund, testSetup.minFeeToJoin);
assert.equal(await avatarBalance(testSetup),balanceBefore - testSetup.minFeeToJoin);
try {
await testSetup.joinAndQuit.refund({from:accounts[3]});
assert(false, 'cannot refund twice');
} catch (ex) {
helpers.assertVMException(ex);
}
});


});