Skip to content
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
65 changes: 65 additions & 0 deletions contracts/payment/SplitPayment.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
pragma solidity ^0.4.11;

import '../math/SafeMath.sol';

/**
* @title SplitPayment
* @dev Base contract that supports multiple payees claiming funds sent to this contract
* according to the proportion they own.
*/
contract SplitPayment {
using SafeMath for uint256;

uint256 public totalShares = 0;
uint256 public totalReleased = 0;

mapping(address => uint256) public shares;
mapping(address => uint256) public released;
address[] public payees;

/**
* @dev Constructor
*/
function SplitPayment(address[] _payees, uint256[] _shares) {
require(_payees.length == _shares.length);

for (uint256 i = 0; i < _payees.length; i++) {
addPayee(_payees[i], _shares[i]);
}
}

/**
* @dev Add a new payee to the contract.
* @param _payee The address of the payee to add.
* @param _shares The number of shares owned by the payee.
*/
function addPayee(address _payee, uint256 _shares) internal {
require(_payee != address(0));
require(_shares > 0);
require(shares[_payee] == 0);

payees.push(_payee);
shares[_payee] = _shares;
totalShares = totalShares.add(_shares);
}

/**
* @dev Claim your share of the balance.
*/
function claim() public {
address payee = msg.sender;

require(shares[payee] > 0);

uint256 totalReceived = this.balance.add(totalReleased);
uint256 payment = totalReceived.mul(shares[payee]).div(totalShares).sub(released[payee]);

require(payment != 0);
require(this.balance >= payment);

released[payee] = released[payee].add(payment);
totalReleased = totalReleased.add(payment);

payee.transfer(payment);
}
}
78 changes: 78 additions & 0 deletions test/SplitPayment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const BigNumber = web3.BigNumber

const should = require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()

const EVMThrow = require('./helpers/EVMThrow.js')
const SplitPaymentMock = artifacts.require('./helpers/SplitPaymentMock.sol')

contract('SplitPayment', function ([owner, payee1, payee2, payee3, nonpayee1, payer1]) {
const amount = web3.toWei(1.0, 'ether')

beforeEach(async function () {
this.payees = [payee1, payee2, payee3]
this.shares = [20, 10, 70]

this.contract = await SplitPaymentMock.new(this.payees, this.shares)
})

it('should accept payments', async function () {
await web3.eth.sendTransaction({ from: owner, to: this.contract.address, value: amount })

const balance = web3.eth.getBalance(this.contract.address)
balance.should.be.bignumber.equal(amount)
})

it('should store shares if address is payee', async function () {
const shares = await this.contract.shares.call(payee1)
shares.should.be.bignumber.not.equal(0)
})

it('should not store shares if address is not payee', async function () {
const shares = await this.contract.shares.call(nonpayee1)
shares.should.be.bignumber.equal(0)
})

it('should throw if no funds to claim', async function () {
await this.contract.claim({from: payee1}).should.be.rejectedWith(EVMThrow)
})

it('should throw if non-payee want to claim', async function () {
await web3.eth.sendTransaction({from: payer1, to: this.contract.address, value: amount})
await this.contract.claim({from: nonpayee1}).should.be.rejectedWith(EVMThrow)
})

it('should distribute funds to payees', async function () {
await web3.eth.sendTransaction({from: payer1, to: this.contract.address, value: amount})

// receive funds
const initBalance = web3.eth.getBalance(this.contract.address)
initBalance.should.be.bignumber.equal(amount)

// distribute to payees
const initAmount1 = web3.eth.getBalance(payee1)
await this.contract.claim({from: payee1})
const profit1 = web3.eth.getBalance(payee1) - initAmount1
assert(Math.abs(profit1 - web3.toWei(0.20, 'ether')) < 1e16)

const initAmount2 = web3.eth.getBalance(payee2)
await this.contract.claim({from: payee2})
const profit2 = web3.eth.getBalance(payee2) - initAmount2
assert(Math.abs(profit2 - web3.toWei(0.10, 'ether')) < 1e16)

const initAmount3 = web3.eth.getBalance(payee3)
await this.contract.claim({from: payee3})
const profit3 = web3.eth.getBalance(payee3) - initAmount3
assert(Math.abs(profit3 - web3.toWei(0.70, 'ether')) < 1e16)

// end balance should be zero
const endBalance = web3.eth.getBalance(this.contract.address)
endBalance.should.be.bignumber.equal(0)

// check correct funds released accounting
const totalReleased = await this.contract.totalReleased.call()
totalReleased.should.be.bignumber.equal(initBalance)
})
})
10 changes: 10 additions & 0 deletions test/helpers/SplitPaymentMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pragma solidity ^0.4.11;

import '../../contracts/payment/SplitPayment.sol';

// mock class using SplitPayment
contract SplitPaymentMock is SplitPayment {
function SplitPaymentMock(address[] _payees, uint256[] _shares)
SplitPayment(_payees, _shares) payable {}
function () payable {}
}