This repository has been archived by the owner on Jul 14, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
EvmosVesting.sol
212 lines (172 loc) · 6.83 KB
/
EvmosVesting.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./storage/VestingData.sol";
/**
* @title EvmosVesting
* @dev An Evmos holding contract that can release its balance gradually like a
* typical vesting scheme, with a vesting period. Optionally revocable by the
* owner, or in our case a community controlled multisig safe.
* NOTE: anyone can send EVMOS to the contract but only the owner of the contract or the beneficiary can receive EVMOS from this contract.
* TODO: Create factory contract for easier deployment of vesting contracts.
* TODO: Allow the vesting contract to handle ERC20 deposits.
*/
contract EvmosVesting is Ownable, ReentrancyGuard, VestingData {
// The vesting schedule is time-based (i.e. using block timestamps as opposed to e.g. block numbers). In Ethereum
// pre-merge, short vesting times (minutes or even hours) were somewhat susceptible to timestamp manipulation by miners.
// This is no longer relevant, and long vesting periods - for example, 4 years as I proposed - are not susceptible to this manipulation.
// Refer to OpenZeppelin documentation regarding the access control Ownable.sol and the ReentrancyGuard contract.
// solhint-disable not-rely-on-time
using SafeMath for uint256;
event LogReleased(uint256 amount);
event LogRevoked(bool releaseSuccessful);
// Beneficiary of the EVMOS vesting contract.
address private _beneficiary;
// Durations and timestamps are expressed in UNIX time, the same units as block.timestamp.
uint256 private _start;
uint256 private _duration;
// Set to true when initializing the contract for DAO contributors unless in
// special cases where governance has allowed an unrevocable contract.
bool private _revocable;
uint256 private _released;
bool private _revoked;
/**
* @dev Creates the contract that vests its balance of EVMOS to the
* beneficiary, linearly from start time + duration of the overall vesting period.
* @param beneficiary address of the beneficiary to whom vested Evmos is transferred
* @param start the time (as Unix time) at which point vesting starts
* @param duration duration in seconds of the period in which the Evmos will vest
* @param revocable whether the vesting is revocable or not
*/
constructor (address beneficiary, uint256 start, uint256 duration, bool revocable) {
require(beneficiary != address(0), "EvmosVesting: beneficiary is the zero address");
// solhint-disable-next-line max-line-length
require(duration > 0, "EvmosVesting: duration is 0");
// solhint-disable-next-line max-line-length
require(start.add(duration) > block.timestamp, "EvmosVesting: final time is before current time");
_beneficiary = beneficiary;
_revocable = revocable;
_duration = duration;
_start = start;
}
/**
* @return the beneficiary of the Evmos.
*/
function beneficiary() public view returns (address) {
return _beneficiary;
}
/**
* @return the start time of the Evmos vesting.
*/
function start() public view returns (uint256) {
return _start;
}
/**
* @return the duration of the Evmos vesting.
*/
function duration() public view returns (uint256) {
return _duration;
}
/**
* @return true if the vesting is revocable.
*/
function revocable() public view returns (bool) {
return _revocable;
}
/**
* @return the amount of the Evmos released.
*/
function released() public view returns (uint256) {
return _released;
}
/**
* @return true if the Evmos is revoked.
*/
function revoked() public view returns (bool) {
return _revoked;
}
/**
* @notice Transfers vested Evmos to beneficiary.
*/
function release()
external
nonReentrant
{
uint256 unreleased = _releasableAmount();
require(unreleased > 0, "EvmosVesting: no Evmos are due");
_released = _released.add(unreleased);
(bool success, ) = _beneficiary.call{ value: unreleased}("");
require(
success,
"EvmosVesting::Transfer Error. Unable to send unreleased to _beneficiary."
);
emit LogReleased(unreleased);
}
/**
* @notice Allows the owner (multisig or treasury) to revoke the vesting. Evmos already vested
* remain in the contract, the rest are returned to the owner.
* @TODO - Create function to allow partial revokes for negative adjustments.
*/
function revoke()
external
onlyOwner
nonReentrant
{
require(_revocable, "EvmosVesting: cannot revoke");
require(!_revoked, "EvmosVesting: EVMOS already revoked");
uint256 unreleased = _releasableAmount();
(bool releaseSuccessful, ) = _beneficiary.call{ value: unreleased }("");
if (releaseSuccessful) {
_released = _released.add(unreleased);
emit LogReleased(unreleased);
}
uint256 refund = address(this).balance;
_revoked = true;
if (refund > 0) {
(bool success, ) = owner().call{ value: refund}("");
require(
success,
"EvmosVesting::Transfer Error. Unable to send refund to owner."
);
}
emit LogRevoked(releaseSuccessful);
}
/**
* @dev Calculates the amount that has already vested but hasn't been released yet.
*/
function _releasableAmount() private view returns (uint256) {
return _vestedAmount().sub(_released);
}
/**
* @dev Calculates the amount that has already vested.
*/
function _vestedAmount() private view returns (uint256) {
if (block.timestamp <= _start) return 0;
uint256 currentBalance = address(this).balance;
uint256 totalBalance = currentBalance.add(_released);
if (block.timestamp >= _start.add(_duration) || _revoked) {
return totalBalance;
} else {
return totalBalance.mul(block.timestamp.sub(_start)).div(_duration);
}
}
/**
* @dev For when more Evmos is received by the contract. Most likely to be sent from a multisig after
* each funding cycle adjustments or as bonus. Does NOT have to be sent by the contract owner, but ONLY the owner
* can recover revoked funds.
*/
receive()
external
payable
nonReentrant
{
require(
msg.value > 0,
"fallback::Invalid Value. msg.value must be greater than 0."
);
increaseTotalFundingBy(msg.value);
emit LogFunding(msg.sender, msg.value);
}
}