Skip to content

Commit b81bec4

Browse files
ernestognwfrangio
andauthored
Use Ownable in VestingWallet instead of an immutable beneficiary (#4508)
Co-authored-by: Francisco Giordano <[email protected]>
1 parent f715365 commit b81bec4

File tree

4 files changed

+26
-22
lines changed

4 files changed

+26
-22
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': major
3+
---
4+
5+
`VestingWallet`: Use `Ownable` instead of an immutable `beneficiary`.

contracts/finance/VestingWallet.sol

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,28 @@ import {IERC20} from "../token/ERC20/IERC20.sol";
66
import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol";
77
import {Address} from "../utils/Address.sol";
88
import {Context} from "../utils/Context.sol";
9+
import {Ownable} from "../access/Ownable.sol";
910

1011
/**
11-
* @title VestingWallet
12-
* @dev This contract handles the vesting of Eth and ERC20 tokens for a given beneficiary. Custody of multiple tokens
13-
* can be given to this contract, which will release the token to the beneficiary following a given vesting schedule.
14-
* The vesting schedule is customizable through the {vestedAmount} function.
12+
* @dev A vesting wallet is an ownable contract that can receive native currency and ERC20 tokens, and release these
13+
* assets to the wallet owner, also referred to as "beneficiary", according to a vesting schedule.
1514
*
16-
* Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning.
15+
* Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning.
1716
* Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly)
1817
* be immediately releasable.
1918
*
2019
* By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for
2120
* a beneficiary until a specified time.
2221
*
22+
* NOTE: Since the wallet is {Ownable}, and ownership can be transferred, it is possible to sell unvested tokens.
23+
* Preventing this in a smart contract is difficult, considering that: 1) a beneficiary address could be a
24+
* counterfactually deployed contract, 2) there is likely to be a migration path for EOAs to become contracts in the
25+
* near future.
26+
*
2327
* NOTE: When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make sure
2428
* to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended.
2529
*/
26-
contract VestingWallet is Context {
30+
contract VestingWallet is Context, Ownable {
2731
event EtherReleased(uint256 amount);
2832
event ERC20Released(address indexed token, uint256 amount);
2933

@@ -34,18 +38,18 @@ contract VestingWallet is Context {
3438

3539
uint256 private _released;
3640
mapping(address => uint256) private _erc20Released;
37-
address private immutable _beneficiary;
3841
uint64 private immutable _start;
3942
uint64 private immutable _duration;
4043

4144
/**
42-
* @dev Set the beneficiary, start timestamp and vesting duration of the vesting wallet.
45+
* @dev Sets the sender as the initial owner, the beneficiary as the pending owner, the start timestamp and the
46+
* vesting duration of the vesting wallet.
4347
*/
44-
constructor(address beneficiaryAddress, uint64 startTimestamp, uint64 durationSeconds) payable {
45-
if (beneficiaryAddress == address(0)) {
48+
constructor(address beneficiary, uint64 startTimestamp, uint64 durationSeconds) payable Ownable(beneficiary) {
49+
if (beneficiary == address(0)) {
4650
revert VestingWalletInvalidBeneficiary(address(0));
4751
}
48-
_beneficiary = beneficiaryAddress;
52+
4953
_start = startTimestamp;
5054
_duration = durationSeconds;
5155
}
@@ -55,13 +59,6 @@ contract VestingWallet is Context {
5559
*/
5660
receive() external payable virtual {}
5761

58-
/**
59-
* @dev Getter for the beneficiary address.
60-
*/
61-
function beneficiary() public view virtual returns (address) {
62-
return _beneficiary;
63-
}
64-
6562
/**
6663
* @dev Getter for the start timestamp.
6764
*/
@@ -121,7 +118,7 @@ contract VestingWallet is Context {
121118
uint256 amount = releasable();
122119
_released += amount;
123120
emit EtherReleased(amount);
124-
Address.sendValue(payable(beneficiary()), amount);
121+
Address.sendValue(payable(owner()), amount);
125122
}
126123

127124
/**
@@ -133,7 +130,7 @@ contract VestingWallet is Context {
133130
uint256 amount = releasable(token);
134131
_erc20Released[token] += amount;
135132
emit ERC20Released(token, amount);
136-
SafeERC20.safeTransfer(IERC20(token), beneficiary(), amount);
133+
SafeERC20.safeTransfer(IERC20(token), owner(), amount);
137134
}
138135

139136
/**

test/finance/VestingWallet.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ contract('VestingWallet', function (accounts) {
2929
});
3030

3131
it('check vesting contract', async function () {
32-
expect(await this.mock.beneficiary()).to.be.equal(beneficiary);
32+
expect(await this.mock.owner()).to.be.equal(beneficiary);
3333
expect(await this.mock.start()).to.be.bignumber.equal(this.start);
3434
expect(await this.mock.duration()).to.be.bignumber.equal(duration);
3535
expect(await this.mock.end()).to.be.bignumber.equal(this.start.add(duration));

test/utils/Create2.test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ contract('Create2', function (accounts) {
5959
addr: offChainComputed,
6060
});
6161

62-
expect(await VestingWallet.at(offChainComputed).then(instance => instance.beneficiary())).to.be.equal(other);
62+
const instance = await VestingWallet.at(offChainComputed);
63+
64+
expect(await instance.owner()).to.be.equal(other);
6365
});
6466

6567
it('deploys a contract with funds deposited in the factory', async function () {

0 commit comments

Comments
 (0)