Skip to content
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

Add getter for number of unclaimed tokens in PaymentSplitter #3350

Merged
merged 7 commits into from May 31, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,7 @@
* `MerkleProof`: add `multiProofVerify` to prove multiple values are part of a Merkle tree. ([#3276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3276))
* `ERC721`, `ERC1155`: simplified revert reasons. ([#3254](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3254))
* `ERC721`: removed redundant require statement. ([#3434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3434))
* `PaymentSplitter`: add `releasable` getters. ([#3350](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3350))

## 4.6.0 (2022-04-26)

Expand Down
23 changes: 19 additions & 4 deletions contracts/finance/PaymentSplitter.sol
Expand Up @@ -120,15 +120,31 @@ contract PaymentSplitter is Context {
return _payees[index];
}

/**
* @dev Getter for the amount of payee's releasable Ether.
*/
function releasable(address account) public view returns (uint256) {
uint256 totalReceived = address(this).balance + totalReleased();
return _pendingPayment(account, totalReceived, released(account));
}

/**
* @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an
* IERC20 contract.
*/
function releasable(IERC20 token, address account) public view returns (uint256) {
uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);
return _pendingPayment(account, totalReceived, released(token, account));
}

/**
* @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the
* total shares and their previous withdrawals.
*/
function release(address payable account) public virtual {
require(_shares[account] > 0, "PaymentSplitter: account has no shares");

uint256 totalReceived = address(this).balance + totalReleased();
uint256 payment = _pendingPayment(account, totalReceived, released(account));
uint256 payment = releasable(account);

require(payment != 0, "PaymentSplitter: account is not due payment");

Expand All @@ -147,8 +163,7 @@ contract PaymentSplitter is Context {
function release(IERC20 token, address account) public virtual {
require(_shares[account] > 0, "PaymentSplitter: account has no shares");

uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);
uint256 payment = _pendingPayment(account, totalReceived, released(token, account));
uint256 payment = releasable(token, account);

require(payment != 0, "PaymentSplitter: account is not due payment");

Expand Down
33 changes: 27 additions & 6 deletions test/finance/PaymentSplitter.test.js
Expand Up @@ -62,10 +62,11 @@ contract('PaymentSplitter', function (accounts) {
await Promise.all(this.payees.map(async (payee, index) => {
expect(await this.contract.payee(index)).to.equal(payee);
expect(await this.contract.released(payee)).to.be.bignumber.equal('0');
expect(await this.contract.releasable(payee)).to.be.bignumber.equal('0');
}));
});

describe('accepts payments', async function () {
describe('accepts payments', function () {
it('Ether', async function () {
await send.ether(owner, this.contract.address, amount);

Expand All @@ -79,7 +80,7 @@ contract('PaymentSplitter', function (accounts) {
});
});

describe('shares', async function () {
describe('shares', function () {
it('stores shares if address is payee', async function () {
expect(await this.contract.shares(payee1)).to.be.bignumber.not.equal('0');
});
Expand All @@ -89,8 +90,8 @@ contract('PaymentSplitter', function (accounts) {
});
});

describe('release', async function () {
describe('Ether', async function () {
describe('release', function () {
describe('Ether', function () {
it('reverts if no funds to claim', async function () {
await expectRevert(this.contract.release(payee1),
'PaymentSplitter: account is not due payment',
Expand All @@ -104,7 +105,7 @@ contract('PaymentSplitter', function (accounts) {
});
});

describe('Token', async function () {
describe('Token', function () {
it('reverts if no funds to claim', async function () {
await expectRevert(this.contract.release(this.token.address, payee1),
'PaymentSplitter: account is not due payment',
Expand All @@ -119,7 +120,27 @@ contract('PaymentSplitter', function (accounts) {
});
});

describe('distributes funds to payees', async function () {
describe('tracks releasable and released', function () {
it('Ether', async function () {
await send.ether(payer1, this.contract.address, amount);
const payment = amount.divn(10);
expect(await this.contract.releasable(payee2)).to.be.bignumber.equal(payment);
await this.contract.release(payee2);
expect(await this.contract.releasable(payee2)).to.be.bignumber.equal('0');
expect(await this.contract.released(payee2)).to.be.bignumber.equal(payment);
});

it('Token', async function () {
await this.token.transfer(this.contract.address, amount, { from: owner });
const payment = amount.divn(10);
expect(await this.contract.releasable(this.token.address, payee2, {})).to.be.bignumber.equal(payment);
await this.contract.release(this.token.address, payee2);
expect(await this.contract.releasable(this.token.address, payee2, {})).to.be.bignumber.equal('0');
expect(await this.contract.released(this.token.address, payee2)).to.be.bignumber.equal(payment);
});
});

describe('distributes funds to payees', function () {
it('Ether', async function () {
await send.ether(payer1, this.contract.address, amount);

Expand Down