Unverified Commit 4eb4d711 by Francisco Giordano Committed by GitHub

Allow non-beneficiaries to trigger release of their funds (#1275)

* add address argument to PullPayments#withdrawPayments

* add argument to SplitPayment#claim

* add address argument to PostDeliveryCrowdsale#withdrawTokens

* add address argument to RefundableCrowdsale#claimRefund

* rename SplitPayment#claim to SplitPayment#release
parent 2aa5dd26
...@@ -16,13 +16,14 @@ contract PostDeliveryCrowdsale is TimedCrowdsale { ...@@ -16,13 +16,14 @@ contract PostDeliveryCrowdsale is TimedCrowdsale {
/** /**
* @dev Withdraw tokens only after crowdsale ends. * @dev Withdraw tokens only after crowdsale ends.
* @param _beneficiary Whose tokens will be withdrawn.
*/ */
function withdrawTokens() public { function withdrawTokens(address _beneficiary) public {
require(hasClosed()); require(hasClosed());
uint256 amount = balances[msg.sender]; uint256 amount = balances[_beneficiary];
require(amount > 0); require(amount > 0);
balances[msg.sender] = 0; balances[_beneficiary] = 0;
_deliverTokens(msg.sender, amount); _deliverTokens(_beneficiary, amount);
} }
/** /**
......
...@@ -32,12 +32,13 @@ contract RefundableCrowdsale is FinalizableCrowdsale { ...@@ -32,12 +32,13 @@ contract RefundableCrowdsale is FinalizableCrowdsale {
/** /**
* @dev Investors can claim refunds here if crowdsale is unsuccessful * @dev Investors can claim refunds here if crowdsale is unsuccessful
* @param _beneficiary Whose refund will be claimed.
*/ */
function claimRefund() public { function claimRefund(address _beneficiary) public {
require(isFinalized); require(isFinalized);
require(!goalReached()); require(!goalReached());
escrow_.withdraw(msg.sender); escrow_.withdraw(_beneficiary);
} }
/** /**
......
...@@ -16,11 +16,11 @@ contract PullPayment { ...@@ -16,11 +16,11 @@ contract PullPayment {
} }
/** /**
* @dev Withdraw accumulated balance, called by payee. * @dev Withdraw accumulated balance.
* @param _payee Whose balance will be withdrawn.
*/ */
function withdrawPayments() public { function withdrawPayments(address _payee) public {
address payee = msg.sender; escrow.withdraw(_payee);
escrow.withdraw(payee);
} }
/** /**
......
...@@ -5,8 +5,8 @@ import "../math/SafeMath.sol"; ...@@ -5,8 +5,8 @@ import "../math/SafeMath.sol";
/** /**
* @title SplitPayment * @title SplitPayment
* @dev Base contract that supports multiple payees claiming funds sent to this contract * @dev This contract can be used when payments need to be received by a group
* according to the proportion they own. * of people and split proportionately to some number of shares they own.
*/ */
contract SplitPayment { contract SplitPayment {
using SafeMath for uint256; using SafeMath for uint256;
...@@ -36,27 +36,26 @@ contract SplitPayment { ...@@ -36,27 +36,26 @@ contract SplitPayment {
function () external payable {} function () external payable {}
/** /**
* @dev Claim your share of the balance. * @dev Release one of the payee's proportional payment.
* @param _payee Whose payments will be released.
*/ */
function claim() public { function release(address _payee) public {
address payee = msg.sender; require(shares[_payee] > 0);
require(shares[payee] > 0);
uint256 totalReceived = address(this).balance.add(totalReleased); uint256 totalReceived = address(this).balance.add(totalReleased);
uint256 payment = totalReceived.mul( uint256 payment = totalReceived.mul(
shares[payee]).div( shares[_payee]).div(
totalShares).sub( totalShares).sub(
released[payee] released[_payee]
); );
require(payment != 0); require(payment != 0);
assert(address(this).balance >= payment); assert(address(this).balance >= payment);
released[payee] = released[payee].add(payment); released[_payee] = released[_payee].add(payment);
totalReleased = totalReleased.add(payment); totalReleased = totalReleased.add(payment);
payee.transfer(payment); _payee.transfer(payment);
} }
/** /**
......
...@@ -70,16 +70,12 @@ contract('BreakInvariantBounty', function ([_, owner, researcher, nonTarget]) { ...@@ -70,16 +70,12 @@ contract('BreakInvariantBounty', function ([_, owner, researcher, nonTarget]) {
const researcherPrevBalance = await ethGetBalance(researcher); const researcherPrevBalance = await ethGetBalance(researcher);
const gas = await this.bounty.withdrawPayments.estimateGas({ from: researcher }); await this.bounty.withdrawPayments(researcher, { gasPrice: 0 });
const gasPrice = web3.toWei(1, 'gwei');
const gasCost = (new web3.BigNumber(gas)).times(gasPrice);
await this.bounty.withdrawPayments({ from: researcher, gasPrice: gasPrice });
const updatedBalance = await ethGetBalance(this.bounty.address); const updatedBalance = await ethGetBalance(this.bounty.address);
updatedBalance.should.be.bignumber.equal(0); updatedBalance.should.be.bignumber.equal(0);
const researcherCurrBalance = await ethGetBalance(researcher); const researcherCurrBalance = await ethGetBalance(researcher);
researcherCurrBalance.sub(researcherPrevBalance).should.be.bignumber.equal(reward.sub(gasCost)); researcherCurrBalance.sub(researcherPrevBalance).should.be.bignumber.equal(reward);
}); });
it('cannot claim reward from non-target', async function () { it('cannot claim reward from non-target', async function () {
......
...@@ -51,7 +51,7 @@ contract('PostDeliveryCrowdsale', function ([_, investor, wallet, purchaser]) { ...@@ -51,7 +51,7 @@ contract('PostDeliveryCrowdsale', function ([_, investor, wallet, purchaser]) {
}); });
it('does not allow beneficiaries to withdraw tokens before crowdsale ends', async function () { it('does not allow beneficiaries to withdraw tokens before crowdsale ends', async function () {
await expectThrow(this.crowdsale.withdrawTokens({ from: investor }), EVMRevert); await expectThrow(this.crowdsale.withdrawTokens(investor), EVMRevert);
}); });
context('after closing time', function () { context('after closing time', function () {
...@@ -60,13 +60,13 @@ contract('PostDeliveryCrowdsale', function ([_, investor, wallet, purchaser]) { ...@@ -60,13 +60,13 @@ contract('PostDeliveryCrowdsale', function ([_, investor, wallet, purchaser]) {
}); });
it('allows beneficiaries to withdraw tokens', async function () { it('allows beneficiaries to withdraw tokens', async function () {
await this.crowdsale.withdrawTokens({ from: investor }); await this.crowdsale.withdrawTokens(investor);
(await this.token.balanceOf(investor)).should.be.bignumber.equal(value); (await this.token.balanceOf(investor)).should.be.bignumber.equal(value);
}); });
it('rejects multiple withdrawals', async function () { it('rejects multiple withdrawals', async function () {
await this.crowdsale.withdrawTokens({ from: investor }); await this.crowdsale.withdrawTokens(investor);
await expectThrow(this.crowdsale.withdrawTokens({ from: investor }), EVMRevert); await expectThrow(this.crowdsale.withdrawTokens(investor), EVMRevert);
}); });
}); });
}); });
......
...@@ -55,7 +55,7 @@ contract('RefundableCrowdsale', function ([_, owner, wallet, investor, purchaser ...@@ -55,7 +55,7 @@ contract('RefundableCrowdsale', function ([_, owner, wallet, investor, purchaser
context('before opening time', function () { context('before opening time', function () {
it('denies refunds', async function () { it('denies refunds', async function () {
await expectThrow(this.crowdsale.claimRefund({ from: investor }), EVMRevert); await expectThrow(this.crowdsale.claimRefund(investor), EVMRevert);
}); });
}); });
...@@ -65,7 +65,7 @@ contract('RefundableCrowdsale', function ([_, owner, wallet, investor, purchaser ...@@ -65,7 +65,7 @@ contract('RefundableCrowdsale', function ([_, owner, wallet, investor, purchaser
}); });
it('denies refunds', async function () { it('denies refunds', async function () {
await expectThrow(this.crowdsale.claimRefund({ from: investor }), EVMRevert); await expectThrow(this.crowdsale.claimRefund(investor), EVMRevert);
}); });
context('with unreached goal', function () { context('with unreached goal', function () {
...@@ -81,7 +81,7 @@ contract('RefundableCrowdsale', function ([_, owner, wallet, investor, purchaser ...@@ -81,7 +81,7 @@ contract('RefundableCrowdsale', function ([_, owner, wallet, investor, purchaser
it('refunds', async function () { it('refunds', async function () {
const pre = await ethGetBalance(investor); const pre = await ethGetBalance(investor);
await this.crowdsale.claimRefund({ from: investor, gasPrice: 0 }); await this.crowdsale.claimRefund(investor, { gasPrice: 0 });
const post = await ethGetBalance(investor); const post = await ethGetBalance(investor);
post.minus(pre).should.be.bignumber.equal(lessThanGoal); post.minus(pre).should.be.bignumber.equal(lessThanGoal);
}); });
...@@ -100,7 +100,7 @@ contract('RefundableCrowdsale', function ([_, owner, wallet, investor, purchaser ...@@ -100,7 +100,7 @@ contract('RefundableCrowdsale', function ([_, owner, wallet, investor, purchaser
}); });
it('denies refunds', async function () { it('denies refunds', async function () {
await expectThrow(this.crowdsale.claimRefund({ from: investor }), EVMRevert); await expectThrow(this.crowdsale.claimRefund(investor), EVMRevert);
}); });
it('forwards funds to wallet', async function () { it('forwards funds to wallet', async function () {
......
...@@ -105,7 +105,7 @@ contract('SampleCrowdsale', function ([_, owner, wallet, investor]) { ...@@ -105,7 +105,7 @@ contract('SampleCrowdsale', function ([_, owner, wallet, investor]) {
await increaseTimeTo(this.afterClosingTime); await increaseTimeTo(this.afterClosingTime);
await this.crowdsale.finalize({ from: owner }); await this.crowdsale.finalize({ from: owner });
await this.crowdsale.claimRefund({ from: investor, gasPrice: 0 }); await this.crowdsale.claimRefund(investor, { gasPrice: 0 });
const balanceAfterRefund = await ethGetBalance(investor); const balanceAfterRefund = await ethGetBalance(investor);
balanceBeforeInvestment.should.be.bignumber.equal(balanceAfterRefund); balanceBeforeInvestment.should.be.bignumber.equal(balanceAfterRefund);
......
...@@ -42,7 +42,7 @@ contract('PullPayment', function ([_, payer, payee1, payee2]) { ...@@ -42,7 +42,7 @@ contract('PullPayment', function ([_, payer, payee1, payee2]) {
(await this.contract.payments(payee1)).should.be.bignumber.equal(amount); (await this.contract.payments(payee1)).should.be.bignumber.equal(amount);
await this.contract.withdrawPayments({ from: payee1 }); await this.contract.withdrawPayments(payee1);
(await this.contract.payments(payee1)).should.be.bignumber.equal(0); (await this.contract.payments(payee1)).should.be.bignumber.equal(0);
const balance = await ethGetBalance(payee1); const balance = await ethGetBalance(payee1);
......
...@@ -61,12 +61,12 @@ contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1, ...@@ -61,12 +61,12 @@ contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1,
}); });
it('should throw if no funds to claim', async function () { it('should throw if no funds to claim', async function () {
await expectThrow(this.contract.claim({ from: payee1 }), EVMRevert); await expectThrow(this.contract.release(payee1), EVMRevert);
}); });
it('should throw if non-payee want to claim', async function () { it('should throw if non-payee want to claim', async function () {
await ethSendTransaction({ from: payer1, to: this.contract.address, value: amount }); await ethSendTransaction({ from: payer1, to: this.contract.address, value: amount });
await expectThrow(this.contract.claim({ from: nonpayee1 }), EVMRevert); await expectThrow(this.contract.release(nonpayee1), EVMRevert);
}); });
it('should distribute funds to payees', async function () { it('should distribute funds to payees', async function () {
...@@ -78,17 +78,17 @@ contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1, ...@@ -78,17 +78,17 @@ contract('SplitPayment', function ([_, owner, payee1, payee2, payee3, nonpayee1,
// distribute to payees // distribute to payees
const initAmount1 = await ethGetBalance(payee1); const initAmount1 = await ethGetBalance(payee1);
await this.contract.claim({ from: payee1 }); await this.contract.release(payee1);
const profit1 = (await ethGetBalance(payee1)).sub(initAmount1); const profit1 = (await ethGetBalance(payee1)).sub(initAmount1);
profit1.sub(web3.toWei(0.20, 'ether')).abs().should.be.bignumber.lt(1e16); profit1.sub(web3.toWei(0.20, 'ether')).abs().should.be.bignumber.lt(1e16);
const initAmount2 = await ethGetBalance(payee2); const initAmount2 = await ethGetBalance(payee2);
await this.contract.claim({ from: payee2 }); await this.contract.release(payee2);
const profit2 = (await ethGetBalance(payee2)).sub(initAmount2); const profit2 = (await ethGetBalance(payee2)).sub(initAmount2);
profit2.sub(web3.toWei(0.10, 'ether')).abs().should.be.bignumber.lt(1e16); profit2.sub(web3.toWei(0.10, 'ether')).abs().should.be.bignumber.lt(1e16);
const initAmount3 = await ethGetBalance(payee3); const initAmount3 = await ethGetBalance(payee3);
await this.contract.claim({ from: payee3 }); await this.contract.release(payee3);
const profit3 = (await ethGetBalance(payee3)).sub(initAmount3); const profit3 = (await ethGetBalance(payee3)).sub(initAmount3);
profit3.sub(web3.toWei(0.70, 'ether')).abs().should.be.bignumber.lt(1e16); profit3.sub(web3.toWei(0.70, 'ether')).abs().should.be.bignumber.lt(1e16);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment