Files
openzeppelin-contracts/contracts/finance/VestingWallet.sol
github-actions[bot] d183d9b07a Merge release-v5.3 branch (#5632)
Signed-off-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: Francisco Giordano <fg@frang.io>
Co-authored-by: Joseph Delong <joseph@delong.me>
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
Co-authored-by: Renan Souza <renan.rodrigues.souza1@gmail.com>
Co-authored-by: Ernesto García <ernestognw@gmail.com>
Co-authored-by: Voronor <129545215+voronor@users.noreply.github.com>
Co-authored-by: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com>
Co-authored-by: Michalis Kargakis <kargakis@protonmail.com>
Co-authored-by: Bilog WEB3 <155262265+Bilogweb3@users.noreply.github.com>
Co-authored-by: Fallengirl <155266340+Fallengirl@users.noreply.github.com>
Co-authored-by: XxAlex74xX <30472093+XxAlex74xX@users.noreply.github.com>
Co-authored-by: PixelPilot <161360836+PixelPil0t1@users.noreply.github.com>
Co-authored-by: kilavvy <140459108+kilavvy@users.noreply.github.com>
Co-authored-by: Devkuni <155117116+detrina@users.noreply.github.com>
Co-authored-by: Danbo <140512416+dannbbb1@users.noreply.github.com>
Co-authored-by: Ann Wagner <chant_77_swirly@icloud.com>
Co-authored-by: comfsrt <155266597+comfsrt@users.noreply.github.com>
Co-authored-by: Bob <158583129+bouchmann@users.noreply.github.com>
Co-authored-by: JohnBonny <158583902+JohnBonny@users.noreply.github.com>
Co-authored-by: moonman <155266991+moooonman@users.noreply.github.com>
Co-authored-by: kazak <alright-epsilon8h@icloud.com>
Co-authored-by: Wei <ybxerlvqtx@rambler.ru>
Co-authored-by: Maxim Evtush <154841002+maximevtush@users.noreply.github.com>
Co-authored-by: Vitalyr <158586577+Vitaliyr888@users.noreply.github.com>
Co-authored-by: pendrue <158588659+pendrue@users.noreply.github.com>
Co-authored-by: Tronica <wudmytrotest404@gmail.com>
Co-authored-by: emmmm <155267286+eeemmmmmm@users.noreply.github.com>
Co-authored-by: bigbear <155267841+aso20455@users.noreply.github.com>
Co-authored-by: Tomás Andróil <tomasandroil@gmail.com>
Co-authored-by: GooseMatrix <155266802+GooseMatrix@users.noreply.github.com>
Co-authored-by: jasmy <3776356370@qq.com>
Co-authored-by: SITADRITA1 <mrlime2018@gmail.com>
Co-authored-by: Ocenka <testoviydiman1@gmail.com>
2025-04-09 20:47:07 +02:00

160 lines
5.9 KiB
Solidity

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (finance/VestingWallet.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol";
import {Address} from "../utils/Address.sol";
import {Context} from "../utils/Context.sol";
import {Ownable} from "../access/Ownable.sol";
/**
* @dev A vesting wallet is an ownable contract that can receive native currency and ERC-20 tokens, and release these
* assets to the wallet owner, also referred to as "beneficiary", according to a vesting schedule.
*
* Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning.
* Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly)
* be immediately releasable.
*
* By setting the duration to 0, one can configure this contract to behave like an asset timelock that holds tokens for
* a beneficiary until a specified time.
*
* NOTE: Since the wallet is {Ownable}, and ownership can be transferred, it is possible to sell unvested tokens.
* Preventing this in a smart contract is difficult, considering that: 1) a beneficiary address could be a
* counterfactually deployed contract, 2) there is likely to be a migration path for EOAs to become contracts in the
* near future.
*
* NOTE: When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make
* sure to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended.
*
* NOTE: Chains with support for native ERC20s may allow the vesting wallet to withdraw the underlying asset as both an
* ERC20 and as native currency. For example, if chain C supports token A and the wallet gets deposited 100 A, then
* at 50% of the vesting period, the beneficiary can withdraw 50 A as ERC20 and 25 A as native currency (totaling 75 A).
* Consider disabling one of the withdrawal methods.
*/
contract VestingWallet is Context, Ownable {
event EtherReleased(uint256 amount);
event ERC20Released(address indexed token, uint256 amount);
uint256 private _released;
mapping(address token => uint256) private _erc20Released;
uint64 private immutable _start;
uint64 private immutable _duration;
/**
* @dev Sets the beneficiary (owner), the start timestamp and the vesting duration (in seconds) of the vesting
* wallet.
*/
constructor(address beneficiary, uint64 startTimestamp, uint64 durationSeconds) payable Ownable(beneficiary) {
_start = startTimestamp;
_duration = durationSeconds;
}
/**
* @dev The contract should be able to receive Eth.
*/
receive() external payable virtual {}
/**
* @dev Getter for the start timestamp.
*/
function start() public view virtual returns (uint256) {
return _start;
}
/**
* @dev Getter for the vesting duration.
*/
function duration() public view virtual returns (uint256) {
return _duration;
}
/**
* @dev Getter for the end timestamp.
*/
function end() public view virtual returns (uint256) {
return start() + duration();
}
/**
* @dev Amount of eth already released
*/
function released() public view virtual returns (uint256) {
return _released;
}
/**
* @dev Amount of token already released
*/
function released(address token) public view virtual returns (uint256) {
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 amount = releasable();
_released += amount;
emit EtherReleased(amount);
Address.sendValue(payable(owner()), amount);
}
/**
* @dev Release the tokens that have already vested.
*
* Emits a {ERC20Released} event.
*/
function release(address token) public virtual {
uint256 amount = releasable(token);
_erc20Released[token] += amount;
emit ERC20Released(token, amount);
SafeERC20.safeTransfer(IERC20(token), owner(), amount);
}
/**
* @dev Calculates the amount of ether that has already vested. Default implementation is a linear vesting curve.
*/
function vestedAmount(uint64 timestamp) public view virtual returns (uint256) {
return _vestingSchedule(address(this).balance + released(), timestamp);
}
/**
* @dev Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve.
*/
function vestedAmount(address token, uint64 timestamp) public view virtual returns (uint256) {
return _vestingSchedule(IERC20(token).balanceOf(address(this)) + released(token), timestamp);
}
/**
* @dev Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for
* an asset given its total historical allocation.
*/
function _vestingSchedule(uint256 totalAllocation, uint64 timestamp) internal view virtual returns (uint256) {
if (timestamp < start()) {
return 0;
} else if (timestamp >= end()) {
return totalAllocation;
} else {
return (totalAllocation * (timestamp - start())) / duration();
}
}
}