Skip to content

Commit

Permalink
feat: resolve vesting with delay instead of clif (#17)
Browse files Browse the repository at this point in the history
* resolve vesting with delay

* bump readme
  • Loading branch information
AlcibiadesCleinias authored Jan 19, 2024
1 parent f1a0879 commit 535d8d9
Show file tree
Hide file tree
Showing 11 changed files with 58 additions and 77 deletions.
10 changes: 6 additions & 4 deletions contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ This is solidity contracts for Fluence DAO. For DAO it uses OpenZeppelin contrac
- everyone could **execute a proposal**
- **veto power**: Finally CANCELLER_ROLE is granted to `Governor` contract & `Fluence Multisig`. Veto could be applied after proposal is queued to execute (before it is executed).
- LBP Vesting with moving funds after specified time to Uniswap (TODO: write more precisely)
- 3 Vesting Contract (TODO: write more precisely)
- 3 Vesting Contract with Delayed Start:
- 2 to use different configs between Fluence team and investors
- 1 with Voting
- [0003_DevRewardDistributor.ts](deploy%2F0003_DevRewardDistributor.ts) (TODO: write more precisely)
- **FluenceToken** based on **ERC20VotesUpgradeable** (OpenZeppelin) for the DAO purposes.

Expand Down Expand Up @@ -198,18 +200,18 @@ sequenceDiagram
claimingPeriodMonths: {claim period in months}

investorsVesting:
cliffDurationMonths: 1
delayDurationMonths: 1
vestingDurationMonths: 1
csvFile: {csv file with investors addresses and tokens}

fluenceVesting:
cliffDurationMonths: 1
delayDurationMonths: 1
vestingDurationMonths: 1
account: {fluence account}
amount: 10

teamVesting:
cliffDurationMonths: 1
delayDurationMonths: 1
vestingDurationMonths: 1
csvFile: {csv file with team members addresses and tokens}

Expand Down
6 changes: 3 additions & 3 deletions contracts/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ deployment:
claimingPeriodMonths: 1

investorsVesting:
cliffDurationMonths: 1
delayDurationMonths: 1
vestingDurationMonths: 1
csvFile: "table.csv"
accounts:
Expand All @@ -51,13 +51,13 @@ deployment:
- 10

fluenceVesting:
cliffDurationMonths: 1
delayDurationMonths: 1
vestingDurationMonths: 1
account: "0x0000000000000000000000000000000000000000"
amount: 10

teamVesting:
cliffDurationMonths: 1
delayDurationMonths: 1
vestingDurationMonths: 1
csvFile: "table.csv"
accounts:
Expand Down
29 changes: 10 additions & 19 deletions contracts/contracts/Vesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import "./FluenceToken.sol";
import "./interfaces/IVestingERC20.sol";

/**
* @title Vesting
* @notice Vesting fluence token contract
* @title Vesting with Delayed Start
* @notice Vesting Fluence token contract
* @dev This contract implements the ERC20 standard. It is possible to add the contract to a wallet. Transferring to zero address is unlocking the released amount.
*/
contract Vesting is IVestingERC20 {
Expand All @@ -25,15 +25,9 @@ contract Vesting is IVestingERC20 {
uint256 public immutable startTimestamp;

/**
* @notice Returns the end vesting time
* @notice Returns the vesting duration since vesting start
**/
uint256 public immutable cliffEndTimestamp;

/**
* @notice Returns the total locked time
* @dev total locked time = vesting period + clif period
**/
uint256 public immutable totalLockedTime;
uint256 public immutable vestingDuration;

/**
* @notice Returns the vesting contract decimals
Expand Down Expand Up @@ -63,7 +57,7 @@ contract Vesting is IVestingERC20 {
* @param token_ - vesting token address
* @param name_ - vesting contract name
* @param symbol_ - vesting contract symbol
* @param _cliffDuration - cliff duration
* @param _vestingDelay - delay before vesting start
* @param _vestingDuration - vesting duration
* @param accounts - vesting accounts
* @param amounts - vesting amounts of accounts
Expand All @@ -72,7 +66,7 @@ contract Vesting is IVestingERC20 {
FluenceToken token_,
string memory name_,
string memory symbol_,
uint256 _cliffDuration,
uint256 _vestingDelay,
uint256 _vestingDuration,
address[] memory accounts,
uint256[] memory amounts
Expand All @@ -85,12 +79,9 @@ contract Vesting is IVestingERC20 {
require(bytes(name_).length <= 31, "invalid name length");
require(bytes(symbol_).length <= 31, "invalid symbol length");

startTimestamp = block.timestamp;

uint256 cliffDuration = _cliffDuration;
cliffEndTimestamp = block.timestamp + cliffDuration;
startTimestamp = block.timestamp + _vestingDelay;

totalLockedTime = _vestingDuration + cliffDuration;
vestingDuration = _vestingDuration;

token = token_;

Expand Down Expand Up @@ -137,11 +128,11 @@ contract Vesting is IVestingERC20 {
* @return available amount
**/
function getAvailableAmount(address account) public view returns (uint256) {
if (block.timestamp <= cliffEndTimestamp) {
if (block.timestamp <= startTimestamp) {
return 0;
}

uint256 totalTime = totalLockedTime;
uint256 totalTime = vestingDuration;
uint256 locked = lockedBalances[account];
uint256 released = locked - balanceOf[account];

Expand Down
2 changes: 1 addition & 1 deletion contracts/deploy/0004_FluenceVesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
"Fluence Vesting",
"FLTFV",
Math.floor(
config.deployment!.fluenceVesting!.cliffDurationMonths * MONTH
config.deployment!.fluenceVesting!.delayDurationMonths * MONTH
),
Math.floor(
config.deployment!.fluenceVesting!.vestingDurationMonths * MONTH
Expand Down
2 changes: 1 addition & 1 deletion contracts/deploy/0005_InvestorsVesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
"Investors Vesting",
"FLTIV",
Math.floor(
config.deployment!.investorsVesting!.cliffDurationMonths * MONTH
config.deployment!.investorsVesting!.delayDurationMonths * MONTH
),
Math.floor(
config.deployment!.investorsVesting!.vestingDurationMonths * MONTH
Expand Down
2 changes: 1 addition & 1 deletion contracts/deploy/0006_TeamVesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
(await hre.deployments.get("FluenceToken")).address,
"Team Vesting",
"FLTTV",
Math.floor(config.deployment!.teamVesting!.cliffDurationMonths * MONTH),
Math.floor(config.deployment!.teamVesting!.delayDurationMonths * MONTH),
Math.floor(config.deployment!.teamVesting!.vestingDurationMonths * MONTH),
accounts,
amounts,
Expand Down
19 changes: 6 additions & 13 deletions contracts/test/Deploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ describe("Deploy script", () => {
claimingPeriodMonths: 5,
},
fluenceVesting: {
cliffDurationMonths: 2,
delayDurationMonths: 2,
vestingDurationMonths: 3,
account: "0x0000000000000000000000000000000000000001",
amount: 1,
},
investorsVesting: {
cliffDurationMonths: 2,
delayDurationMonths: 2,
vestingDurationMonths: 3,
accounts: [
"0x0000000000000000000000000000000000000002",
Expand All @@ -109,7 +109,7 @@ describe("Deploy script", () => {
amounts: [2, 3],
},
teamVesting: {
cliffDurationMonths: 2,
delayDurationMonths: 2,
vestingDurationMonths: 3,
accounts: [
"0x0000000000000000000000000000000000000003",
Expand Down Expand Up @@ -340,8 +340,8 @@ describe("Deploy script", () => {
break;
case fluenceVesting.address:
cfg = {
cliffDurationMonths:
config.deployment!.fluenceVesting!.cliffDurationMonths,
delayDurationMonths:
config.deployment!.fluenceVesting!.delayDurationMonths,
vestingDurationMonths:
config.deployment!.fluenceVesting!.vestingDurationMonths,
accounts: [config.deployment!.fluenceVesting!.account],
Expand All @@ -359,14 +359,7 @@ describe("Deploy script", () => {
expect(await vesting.decimals()).to.eq(18);

expect(await vesting.token()).to.eq(token.address);
expect(await vesting.cliffEndTimestamp()).to.eq(
startTimestamp.add(cfg.cliffDurationMonths * MONTH)
);

expect(await vesting.totalLockedTime()).to.eq(
(cfg.cliffDurationMonths + cfg.vestingDurationMonths) * MONTH
);

expect(await vesting.vestingDuration()).to.eq(cfg.vestingDurationMonths * MONTH);
const accounts = cfg.accounts;
const amounts = cfg.amounts;
for (let i = 0; i < accounts.length; i++) {
Expand Down
6 changes: 3 additions & 3 deletions contracts/test/Governor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe("Deploy script", () => {
delayDays: 50 / 86400,
},
teamVesting: {
cliffDurationMonths: 2,
delayDurationMonths: 2,
vestingDurationMonths: 3,
accounts: [account.address],
amounts: [1_000_000],
Expand Down Expand Up @@ -182,7 +182,7 @@ describe("Deploy script", () => {

await ethers.provider.send("evm_setNextBlockTimestamp", [
(await ethers.provider.getBlock("latest")).timestamp +
(config.deployment!.teamVesting!.cliffDurationMonths * MONTH + 1),
(config.deployment!.teamVesting!.delayDurationMonths * MONTH + 1),
]);
await ethers.provider.send("evm_mine", []);

Expand Down Expand Up @@ -213,7 +213,7 @@ describe("Deploy script", () => {

await ethers.provider.send("evm_setNextBlockTimestamp", [
(await ethers.provider.getBlock("latest")).timestamp +
((config.deployment!.teamVesting!.cliffDurationMonths +
((config.deployment!.teamVesting!.delayDurationMonths +
config.deployment!.teamVesting!.vestingDurationMonths) *
MONTH +
1),
Expand Down
49 changes: 22 additions & 27 deletions contracts/test/Vesting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { Config } from "../utils/config";
chai.use(waffle.solidity);

const vestingAmount = ethers.utils.parseEther("100");
const cliffDurationMonths = 3;
const delayDurationMonths = 3;
const vestingDurationMonths = 12;
const amountBySec: BigNumber = vestingAmount.div(
BigNumber.from((cliffDurationMonths + vestingDurationMonths) * MONTH)
BigNumber.from(vestingDurationMonths * MONTH)
);

const setupTest = deployments.createFixture(
Expand Down Expand Up @@ -50,7 +50,7 @@ const setupTest = deployments.createFixture(
tokenAddress,
"TestVesting",
"TV",
cliffDurationMonths * MONTH,
delayDurationMonths * MONTH,
vestingDurationMonths * MONTH,
[mainAccount],
[vestingAmount],
Expand Down Expand Up @@ -81,10 +81,10 @@ describe("Vesting", () => {
let vesting: Vesting;
let token: FluenceToken;
let receiverAccount: Wallet;
let startTime: number;
let vestingStartTime: number;

const setTimeAfterStart = async (time: number) => {
await ethers.provider.send("evm_setNextBlockTimestamp", [startTime + time]);
const setTimeAfterVestingStart = async (time: number) => {
await ethers.provider.send("evm_setNextBlockTimestamp", [vestingStartTime + time]);
await ethers.provider.send("evm_mine", []);
};

Expand All @@ -103,30 +103,33 @@ describe("Vesting", () => {
const settings = await setupTest();
vesting = settings.vesting;
token = settings.token;
startTime = (await vesting.startTimestamp()).toNumber();
vestingStartTime = (await vesting.startTimestamp()).toNumber();

vesting = vesting.connect(receiverAccount);
});

it("when cliff is active #1", async () => {
it("when in delay period #1", async () => {
await expect(vesting.transfer(ZERO_ADDRESS, 1)).to.be.revertedWith(
`${THROW_ERROR_PREFIX} 'Not enough the release amount'`
);
});

it("when cliff is active #2", async () => {
await setTimeAfterStart(cliffDurationMonths * MONTH);
it("when in delay period #2", async () => {
await setTimeAfterVestingStart(0);

expect(await vesting.getAvailableAmount(receiverAccount.address)).to.eq(
BigNumber.from(0)
);
});

it("after cliff", async () => {
const time = cliffDurationMonths * MONTH + 1;
await setTimeAfterStart(time);
it("after delay period", async () => {
const vestingTimePassed = 1;
await setTimeAfterVestingStart(vestingTimePassed);

const expectedAmount = amountBySec.mul(BigNumber.from(time));
console.log('amountBySec', amountBySec)
console.log(MONTH);
const expectedAmount = amountBySec.mul(BigNumber.from(vestingTimePassed));
console.log('expectedAmount', expectedAmount)

const amount = await vesting.getAvailableAmount(receiverAccount.address);
expect(amount).to.eq(expectedAmount);
Expand All @@ -147,11 +150,9 @@ describe("Vesting", () => {
});

it("after cliff with random time", async () => {
const time =
Math.floor(Math.random() * vestingDurationMonths * MONTH) +
cliffDurationMonths * MONTH;
const time = Math.floor(Math.random() * vestingDurationMonths * MONTH);

await setTimeAfterStart(time);
await setTimeAfterVestingStart(time);
const amount = await vesting.getAvailableAmount(receiverAccount.address);

const expectedAmount = amountBySec.mul(BigNumber.from(time));
Expand All @@ -172,9 +173,7 @@ describe("Vesting", () => {
});

it("all balance #1", async () => {
await setTimeAfterStart(
(vestingDurationMonths + cliffDurationMonths) * MONTH
);
await setTimeAfterVestingStart(vestingDurationMonths * MONTH);

const amount = await vesting.getAvailableAmount(receiverAccount.address);
expect(amount).to.eq(vestingAmount);
Expand All @@ -192,9 +191,7 @@ describe("Vesting", () => {
});

it("all balance #2", async () => {
await setTimeAfterStart(
(vestingDurationMonths + cliffDurationMonths) * 3 * MONTH
);
await setTimeAfterVestingStart(vestingDurationMonths * 3 * MONTH);

const amount = await vesting.getAvailableAmount(receiverAccount.address);
expect(amount).to.eq(vestingAmount);
Expand Down Expand Up @@ -226,9 +223,7 @@ describe("Vesting", () => {
});

it("transferFrom full", async () => {
await setTimeAfterStart(
(vestingDurationMonths + cliffDurationMonths) * 3 * MONTH
);
await setTimeAfterVestingStart(vestingDurationMonths * 3 * MONTH);

const amount = await vesting.getAvailableAmount(receiverAccount.address);
expect(amount).to.eq(vestingAmount);
Expand Down
6 changes: 3 additions & 3 deletions contracts/test/VestingWithVoting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ZERO_ADDRESS } from "../utils/consts";
chai.use(waffle.solidity);

const vestingAmount = ethers.utils.parseEther("100");
const cliffDurationMonths = 3;
const delayDurationMonths = 3;
const vestingDurationMonths = 12;

const setupTest = deployments.createFixture(
Expand Down Expand Up @@ -48,7 +48,7 @@ const setupTest = deployments.createFixture(
tokenAddress,
"Test Vesting With Votes",
"TVV",
cliffDurationMonths * MONTH,
delayDurationMonths * MONTH,
vestingDurationMonths * MONTH,
[mainAccount],
[vestingAmount],
Expand Down Expand Up @@ -116,7 +116,7 @@ describe("Vesting with voting", () => {
it("get votes after release", async () => {
await vesting.delegate(receiverAccount.address);

const time = cliffDurationMonths * MONTH + 100;
const time = delayDurationMonths * MONTH + 100;
await setTimeAfterStart(time);

const amount = await vesting.getAvailableAmount(receiverAccount.address);
Expand Down
Loading

0 comments on commit 535d8d9

Please sign in to comment.