Skip to content

Commit

Permalink
Add getter for number of releasable tokens in VestingWallet (OpenZepp…
Browse files Browse the repository at this point in the history
…elin#3580)

Co-authored-by: Francisco <frangio.1@gmail.com>
  • Loading branch information
ronhuafeng and frangio committed Sep 9, 2022
1 parent 3fd9b5f commit 1797e78
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 1797e78

Please sign in to comment.