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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@

</div>

## Participant Registration

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 |
| ----- | -------- | ----------------------------------------------- | ---------------- |
| 🐉 | Wade | [groundedsage](https://github.com/groundedsage) | OpenGuild Member |
## (Optional) Setup environment and register for the challenges

TLDR: If you are not familiar with Git & Github, follow these steps below to fork and setup your own repository.
Expand Down
2 changes: 1 addition & 1 deletion challenge-1-vesting/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 |
| 🐉 | Wade | [groundedsage](https://github.com/groundedsage) | OpenGuild Member |

## 💻 Local development environment setup

Expand Down
98 changes: 80 additions & 18 deletions challenge-1-vesting/contracts/TokenVesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,20 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

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

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

mapping(address => VestingSchedule) public vestingSchedules;
mapping(address => bool) public whitelist;

// Mapping from beneficiary to vesting schedule
// TODO: Add state variables

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


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

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

require(tokenAddress != address(0), "Invalid token address");
token = IERC20(tokenAddress);
}

// Modifier to check if beneficiary is whitelisted
Expand All @@ -79,22 +80,81 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
uint256 vestingDuration,
uint256 startTime
) external onlyOwner onlyWhitelisted(beneficiary) whenNotPaused {
// TODO: Implement vesting schedule creation
require(amount > 0, "Amount must be greater than 0");
require(vestingDuration >= cliffDuration, "Vesting duration must be >= cliff duration");

vestingSchedules[beneficiary] = VestingSchedule({
totalAmount: amount,
startTime: startTime,
cliffDuration: cliffDuration,
vestingDuration: vestingDuration,
claimedAmount: 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
function calculateVestedAmount(address beneficiary) public view returns (uint256) {
VestingSchedule memory schedule = vestingSchedules[beneficiary];
uint256 currentTime = block.timestamp;
uint256 vested;

if (currentTime < schedule.startTime + schedule.cliffDuration) {
vested = 0;
} else if (currentTime >= schedule.startTime + schedule.vestingDuration) {
vested = schedule.totalAmount;
} else {
uint256 timePassed = currentTime - schedule.startTime;
vested = (schedule.totalAmount * timePassed) / schedule.vestingDuration;
}

if (vested < schedule.claimedAmount) {
return 0;
}

return vested - schedule.claimedAmount;
}

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

require(!schedule.revoked, "Vesting has been revoked");

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

schedule.claimedAmount += 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.revoked, "Vesting already revoked");

uint256 vestedSoFar;
uint256 currentTime = block.timestamp;

if (currentTime < schedule.startTime + schedule.cliffDuration) {
vestedSoFar = 0;
} else if (currentTime >= schedule.startTime + schedule.vestingDuration) {
vestedSoFar = schedule.totalAmount;
} else {
vestedSoFar = (schedule.totalAmount * (currentTime - schedule.startTime)) / schedule.vestingDuration;
}

uint256 unvestedTokens = schedule.totalAmount - vestedSoFar;

schedule.revoked = true;

require(token.transfer(owner(), unvestedTokens), "Token transfer failed");

emit VestingRevoked(beneficiary);
}

function pause() external onlyOwner {
Expand All @@ -109,6 +169,8 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
/*
Solution template (key points to implement):



1. VestingSchedule struct should contain:
- Total amount
- Start time
Expand Down
126 changes: 84 additions & 42 deletions challenge-2-yield-farm/contracts/yeild.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ contract YieldFarm is ReentrancyGuard, Ownable {
address _rewardToken,
uint256 _rewardRate
) Ownable(msg.sender) {
// TODO: Initialize contract state
lpToken = IERC20(_lpToken);
rewardToken = IERC20(_rewardToken);
rewardRate = _rewardRate;
lastUpdateTime = block.timestamp;
}

function updateReward(address _user) internal {
Expand All @@ -85,68 +88,103 @@ contract YieldFarm is ReentrancyGuard, Ownable {
}

function rewardPerToken() public view returns (uint256) {
// TODO: Implement pending rewards calculation
// Requirements:
// 1. Calculate rewards since last update
// 2. Apply boost multiplier
// 3. Return total pending rewards
if (totalStaked == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / totalStaked);
}

function earned(address _user) public view returns (uint256) {
// TODO: Implement pending rewards calculation
// Requirements:
// 1. Calculate rewards since last update
// 2. Apply boost multiplier
// 3. Return total pending rewards
UserInfo storage user = userInfo[_user];

uint256 newReward = (user.amount * (rewardPerToken() - user.rewardDebt)) / 1e18;

uint256 boost = calculateBoostMultiplier(_user);
newReward = (newReward * boost) / 100;
return user.pendingRewards + newReward;
}

/**
* @notice Stake LP tokens into the farm
* @param _amount Amount of LP tokens to stake
*/
function stake(uint256 _amount) external nonReentrant {
// TODO: Implement staking logic
// Requirements:
// 1. Update rewards
// 2. Transfer LP tokens from user
// 3. Update user info and total staked amount
// 4. Emit Staked event
require(_amount > 0, "Cannot stake 0");

// Update the reward information for the user
updateReward(msg.sender);

// If the user has no previous stake, set the start time
if (userInfo[msg.sender].amount == 0) {
userInfo[msg.sender].startTime = block.timestamp;
}

// Transfer LP tokens from the user to this contract
lpToken.transferFrom(msg.sender, address(this), _amount);

// Increase the user's staked amount and update the global total
userInfo[msg.sender].amount += _amount;
totalStaked += _amount;

// Update reward debt to the new total amount
userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * rewardPerTokenStored) / 1e18;

// Emit the staking event
emit Staked(msg.sender, _amount);
}

/**
* @notice Withdraw staked LP tokens
* @param _amount Amount of LP tokens to withdraw
*/
function withdraw(uint256 _amount) external nonReentrant {
// TODO: Implement withdrawal logic
// Requirements:
// 1. Update rewards
// 2. Transfer LP tokens to user
// 3. Update user info and total staked amount
// 4. Emit Withdrawn event
updateReward(msg.sender);

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

userInfo[msg.sender].amount -= _amount;
totalStaked -= _amount;

userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * rewardPerTokenStored) / 1e18;

lpToken.transfer(msg.sender, _amount);

emit Withdrawn(msg.sender, _amount);
}

