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 releasable tokens in VestingWallet #3580

Merged
merged 4 commits into from Aug 19, 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 @@ -15,6 +15,7 @@
* `SafeCast`: optimize downcasting of signed integers. ([#3565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3565))
* `VestingWallet`: remove unused library `Math.sol`. ([#3605](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3605))
* `ECDSA`: Remove redundant check on the `v` value. ([#3591](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3591))
* `VestingWallet`: add `releasable` getters. ([#3580](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3580))

### Deprecations

Expand Down
31 changes: 23 additions & 8 deletions contracts/finance/VestingWallet.sol
Expand Up @@ -80,16 +80,31 @@ contract VestingWallet is Context {
return _erc20Released[token];
}

/**
* @dev Getter for the amount of releasable eth.
*/
function releasable() public view virtual returns (uint256) {
return vestedAmount(uint64(block.timestamp)) - released();
}

/**
* @dev Getter for the amount of releasable `token` tokens. `token` should be the address of an
* IERC20 contract.
*/
function releasable(address token) public view virtual returns (uint256) {
return vestedAmount(token, uint64(block.timestamp)) - released(token);
}

/**
* @dev Release the native token (ether) that have already vested.
*
* Emits a {EtherReleased} event.
*/
function release() public virtual {
uint256 releasable = vestedAmount(uint64(block.timestamp)) - released();
_released += releasable;
emit EtherReleased(releasable);
Address.sendValue(payable(beneficiary()), releasable);
uint256 amount = releasable();
_released += amount;
emit EtherReleased(amount);
Address.sendValue(payable(beneficiary()), amount);
}

/**
Expand All @@ -98,10 +113,10 @@ contract VestingWallet is Context {
* Emits a {ERC20Released} event.
*/
function release(address token) public virtual {
uint256 releasable = vestedAmount(token, uint64(block.timestamp)) - released(token);
_erc20Released[token] += releasable;
emit ERC20Released(token, releasable);
SafeERC20.safeTransfer(IERC20(token), beneficiary(), releasable);
uint256 amount = releasable(token);
_erc20Released[token] += amount;
emit ERC20Released(token, amount);
SafeERC20.safeTransfer(IERC20(token), beneficiary(), amount);
}

/**
Expand Down
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -53,6 +53,7 @@
},
"homepage": "https://openzeppelin.com/contracts/",
"devDependencies": {
"@nomicfoundation/hardhat-network-helpers": "^1.0.3",
"@nomiclabs/hardhat-truffle5": "^2.0.5",
"@nomiclabs/hardhat-web3": "^2.0.0",
"@openzeppelin/docs-utils": "^0.1.0",
Expand Down
30 changes: 16 additions & 14 deletions test/finance/VestingWallet.behavior.js
@@ -1,3 +1,4 @@
const { time } = require('@nomicfoundation/hardhat-network-helpers');
const { expectEvent } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');

Expand All @@ -9,26 +10,32 @@ function releasedEvent (token, amount) {

function shouldBehaveLikeVesting (beneficiary) {
it('check vesting schedule', async function () {
const [ method, ...args ] = this.token
? [ 'vestedAmount(address,uint64)', this.token.address ]
: [ 'vestedAmount(uint64)' ];
const [ fnVestedAmount, fnReleasable, ...args ] = this.token
? [ 'vestedAmount(address,uint64)', 'releasable(address)', this.token.address ]
: [ 'vestedAmount(uint64)', 'releasable()' ];

for (const timestamp of this.schedule) {
expect(await this.mock.methods[method](...args, timestamp))
.to.be.bignumber.equal(this.vestingFn(timestamp));
await time.increaseTo(timestamp);
const vesting = this.vestingFn(timestamp);

expect(await this.mock.methods[fnVestedAmount](...args, timestamp))
.to.be.bignumber.equal(vesting);

expect(await this.mock.methods[fnReleasable](...args))
.to.be.bignumber.equal(vesting);
}
});

it('execute vesting schedule', async function () {
const [ method, ...args ] = this.token
const [ fnRelease, ...args ] = this.token
? [ 'release(address)', this.token.address ]
: [ 'release()' ];

let released = web3.utils.toBN(0);
const before = await this.getBalance(beneficiary);

{
const receipt = await this.mock.methods[method](...args);
const receipt = await this.mock.methods[fnRelease](...args);

await expectEvent.inTransaction(
receipt.tx,
Expand All @@ -42,15 +49,10 @@ function shouldBehaveLikeVesting (beneficiary) {
}

for (const timestamp of this.schedule) {
await time.setNextBlockTimestamp(timestamp);
const vested = this.vestingFn(timestamp);

await new Promise(resolve => web3.currentProvider.send({
method: 'evm_setNextBlockTimestamp',
params: [ timestamp.toNumber() ],
}, resolve));

const receipt = await this.mock.methods[method](...args);

const receipt = await this.mock.methods[fnRelease](...args);
await expectEvent.inTransaction(
receipt.tx,
this.mock,
Expand Down