Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Add your information to the below list to officially participate in the workshop

| Emoji | Name | Github Username | Occupations |
| ----- | --------------- | ----------------------------------------------------- | ------------------------ |
| 🎅 | Ippo | [NTP-996](https://github.com/NTP-996) | DevRel |
| 🙋‍♂️ |Aliyu Adeniji | [AbuTuraab](https://github.com/AbuTuraab) | Freelance Dev |

## 💻 Local development environment setup

Expand Down
127 changes: 103 additions & 24 deletions contracts/TokenVesting.sol
Original file line number Diff line number Diff line change
@@ -1,25 +1,3 @@
// Challenge: Token Vesting Contract
/*
Create a token vesting contract with the following requirements:

1. The contract should allow an admin to create vesting schedules for different beneficiaries
2. Each vesting schedule should have:
- Total amount of tokens to be vested
- Cliff period (time before any tokens can be claimed)
- Vesting duration (total time for all tokens to vest)
- Start time
3. After the cliff period, tokens should vest linearly over time
4. Beneficiaries should be able to claim their vested tokens at any time
5. Admin should be able to revoke unvested tokens from a beneficiary

Bonus challenges:
- Add support for multiple token types
- Implement a whitelist for beneficiaries
- Add emergency pause functionality

Here's your starter code:
*/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

Expand All @@ -30,18 +8,29 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
struct VestingSchedule {

uint256 totalAmount;
uint256 startTime;
uint256 cliffDuration;
uint256 vestingDuration;
uint256 amountClaimed;
bool revoked;
// TODO: Define the vesting schedule struct
}

// Token being vested
IERC20 public token;
// TODO: Add state variables


// Mapping from beneficiary to vesting schedule
// TODO: Add state variables
mapping(address => VestingSchedule) public vestingSchedules;

// Whitelist of beneficiaries
// TODO: Add state variables
mapping(address => bool) public whitelist;


// Events
event VestingScheduleCreated(address indexed beneficiary, uint256 amount);
Expand All @@ -52,7 +41,7 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {

constructor(address tokenAddress) {
// TODO: Initialize the contract

token = IERC20(tokenAddress);
}

// Modifier to check if beneficiary is whitelisted
Expand Down Expand Up @@ -80,20 +69,110 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
uint256 startTime
) external onlyOwner onlyWhitelisted(beneficiary) whenNotPaused {
// TODO: Implement vesting schedule creation

require(beneficiary != address(0), "Invalid address");
require(amount > 0, "Invalid amount");
require(vestingDuration > 0, "Invalid vesting duration");
require(vestingDuration >= cliffDuration, "Invalid cliff duration");
require(vestingSchedules[beneficiary].totalAmount == 0, "Vesting already exists");
require(startTime > block.timestamp, "Invalid start time");

VestingSchedule memory schedule = VestingSchedule({
totalAmount: amount,
startTime: startTime,
cliffDuration: cliffDuration,
vestingDuration: vestingDuration,
amountClaimed: 0,
revoked: false
});

vestingSchedules[beneficiary] = schedule;

require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");

emit VestingScheduleCreated(beneficiary, amount);



}

function calculateVestedAmount(
address beneficiary

) public view returns (uint256) {




// TODO: Implement vested amount calculation
VestingSchedule memory schedule = vestingSchedules[beneficiary];

if (schedule.totalAmount == 0 || schedule.revoked) {
return 0;
}

if (block.timestamp < schedule.startTime + schedule.cliffDuration) {
return 0;
}

if (block.timestamp >= schedule.startTime + schedule.vestingDuration) {
return schedule.totalAmount;
}

uint256 timeFromStart = block.timestamp - schedule.startTime;

uint256 vestedAmount = (schedule.totalAmount * timeFromStart) / schedule.vestingDuration;

return vestedAmount;





}

function claimVestedTokens() external nonReentrant whenNotPaused {
// TODO: Implement token claiming
address beneficiary = msg.sender;
VestingSchedule storage schedule = vestingSchedules[beneficiary];

require(schedule.totalAmount > 0, "No vesting schedule");
require(!schedule.revoked, "Vesting revoked");

uint256 vestedAmount = calculateVestedAmount(beneficiary);
uint256 claimableAmount = vestedAmount - schedule.amountClaimed;

require(claimableAmount > 0, "No tokens to claim");

schedule.amountClaimed = vestedAmount;

require(token.transfer(beneficiary, claimableAmount), "Transfer failed");

emit TokensClaimed(beneficiary, claimableAmount);



}

function revokeVesting(address beneficiary) external onlyOwner {
// TODO: Implement vesting revocation
VestingSchedule storage schedule = vestingSchedules[beneficiary];

require(schedule.totalAmount > 0, "No vesting schedule");
require(!schedule.revoked, "Vesting already revoked");

uint256 vestedAmount = calculateVestedAmount(beneficiary);
uint256 unclaimedAmount = schedule.totalAmount - vestedAmount;

schedule.revoked = true;

if (unclaimedAmount > 0) {
require(token.transfer(owner(), unclaimedAmount), "Transfer failed");
}

emit VestingRevoked(beneficiary);



}

Expand Down Expand Up @@ -145,4 +224,4 @@ Solution template (key points to implement):
- Calculate and transfer unvested tokens back
- Mark schedule as revoked
- Emit event
*/
*/