/**
* @notice Claim pending rewards
*/
function claimRewards() external nonReentrant {
// TODO: Implement reward claiming logic
// Requirements:
// 1. Calculate pending rewards with boost multiplier
// 2. Transfer rewards to user
// 3. Update user reward debt
// 4. Emit RewardsClaimed event
updateReward(msg.sender);

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

userInfo[msg.sender].pendingRewards = 0;

rewardToken.transfer(msg.sender, rewards);

emit RewardsClaimed(msg.sender, rewards);
}

/**
* @notice Emergency withdraw without caring about rewards
*/
function emergencyWithdraw() external nonReentrant {
// TODO: Implement emergency withdrawal
// Requirements:
// 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, "No tokens to withdraw");

user.amount = 0;
user.pendingRewards = 0;
user.rewardDebt = 0;

totalStaked -= amount;

lpToken.transfer(msg.sender, amount);

emit EmergencyWithdrawn(msg.sender, amount);
}

/**
Expand All @@ -157,21 +195,25 @@ contract YieldFarm is ReentrancyGuard, Ownable {
function calculateBoostMultiplier(
address _user
) public view returns (uint256) {
// TODO: Implement boost multiplier calculation
// Requirements:
// 1. Calculate staking duration
// 2. Return appropriate multiplier based on duration thresholds
uint256 stakedDuration = block.timestamp - userInfo[_user].startTime;
if (stakedDuration >= BOOST_THRESHOLD_3) {
return 200; // 2x boost
} else if (stakedDuration >= BOOST_THRESHOLD_2) {
return 150; // 1.5x boost
} else if (stakedDuration >= BOOST_THRESHOLD_1) {
return 125; // 1.25x boost
}
return 100; // 1x boost (no boost)
}

/**
* @notice Update reward rate
* @param _newRate New reward rate per second
*/
function updateRewardRate(uint256 _newRate) external onlyOwner {
// TODO: Implement reward rate update logic
// Requirements:
// 1. Update rewards before changing rate
// 2. Set new reward rate
updateReward(address(0));

rewardRate = _newRate;
}

/**
Expand Down
Loading