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 @@ -32,7 +32,7 @@ git clone https://github.com/openguild-labs/open-encode-challenges.git
Go to **Participant Registration** section and register to be the workshop participants. Add the below to the list, replace any placeholder with your personal information.

```
| 🦄 | Name | Github username | Your current occupation |
| 🦄 | Kakooza Vianey | kaksv | Web3 Builder |
```

- Step 5: `Commit` your code and push to the forked Github repository
Expand Down
3 changes: 2 additions & 1 deletion challenge-1-vesting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,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 |
| 🎅 | Kakooza Vianey | [kaksv](https://github.com/kaksv/) | Web3 Developer |

## 💻 Local development environment setup

Expand All @@ -33,7 +34,7 @@ source ~/.bashrc # for bash
source ~/.zshrc # for zsh
```

### 2. Install Node.js and npm using Volta
### 2. Install Node.js and npm using Volta.

```bash
# Install Node.js LTS version
Expand Down
180 changes: 82 additions & 98 deletions challenge-1-vesting/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 @@ -28,73 +6,120 @@ import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
contract TokenVesting is Ownable, Pausable, ReentrancyGuard {
struct VestingSchedule {
// TODO: Define the vesting schedule struct
uint256 totalAmount;
uint256 startTime;
uint256 cliffDuration;
uint256 vestingDuration;
uint256 amountClaimed;
bool revoked;
}

// Token being vested
// TODO: Add state variables


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

// Whitelist of beneficiaries
// TODO: Add state variables

// Events
event VestingScheduleCreated(address indexed beneficiary, uint256 amount);
event TokensClaimed(address indexed beneficiary, uint256 amount);
event VestingRevoked(address indexed beneficiary);
event BeneficiaryWhitelisted(address indexed beneficiary);
event BeneficiaryRemovedFromWhitelist(address indexed beneficiary);

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

constructor(address tokenAddress) Ownable(msg.sender) {
token = IERC20(tokenAddress);
}

// Modifier to check if beneficiary is whitelisted
modifier onlyWhitelisted(address beneficiary) {
require(whitelist[beneficiary], "Beneficiary not whitelisted");
_;
}

function addToWhitelist(address beneficiary) external onlyOwner {
require(beneficiary != address(0), "Invalid address");
whitelist[beneficiary] = true;
emit BeneficiaryWhitelisted(beneficiary);
}

function removeFromWhitelist(address beneficiary) external onlyOwner {
whitelist[beneficiary] = false;
emit BeneficiaryRemovedFromWhitelist(beneficiary);
}

function createVestingSchedule(
address beneficiary,
uint256 amount,
uint256 cliffDuration,
uint256 vestingDuration,
uint256 startTime
) external onlyOwner onlyWhitelisted(beneficiary) whenNotPaused {
// TODO: Implement vesting schedule creation
require(amount > 0, "Amount must be positive");
require(cliffDuration <= vestingDuration, "Cliff exceeds duration");
require(vestingDuration > 0, "Duration must be positive");
require(startTime >= block.timestamp, "Start time in past");
require(vestingSchedules[beneficiary].totalAmount == 0, "Schedule exists");

token.transferFrom(msg.sender, address(this), amount);

vestingSchedules[beneficiary] = VestingSchedule({
totalAmount: amount,
startTime: startTime,
cliffDuration: cliffDuration,
vestingDuration: vestingDuration,
amountClaimed: 0,
revoked: false
});

emit VestingScheduleCreated(beneficiary, amount);
}

function calculateVestedAmount(
address beneficiary
) public view returns (uint256) {
// TODO: Implement vested amount calculation
function calculateVestedAmount(address beneficiary) public view returns (uint256) {
VestingSchedule memory schedule = vestingSchedules[beneficiary];
if (schedule.totalAmount == 0) return 0;

uint256 currentTime = block.timestamp;
if (currentTime < schedule.startTime) return 0;

uint256 elapsed = currentTime - schedule.startTime;
if (elapsed < schedule.cliffDuration) return 0;

if (elapsed >= schedule.vestingDuration || schedule.revoked) {
return schedule.totalAmount - schedule.amountClaimed;
}

uint256 vested = (schedule.totalAmount * elapsed) / schedule.vestingDuration;
return vested - schedule.amountClaimed;
}

function claimVestedTokens() external nonReentrant whenNotPaused {
// TODO: Implement token claiming
VestingSchedule storage schedule = vestingSchedules[msg.sender];
require(schedule.totalAmount > 0, "No vesting schedule");

uint256 vestedAmount = calculateVestedAmount(msg.sender);
require(vestedAmount > 0, "No tokens to claim");

schedule.amountClaimed += vestedAmount;
token.transfer(msg.sender, vestedAmount);

emit TokensClaimed(msg.sender, vestedAmount);
}

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, "Already revoked");

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

schedule.totalAmount = vestedAmount;
schedule.revoked = true;

if (unvested > 0) {
token.transfer(owner(), unvested);
}

emit VestingRevoked(beneficiary);
}

function addToWhitelist(address beneficiary) external onlyOwner {
require(beneficiary != address(0), "Invalid address");
whitelist[beneficiary] = true;
emit BeneficiaryWhitelisted(beneficiary);
}

function removeFromWhitelist(address beneficiary) external onlyOwner {
whitelist[beneficiary] = false;
emit BeneficiaryRemovedFromWhitelist(beneficiary);
}

function pause() external onlyOwner {
Expand All @@ -104,45 +129,4 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
function unpause() external onlyOwner {
_unpause();
}
}

/*
Solution template (key points to implement):

1. VestingSchedule struct should contain:
- Total amount
- Start time
- Cliff duration
- Vesting duration
- Amount claimed
- Revoked status

2. State variables needed:
- Mapping of beneficiary address to VestingSchedule
- ERC20 token reference
- Owner/admin address

3. createVestingSchedule should:
- Validate input parameters
- Create new vesting schedule
- Transfer tokens to contract
- Emit event

4. calculateVestedAmount should:
- Check if cliff period has passed
- Calculate linear vesting based on time passed
- Account for already claimed tokens
- Handle revoked status

5. claimVestedTokens should:
- Calculate claimable amount
- Update claimed amount
- Transfer tokens
- Emit event

6. revokeVesting should:
- Only allow admin
- Calculate and transfer unvested tokens back
- Mark schedule as revoked
- Emit event
*/
}
Loading