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
6 changes: 3 additions & 3 deletions challenge-1-vesting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ OpenGuild Labs makes the repository to introduce OpenHack workshop participants

Add your information to the below list to officially participate in the workshop challenge (This is the first mission of the whole workshop)

| Emoji | Name | Github Username | Occupations |
| ----- | ---- | ------------------------------------- | ----------- |
| 🎅 | Ippo | [NTP-996](https://github.com/NTP-996) | DevRel |
| Emoji | Name | Github Username | Occupations |
| ----- | ---- | ------------------------------------- | ----------- |
| 👨🏻‍💻 | Toheeb | [Horlarmmy](https://github.com/Horlarmmy) | Software Engineer |

## 💻 Local development environment setup

Expand Down
77 changes: 72 additions & 5 deletions challenge-1-vesting/contracts/TokenVesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,25 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
struct VestingSchedule {
// TODO: Define the vesting schedule struct
uint256 totalAmount; // Total tokens to be vested
uint256 startTime; // Vesting start time
uint256 cliffDuration; // Cliff duration in seconds
uint256 vestingDuration; // Total vesting duration in seconds
uint256 amountClaimed; // Amount of tokens already claimed
bool revoked; // Whether the vesting schedule is revoked
}

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

IERC20 public immutable token;

// 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 @@ -51,8 +59,9 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
event BeneficiaryRemovedFromWhitelist(address indexed beneficiary);

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

// TODO: Initialize the contract
require(tokenAddress != address(0), "Invalid token address");
token = IERC20(tokenAddress);
}

// Modifier to check if beneficiary is whitelisted
Expand Down Expand Up @@ -80,21 +89,79 @@ 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 beneficiary address");
require(amount > 0, "Amount must be greater than 0");
require(vestingDuration > cliffDuration, "Vesting duration must be greater than cliff");

VestingSchedule storage schedule = vestingSchedules[beneficiary];
require(schedule.totalAmount == 0, "Vesting schedule already exists");

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

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

emit VestingScheduleCreated(beneficiary, amount);
}

function calculateVestedAmount(
address beneficiary
) public view returns (uint256) {
// TODO: Implement vested amount calculation
VestingSchedule storage schedule = vestingSchedules[beneficiary];
uint256 currentTime = block.timestamp;

if (currentTime < schedule.startTime + schedule.cliffDuration || schedule.revoked) {
return 0;
}

uint256 elapsedTime = currentTime - schedule.startTime;
uint256 totalVestingTime = schedule.vestingDuration;

if (elapsedTime >= totalVestingTime) {
return schedule.totalAmount - schedule.amountClaimed;
}

uint256 vestedAmount = (schedule.totalAmount * elapsedTime) / totalVestingTime;
return vestedAmount - schedule.amountClaimed;
}

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

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

schedule.amountClaimed += claimableAmount;
require(token.transfer(msg.sender, claimableAmount), "Token transfer failed");

emit TokensClaimed(msg.sender, claimableAmount);
}

function revokeVesting(address beneficiary) external onlyOwner {
// TODO: Implement vesting revocation
VestingSchedule storage schedule = vestingSchedules[beneficiary];
require(schedule.totalAmount > 0, "No vesting schedule found");
require(!schedule.revoked, "Vesting schedule already revoked");

uint256 vestedAmount = calculateVestedAmount(beneficiary);
uint256 unvestedAmount = schedule.totalAmount - schedule.amountClaimed - vestedAmount;
schedule.revoked = true;

if (unvestedAmount > 0) {
require(token.transfer(msg.sender, unvestedAmount), "Token transfer failed");
}

emit VestingRevoked(beneficiary);
}

