Skip to content

Commit 1797e78

Browse files
ronhuafengfrangio
andcommitted
Add getter for number of releasable tokens in VestingWallet (OpenZeppelin#3580)
Co-authored-by: Francisco <frangio.1@gmail.com>
1 parent 3fd9b5f commit 1797e78

File tree

5 files changed

+63
-22
lines changed

5 files changed

+63
-22
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* `SafeCast`: optimize downcasting of signed integers. ([#3565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3565))
1616
* `VestingWallet`: remove unused library `Math.sol`. ([#3605](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3605))
1717
* `ECDSA`: Remove redundant check on the `v` value. ([#3591](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3591))
18+
* `VestingWallet`: add `releasable` getters. ([#3580](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3580))
1819

1920
### Deprecations
2021

contracts/finance/VestingWallet.sol

+23-8
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,31 @@ contract VestingWallet is Context {
8080
return _erc20Released[token];
8181
}
8282

83+
/**
84+
* @dev Getter for the amount of releasable eth.
85+
*/
86+
function releasable() public view virtual returns (uint256) {
87+
return vestedAmount(uint64(block.timestamp)) - released();
88+
}
89+
90+
/**
91+
* @dev Getter for the amount of releasable `token` tokens. `token` should be the address of an
92+
* IERC20 contract.
93+
*/
94+
function releasable(address token) public view virtual returns (uint256) {
95+
return vestedAmount(token, uint64(block.timestamp)) - released(token);
96+
}
97+
8398
/**
8499
* @dev Release the native token (ether) that have already vested.
85100
*
86101
* Emits a {EtherReleased} event.
87102
*/
88103
function release() public virtual {
89-
uint256 releasable = vestedAmount(uint64(block.timestamp)) - released();
90-
_released += releasable;
91-
emit EtherReleased(releasable);
92-
Address.sendValue(payable(beneficiary()), releasable);
104+
uint256 amount = releasable();
105+
_released += amount;
106+
emit EtherReleased(amount);
107+
Address.sendValue(payable(beneficiary()), amount);
93108
}
94109

95110
/**
@@ -98,10 +113,10 @@ contract VestingWallet is Context {
98113
* Emits a {ERC20Released} event.
99114
*/
100115
function release(address token) public virtual {
101-
uint256 releasable = vestedAmount(token, uint64(block.timestamp)) - released(token);
102-
_erc20Released[token] += releasable;
103-
emit ERC20Released(token, releasable);
104-
SafeERC20.safeTransfer(IERC20(token), beneficiary(), releasable);
116+
uint256 amount = releasable(token);
117+
_erc20Released[token] += amount;
118+
emit ERC20Released(token, amount);
119+
SafeERC20.safeTransfer(IERC20(token), beneficiary(), amount);
105120
}
106121

107122
/**

package-lock.json

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
},
5454
"homepage": "https://openzeppelin.com/contracts/",
5555
"devDependencies": {
56+
"@nomicfoundation/hardhat-network-helpers": "^1.0.3",
5657
"@nomiclabs/hardhat-truffle5": "^2.0.5",
5758
"@nomiclabs/hardhat-web3": "^2.0.0",
5859
"@openzeppelin/docs-utils": "^0.1.0",

test/finance/VestingWallet.behavior.js

+16-14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const { time } = require('@nomicfoundation/hardhat-network-helpers');
12
const { expectEvent } = require('@openzeppelin/test-helpers');
23
const { expect } = require('chai');
34

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

1011
function shouldBehaveLikeVesting (beneficiary) {
1112
it('check vesting schedule', async function () {
12-
const [ method, ...args ] = this.token
13-
? [ 'vestedAmount(address,uint64)', this.token.address ]
14-
: [ 'vestedAmount(uint64)' ];
13+
const [ fnVestedAmount, fnReleasable, ...args ] = this.token
14+
? [ 'vestedAmount(address,uint64)', 'releasable(address)', this.token.address ]
15+
: [ 'vestedAmount(uint64)', 'releasable()' ];
1516

1617
for (const timestamp of this.schedule) {
17-
expect(await this.mock.methods[method](...args, timestamp))
18-
.to.be.bignumber.equal(this.vestingFn(timestamp));
18+
await time.increaseTo(timestamp);
19+
const vesting = this.vestingFn(timestamp);
20+
21+
expect(await this.mock.methods[fnVestedAmount](...args, timestamp))
22+
.to.be.bignumber.equal(vesting);
23+
24+
expect(await this.mock.methods[fnReleasable](...args))
25+
.to.be.bignumber.equal(vesting);
1926
}
2027
});
2128

2229
it('execute vesting schedule', async function () {
23-
const [ method, ...args ] = this.token
30+
const [ fnRelease, ...args ] = this.token
2431
? [ 'release(address)', this.token.address ]
2532
: [ 'release()' ];
2633

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

3037
{
31-
const receipt = await this.mock.methods[method](...args);
38+
const receipt = await this.mock.methods[fnRelease](...args);
3239

3340
await expectEvent.inTransaction(
3441
receipt.tx,
@@ -42,15 +49,10 @@ function shouldBehaveLikeVesting (beneficiary) {
4249
}
4350

4451
for (const timestamp of this.schedule) {
52+
await time.setNextBlockTimestamp(timestamp);
4553
const vested = this.vestingFn(timestamp);
4654

47-
await new Promise(resolve => web3.currentProvider.send({
48-
method: 'evm_setNextBlockTimestamp',
49-
params: [ timestamp.toNumber() ],
50-
}, resolve));
51-
52-
const receipt = await this.mock.methods[method](...args);
53-
55+
const receipt = await this.mock.methods[fnRelease](...args);
5456
await expectEvent.inTransaction(
5557
receipt.tx,
5658
this.mock,

0 commit comments

Comments
 (0)