Skip to content

Commit a143d1d

Browse files
author
joYyHack
committed
Add comprehensive @notice and change Schedule param to scheduleId
1 parent bb17457 commit a143d1d

File tree

3 files changed

+115
-36
lines changed

3 files changed

+115
-36
lines changed

contracts/finance/Vesting.sol

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,59 @@ import {PRECISION} from "../utils/Globals.sol";
1515
* designed to seamlessly manage vestings and associated schedules for
1616
* multiple beneficiaries and ERC20 tokens. This module stands out for its
1717
* flexibility, offering support for both linear and exponential vesting calculations out of the box.
18+
*
19+
* Linear and Exponential Vesting:
20+
*
21+
* Linear vesting has a constant release rate over time (exponent = 1), resulting in a linear graph.
22+
* Exponential vesting allows for a more flexible release rate, defined by the exponent.
23+
* Higher exponents result in a steeper release curve.
24+
*
25+
* Vesting formula:
26+
*
27+
* vestedAmount = elapsedPeriodsPercentage ** exponent * (totalVestingAmount_)).
28+
*
29+
* Key concepts:
30+
*
31+
* Vesting contract contains two main components: Schedule struct and Vesting struct.
32+
* Each vesting contains scheduleId, which is associated with a schedule struct.
33+
*
34+
* You can create as much Schedules as needed with different parameters with an associated scheduleId.
35+
* Then a schedule can be assigned to vestings. So it's possible to create multiple vestings with the same schedule.
36+
*
37+
* Schedule defines the base structure for the vesting and how the vested amount will be calculated,
38+
* with the following parameters such as the duration, cliff, period, and exponent.
39+
*
40+
* Schedule parameters description:
41+
* - secondsInPeriod: The duration of each vesting period in seconds. (i.e. 86,400 sec for 1 day)
42+
* - durationInPeriods: The total number of periods for the vesting. (i.e. 20 for 20 days)
43+
* - cliffInPeriods: The number of periods before the vesting starts. (i.e. 3 for 3 days).
44+
* - exponent: The exponent for the vesting calculation. (i.e. 1 for linear vesting, 5 for exponential)
45+
*
46+
* Example of schedule:
47+
* Let's define a schedule with the following parameters:
48+
* - secondsInPeriod = 86,400 (1 day)
49+
* - durationInPeriods = 20 (20 days)
50+
* - cliffInPeriods = 3 (3 days)
51+
* - exponent = 1 (linear vesting)
52+
*
53+
* Using the provided schedule, you can create a vesting that will release vested amount linearly over 20 days with a 3-day cliff.
54+
* Let's say you have 1000 tokens to vest; then the vested amount will be released as follows:
55+
* - 1 day: 0 tokens
56+
* - 2 days: 0 tokens
57+
* - 3 days: 0 tokens
58+
* - 4 days: 200 tokens
59+
* - 5 days: 250 tokens
60+
* ...
61+
* - 20 days: 1000 tokens
62+
*
63+
* P.S.
64+
* For defining linear vesting, the exponent should be set to 1,
65+
* or there is an option to create a linear schedule just by defining the baseSchedule struct
66+
* and the exponent will be automatically set to 1.
67+
*
68+
* For the creation of exponential vesting, the exponent should be set to a value greater than 1.
69+
*
70+
* It's not possible to create a schedule with an exponent equal to 0.
1871
*/
1972
abstract contract Vesting is Initializable {
2073
using MathUpgradeable for uint256;
@@ -164,9 +217,8 @@ abstract contract Vesting is Initializable {
164217
*/
165218
function getVestedAmount(uint256 vestingId_) public view virtual returns (uint256) {
166219
VestingData storage _vesting = _vestings[vestingId_];
167-
Schedule storage _schedule = _schedules[_vesting.scheduleId];
168220

169-
return _getVestedAmount(_vesting, _schedule, block.timestamp);
221+
return _getVestedAmount(_vesting, _vesting.scheduleId, block.timestamp);
170222
}
171223

172224
/**
@@ -176,9 +228,8 @@ abstract contract Vesting is Initializable {
176228
*/
177229
function getWithdrawableAmount(uint256 vestingId_) public view virtual returns (uint256) {
178230
VestingData storage _vesting = _vestings[vestingId_];
179-
Schedule storage _schedule = _schedules[_vesting.scheduleId];
180231

181-
return _getWithdrawableAmount(_vesting, _schedule, block.timestamp);
232+
return _getWithdrawableAmount(_vesting, _vesting.scheduleId, block.timestamp);
182233
}
183234

184235
/**
@@ -254,18 +305,18 @@ abstract contract Vesting is Initializable {
254305
/**
255306
* @notice Retrieves the vested amount for a vesting ID.
256307
* @param vesting Vesting data for the vesting contract.
257-
* @param schedule Schedule data for the vesting contract.
308+
* @param scheduleId_ Id for the associated schedule.
258309
* @param timestampUpTo_ The timestamp up to which the calculation is performed.
259310
* @return The amount of tokens vested.
260311
*/
261312
function _getVestedAmount(
262313
VestingData storage vesting,
263-
Schedule storage schedule,
314+
uint256 scheduleId_,
264315
uint256 timestampUpTo_
265316
) internal view virtual returns (uint256) {
266317
return
267318
_vestingCalculation(
268-
schedule,
319+
scheduleId_,
269320
vesting.vestingAmount,
270321
vesting.vestingStartTime,
271322
timestampUpTo_
@@ -275,18 +326,18 @@ abstract contract Vesting is Initializable {
275326
/**
276327
* @notice Retrieves the withdrawable amount for a vesting ID.
277328
* @param vesting Vesting data for the vesting contract.
278-
* @param schedule Schedule data for the vesting contract.
329+
* @param scheduleId_ Id for the associated schedule.
279330
* @param timestampUpTo_ The timestamp up to which the calculation is performed.
280331
* @return The amount of tokens withdrawable.
281332
*/
282333
function _getWithdrawableAmount(
283334
VestingData storage vesting,
284-
Schedule storage schedule,
335+
uint256 scheduleId_,
285336
uint256 timestampUpTo_
286337
) internal view virtual returns (uint256) {
287338
return
288339
_vestingCalculation(
289-
schedule,
340+
scheduleId_,
290341
vesting.vestingAmount,
291342
vesting.vestingStartTime,
292343
timestampUpTo_
@@ -295,19 +346,20 @@ abstract contract Vesting is Initializable {
295346

296347
/**
297348
* @notice Performs the vesting calculation.
298-
* @param schedule_ Schedule data for the vesting contract.
349+
* @param scheduleId_ Id for the associated schedule.
299350
* @param totalVestingAmount_ The total amount of tokens to be vested.
300351
* @param vestingStartTime_ The starting time of the vesting.
301352
* @param timestampUpTo_ The timestamp up to which the calculation is performed.
302353
* @return vestedAmount_ The amount of tokens vested.
303354
*/
304355
function _vestingCalculation(
305-
Schedule memory schedule_,
356+
uint256 scheduleId_,
306357
uint256 totalVestingAmount_,
307358
uint256 vestingStartTime_,
308359
uint256 timestampUpTo_
309360
) internal view virtual returns (uint256 vestedAmount_) {
310-
BaseSchedule memory baseData_ = schedule_.scheduleData;
361+
Schedule storage _schedule = _schedules[scheduleId_];
362+
BaseSchedule storage _baseData = _schedule.scheduleData;
311363

312364
if (vestingStartTime_ > timestampUpTo_) {
313365
return vestedAmount_;
@@ -316,24 +368,24 @@ abstract contract Vesting is Initializable {
316368
uint256 elapsedPeriods_ = _calculateElapsedPeriods(
317369
vestingStartTime_,
318370
timestampUpTo_,
319-
baseData_.secondsInPeriod
371+
_baseData.secondsInPeriod
320372
);
321373

322-
if (elapsedPeriods_ <= baseData_.cliffInPeriods) {
374+
if (elapsedPeriods_ <= _baseData.cliffInPeriods) {
323375
return 0;
324376
}
325377

326-
if (elapsedPeriods_ >= baseData_.durationInPeriods) {
378+
if (elapsedPeriods_ >= _baseData.durationInPeriods) {
327379
return totalVestingAmount_;
328380
}
329381

330382
uint256 elapsedPeriodsPercentage_ = (elapsedPeriods_ * PRECISION) /
331-
baseData_.durationInPeriods;
383+
_baseData.durationInPeriods;
332384

333385
vestedAmount_ =
334-
(_raiseToPower(elapsedPeriodsPercentage_, schedule_.exponent) *
386+
(_raiseToPower(elapsedPeriodsPercentage_, _schedule.exponent) *
335387
(totalVestingAmount_)) /
336-
_raiseToPower(PRECISION, schedule_.exponent);
388+
_raiseToPower(PRECISION, _schedule.exponent);
337389

338390
return vestedAmount_.min(totalVestingAmount_);
339391
}

contracts/mock/finance/VestingMock.sol

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,17 @@ contract VestingMock is Vesting {
3232
}
3333

3434
function vestingCalculation(
35-
Schedule memory schedule_,
35+
uint256 scheduleId_,
3636
uint256 totalVestingAmount_,
3737
uint256 vestingStartTime_,
3838
uint256 timestampUpTo_
3939
) public view returns (uint256 vestedAmount_) {
4040
return
41-
_vestingCalculation(schedule_, totalVestingAmount_, vestingStartTime_, timestampUpTo_);
41+
_vestingCalculation(
42+
scheduleId_,
43+
totalVestingAmount_,
44+
vestingStartTime_,
45+
timestampUpTo_
46+
);
4247
}
4348
}

test/finance/Vesting.test.ts

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ethers } from "hardhat";
88

99
import { ERC20Mock, ERC20Mock__factory, Vesting, VestingMock, VestingMock__factory } from "@ethers-v6";
1010

11-
describe("Vesting", () => {
11+
describe.only("Vesting", () => {
1212
let reverter = new Reverter();
1313

1414
let owner: SignerWithAddress;
@@ -381,6 +381,7 @@ describe("Vesting", () => {
381381

382382
describe("check calculations", () => {
383383
let defaultSchedule: Schedule;
384+
let scheduleId = 1;
384385

385386
beforeEach(async () => {
386387
defaultSchedule = {
@@ -397,9 +398,18 @@ describe("Vesting", () => {
397398
let vestingStartTime = BigInt(await time.latest());
398399
let timestampUpTo = 0n;
399400

400-
expect(
401-
await vesting.vestingCalculation(defaultSchedule, vestingAmount, vestingStartTime, timestampUpTo),
402-
).to.be.equal(0);
401+
expect(await vesting.vestingCalculation(scheduleId, vestingAmount, vestingStartTime, timestampUpTo)).to.be.equal(
402+
0,
403+
);
404+
});
405+
406+
it("should return 0 if start time the same as timestamp up to", async () => {
407+
let vestingStartTime = BigInt(await time.latest());
408+
let timestampUpTo = vestingStartTime;
409+
410+
expect(await vesting.vestingCalculation(scheduleId, vestingAmount, vestingStartTime, timestampUpTo)).to.be.equal(
411+
0,
412+
);
403413
});
404414

405415
it("should return 0 if cliff is active", async () => {
@@ -408,18 +418,30 @@ describe("Vesting", () => {
408418

409419
defaultSchedule.scheduleData.cliffInPeriods = 2n;
410420

411-
expect(
412-
await vesting.vestingCalculation(defaultSchedule, vestingAmount, vestingStartTime, timestampUpTo),
413-
).to.be.equal(0);
414-
});
421+
await vesting.createSchedule(defaultSchedule);
415422

416-
it("should return 0 if start time the same as timestamp up to", async () => {
417-
let vestingStartTime = BigInt(await time.latest());
418-
let timestampUpTo = vestingStartTime;
419-
420-
expect(
421-
await vesting.vestingCalculation(defaultSchedule, vestingAmount, vestingStartTime, timestampUpTo),
422-
).to.be.equal(0);
423+
expect(await vesting.vestingCalculation(scheduleId, vestingAmount, vestingStartTime, timestampUpTo)).to.be.equal(
424+
0,
425+
);
423426
});
427+
428+
// it("console test", async () => {
429+
// defaultSchedule.scheduleData.secondsInPeriod = 86400n;
430+
// defaultSchedule.scheduleData.durationInPeriods = 20n;
431+
// defaultSchedule.scheduleData.cliffInPeriods = 3n;
432+
// defaultSchedule.exponent = 1n;
433+
// let vestingStartTime = BigInt(await time.latest());
434+
// let timestampUpTo = vestingStartTime + secondsInPeriod * durationInPeriods;
435+
436+
// for (let i = 1; i <= 20; i++) {
437+
// let vestedAmount = await vesting.vestingCalculation(
438+
// defaultSchedule,
439+
// 1000,
440+
// vestingStartTime,
441+
// vestingStartTime + secondsInPeriod * BigInt(i),
442+
// );
443+
// console.log("vestedAmount", vestedAmount.toString());
444+
// }
445+
// });
424446
});
425447
});

0 commit comments

Comments
 (0)