function pause() external onlyOwner {
Expand Down Expand Up @@ -145,4 +212,4 @@ Solution template (key points to implement):
- Calculate and transfer unvested tokens back
- Mark schedule as revoked
- Emit event
*/
*/
86 changes: 85 additions & 1 deletion challenge-2-yield-farm/contracts/yeild.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ contract YieldFarm is ReentrancyGuard, Ownable {
uint256 _rewardRate
) Ownable(msg.sender) {
// TODO: Initialize contract state

require(_lpToken != address(0), "Invalid LP token address");
require(_rewardToken != address(0), "Invalid reward token address");
require(_rewardRate > 0, "Reward rate must be greater than 0");

lpToken = IERC20(_lpToken);
rewardToken = IERC20(_rewardToken);
rewardRate = _rewardRate;
lastUpdateTime = block.timestamp;
}

function updateReward(address _user) internal {
Expand All @@ -90,6 +99,12 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 1. Calculate rewards since last update
// 2. Apply boost multiplier
// 3. Return total pending rewards

if (totalStaked == 0) {
return rewardPerTokenStored;
}
uint256 timeElapsed = block.timestamp - lastUpdateTime;
return rewardPerTokenStored + (timeElapsed * rewardRate * 1e18) / totalStaked;
}

function earned(address _user) public view returns (uint256) {
Expand All @@ -98,6 +113,11 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 1. Calculate rewards since last update
// 2. Apply boost multiplier
// 3. Return total pending rewards

UserInfo storage user = userInfo[_user];
uint256 boostRewardPerToken = rewardPerToken() * calculateBoostMultiplier(_user) / 100;
uint256 userReward = (user.amount * boostRewardPerToken) / 1e18;
return userReward - user.rewardDebt + user.pendingRewards;
}

/**
Expand All @@ -111,6 +131,21 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 2. Transfer LP tokens from user
// 3. Update user info and total staked amount
// 4. Emit Staked event

require(_amount > 0, "Cannot stake 0");

updateReward(msg.sender);

UserInfo storage user = userInfo[msg.sender];
if (user.amount == 0) {
user.startTime = block.timestamp;
}
user.amount += _amount;
totalStaked += _amount;

lpToken.transferFrom(msg.sender, address(this), _amount);

emit Staked(msg.sender, _amount);
}

/**
Expand All @@ -124,6 +159,18 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 2. Transfer LP tokens to user
// 3. Update user info and total staked amount
// 4. Emit Withdrawn event

UserInfo storage user = userInfo[msg.sender];
require(user.amount >= _amount, "Insufficient balance");

updateReward(msg.sender);

user.amount -= _amount;
totalStaked -= _amount;

lpToken.transfer(msg.sender, _amount);

emit Withdrawn(msg.sender, _amount);
}

/**
Expand All @@ -136,6 +183,17 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 2. Transfer rewards to user
// 3. Update user reward debt
// 4. Emit RewardsClaimed event

updateReward(msg.sender);

UserInfo storage user = userInfo[msg.sender];
uint256 rewards = user.pendingRewards;
require(rewards > 0, "No rewards to claim");

user.pendingRewards = 0;
rewardToken.transfer(msg.sender, rewards);

emit RewardsClaimed(msg.sender, rewards);
}

/**
Expand All @@ -147,6 +205,19 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 1. Transfer all LP tokens back to user
// 2. Reset user info
// 3. Emit EmergencyWithdrawn event

UserInfo storage user = userInfo[msg.sender];
uint256 amount = user.amount;

require(amount > 0, "Nothing to withdraw");

user.amount = 0;
user.pendingRewards = 0;
totalStaked -= amount;

lpToken.transfer(msg.sender, amount);

emit EmergencyWithdrawn(msg.sender, amount);
}

/**
Expand All @@ -161,6 +232,14 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// Requirements:
// 1. Calculate staking duration
// 2. Return appropriate multiplier based on duration thresholds

require(_user != address(0), "Invalid user address");
uint256 stakingDuration = block.timestamp - userInfo[_user].startTime;

if (stakingDuration >= BOOST_THRESHOLD_3) return 200; // 2x boost
if (stakingDuration >= BOOST_THRESHOLD_2) return 150; // 1.5x boost
if (stakingDuration >= BOOST_THRESHOLD_1) return 125; // 1.25x boost
return 100; // No boost
}

/**
Expand All @@ -172,8 +251,13 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// Requirements:
// 1. Update rewards before changing rate
// 2. Set new reward rate
}

require(_newRate > 0, "Reward rate must be greater than 0");

updateReward(address(0));
rewardRate = _newRate;
}

/**
* @notice View function to see pending rewards for a user
* @param _user Address of the user
Expand Down
15 changes: 14 additions & 1 deletion challenge-2-yield-farm/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion challenge-2-yield-farm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"hardhat": "^2.22.17"
},
"dependencies": {
"@openzeppelin/contracts": "^5.1.0"
"@openzeppelin/contracts": "^5.1.0",
"dotenv": "^16.5.0"
}
}
6 changes: 6 additions & 0 deletions challenge-3-frontend/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export default function Home() {
<li className="mb-2">
<Link href="/mint-redeem-lst-bifrost">Mint/Redeem LST Bifrost</Link>
</li>
<li className="mb-2">
<Link href="/token-vesting">Token Vesting</Link>
</li>
<li className="mb-2">
<Link href="/yield-farm">Yield Farm</Link>
</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
Expand Down
Empty file.
Empty file.
17 changes: 17 additions & 0 deletions challenge-3-frontend/app/yield-farm/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Skeleton } from "@/components/ui/skeleton"

export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return (
<div className="flex flex-col gap-8 max-w-[768px] mx-auto min-h-screen items-center justify-center">
<Skeleton className="w-[40px] h-[40px] rounded-md" />
<div className="flex flex-col md:flex-row gap-2">
<Skeleton className="w-[100px] h-[40px] rounded-md" />
<Skeleton className="w-[100px] h-[40px] rounded-md" />
<Skeleton className="w-[100px] h-[40px] rounded-md" />
<Skeleton className="w-[100px] h-[40px] rounded-md" />
</div>
<Skeleton className="w-full h-[300px] rounded-md" />
</div>
)
}
15 changes: 15 additions & 0 deletions challenge-3-frontend/app/yield-farm/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";
import YieldFarm from "@/components/yield-farm";
import SigpassKit from "@/components/sigpasskit";
import Navbar from "@/components/navbar";

export default function YieldFarmPage() {
return (
<div className="flex flex-col gap-8 max-w-[768px] mx-auto min-h-screen items-center justify-center">
<SigpassKit />
<Navbar />
<h1 className="text-2xl font-bold">Yield Farming</h1>
<YieldFarm />
</div>
);
}
Empty file.
Loading