Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/cliff logic #100

Merged
merged 2 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
MAINNET_ENDPOINT=
MAINNET_PRIVATE_KEY=
GOERLI_ENDPOINT=
GOERLI_PRIVATE_KEY=
ETHERSCAN_API_KEY=
4 changes: 2 additions & 2 deletions src/TokenVesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -344,14 +344,14 @@ contract TokenVesting is Owned, ReentrancyGuard {
// If the current time is after the vesting period, all tokens are releasable,
// minus the amount already released.
else if (
currentTime >= vestingSchedule.start + vestingSchedule.duration
currentTime >= vestingSchedule.cliff + vestingSchedule.duration
) {
return vestingSchedule.amountTotal - vestingSchedule.released;
}
// Otherwise, some tokens are releasable.
else {
// Compute the number of full vesting periods that have elapsed.
uint256 timeFromStart = currentTime - vestingSchedule.start;
uint256 timeFromStart = currentTime - vestingSchedule.cliff;
uint256 secondsPerSlice = vestingSchedule.slicePeriodSeconds;
uint256 vestedSlicePeriods = timeFromStart / secondsPerSlice;
uint256 vestedSeconds = vestedSlicePeriods * secondsPerSlice;
Expand Down
191 changes: 191 additions & 0 deletions test/TokenVesting.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,196 @@ describe("TokenVesting", function () {
)
).to.be.revertedWith("TokenVesting: amount must be > 0");
});

it("Should not release tokens before cliff", async function () {
// deploy vesting contract
const tokenVesting = await TokenVesting.deploy(testToken.address);
await tokenVesting.deployed();

// send tokens to vesting contract
await expect(testToken.transfer(tokenVesting.address, 1000))
.to.emit(testToken, "Transfer")
.withArgs(owner.address, tokenVesting.address, 1000);

const baseTime = 1622551248;
const beneficiary = addr1;
const startTime = baseTime;
const cliff = 100; // Set a non-zero cliff duration
const duration = 1000;
const slicePeriodSeconds = 1;
const revokable = true;
const amount = 100;

// create new vesting schedule
await tokenVesting.createVestingSchedule(
beneficiary.address,
startTime,
cliff,
duration,
slicePeriodSeconds,
revokable,
amount
);

// compute vesting schedule id
const vestingScheduleId =
await tokenVesting.computeVestingScheduleIdForAddressAndIndex(
beneficiary.address,
0
);

// check that vested amount is 0 before cliff
expect(
await tokenVesting.computeReleasableAmount(vestingScheduleId)
).to.be.equal(0);

// set time to just before the cliff
const justBeforeCliff = baseTime + cliff - 1;
await tokenVesting.setCurrentTime(justBeforeCliff);

// check that vested amount is still 0 just before the cliff
expect(
await tokenVesting.computeReleasableAmount(vestingScheduleId)
).to.be.equal(0);

// set time to the cliff
await tokenVesting.setCurrentTime(baseTime + cliff + 200);

// check that vested amount is greater than 0 at the cliff
expect(
await tokenVesting.computeReleasableAmount(vestingScheduleId)
).to.be.equal(20);
});

it("Should vest tokens correctly with a 6-months cliff and 12-months duration", async function () {
// deploy vesting contract
const tokenVesting = await TokenVesting.deploy(testToken.address);
await tokenVesting.deployed();

// send tokens to vesting contract
await expect(testToken.transfer(tokenVesting.address, 1000))
.to.emit(testToken, "Transfer")
.withArgs(owner.address, tokenVesting.address, 1000);

const baseTime = 1622551248; // June 1, 2021
const beneficiary = addr1;
const startTime = baseTime;
const cliff = 15552000; // 6 months in seconds
const duration = 31536000; // 12 months in seconds
const slicePeriodSeconds = 2592000; // 1 month in seconds
const revokable = true;
const amount = 1000;

// create new vesting schedule
await tokenVesting.createVestingSchedule(
beneficiary.address,
startTime,
cliff,
duration,
slicePeriodSeconds,
revokable,
amount
);

// compute vesting schedule id
const vestingScheduleId =
await tokenVesting.computeVestingScheduleIdForAddressAndIndex(
beneficiary.address,
0
);

// check that vested amount is 0 before cliff
expect(
await tokenVesting.computeReleasableAmount(vestingScheduleId)
).to.be.equal(0);

// set time to just before the cliff (5 months)
const justBeforeCliff = baseTime + cliff - 2592000;
await tokenVesting.setCurrentTime(justBeforeCliff);

// check that vested amount is still 0 just before the cliff
expect(
await tokenVesting.computeReleasableAmount(vestingScheduleId)
).to.be.equal(0);

// set time to the cliff (6 months)
await tokenVesting.setCurrentTime(baseTime + cliff);

// check that vested amount is equal to the total amount at the cliff
expect(
await tokenVesting.computeReleasableAmount(vestingScheduleId)
).to.be.equal(0);

// set time to halfway through the vesting period (12 months)
const halfwayThrough = baseTime + cliff + duration / 2;
await tokenVesting.setCurrentTime(halfwayThrough);

// check that vested amount is greater than 0
expect(
await tokenVesting.computeReleasableAmount(vestingScheduleId)
).to.be.gt(400);

// set time to the end of the vesting period (18 months)
const endOfVesting = baseTime + cliff + duration;
await tokenVesting.setCurrentTime(endOfVesting);

// check that vested amount is equal to the total amount
expect(
await tokenVesting.computeReleasableAmount(vestingScheduleId)
).to.be.equal(amount);
});

});

describe("Withdraw", function () {
it("Should not allow non-owners to withdraw tokens", async function () {
const tokenVesting = await TokenVesting.deploy(testToken.address);
await tokenVesting.deployed();
await testToken.transfer(tokenVesting.address, 1000);

// Owner should be able to withdraw tokens
await expect(tokenVesting.withdraw(500))
.to.emit(testToken, "Transfer")
.withArgs(tokenVesting.address, owner.address, 500);

// Non-owner should not be able to withdraw tokens
await expect(
tokenVesting.connect(addr1).withdraw(100)
).to.be.revertedWith("UNAUTHORIZED");

await expect(
tokenVesting.connect(addr2).withdraw(200)
).to.be.revertedWith("UNAUTHORIZED");
});

it("Should not allow withdrawing more than the available withdrawable amount", async function () {
const tokenVesting = await TokenVesting.deploy(testToken.address);
await tokenVesting.deployed();
await testToken.transfer(tokenVesting.address, 1000);

await expect(
tokenVesting.withdraw(1500)
).to.be.revertedWith("TokenVesting: not enough withdrawable funds");
});

it("Should emit a Transfer event when withdrawing tokens", async function () {
const tokenVesting = await TokenVesting.deploy(testToken.address);
await tokenVesting.deployed();
await testToken.transfer(tokenVesting.address, 1000);

await expect(tokenVesting.withdraw(500))
.to.emit(testToken, "Transfer")
.withArgs(tokenVesting.address, owner.address, 500);
});

it("Should update the withdrawable amount after withdrawing tokens", async function () {
const tokenVesting = await TokenVesting.deploy(testToken.address);
await tokenVesting.deployed();
await testToken.transfer(tokenVesting.address, 1000);

await tokenVesting.withdraw(300);
expect(await tokenVesting.getWithdrawableAmount()).to.equal(700);
});

});
});
Loading