Skip to content

Đức Minh | Register for OpenGuild x Encode Club Solidity Challenges #3

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
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 |
| ----- | --------- | ------------------------------------------- | ------------ |
| ❄️ | Đức Minh | [DxcMint868](https://github.com/DxcMint868) | Unemployed |

## 💻 Local development environment setup

Expand Down
120 changes: 107 additions & 13 deletions challenge-1-vesting/contracts/TokenVesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,27 @@ import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

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

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

address public 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 @@ -50,9 +58,9 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
event BeneficiaryWhitelisted(address indexed beneficiary);
event BeneficiaryRemovedFromWhitelist(address indexed beneficiary);

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

constructor(address _tokenAddress) {
// TODO: Initialize the contract
token = _tokenAddress;
}

// Modifier to check if beneficiary is whitelisted
Expand All @@ -76,25 +84,111 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
address beneficiary,
uint256 amount,
uint256 cliffDuration,
uint256 vestingDuration,
uint256 vestDuration,
uint256 startTime
) external onlyOwner onlyWhitelisted(beneficiary) whenNotPaused {
)
external
onlyOwner
onlyWhitelisted(beneficiary)
whenNotPaused
nonReentrant
{
// TODO: Implement vesting schedule creation
require(
startTime > block.timestamp,
"Vesting schedule must start in the future"
);
require(amount > 0, "What are you even trying to vest?");

require(
vestDuration > 0,
"Vest duration must be greater than 0, or this contract will miserably fail, bro"
);

VestingSchedule memory schedule = VestingSchedule(
amount,
startTime,
cliffDuration,
vestDuration,
0,
false
);

vestingSchedules[beneficiary] = schedule;

IERC20(token).transferFrom(owner(), address(this), amount);

emit VestingScheduleCreated(beneficiary, amount);
}

function calculateVestedAmount(
address beneficiary
) public view returns (uint256) {
// TODO: Implement vested amount calculation
VestingSchedule memory schedule = vestingSchedules[beneficiary];
require(schedule.totalAmount > 0, "Vesting schedule not found");

uint256 currTimestamp = block.timestamp;
uint256 cliffEndTimestamp = schedule.startTime + schedule.cliffDuration;

// If current time is before the cliff, return 0
if (currTimestamp <= cliffEndTimestamp) {
return 0;
}

// If vesting is fully completed, return total vested amount
if (currTimestamp >= schedule.startTime + schedule.vestDuration) {
return schedule.totalAmount;
}

// Calculate vested amount
uint256 vestedTime = currTimestamp - schedule.startTime;
uint256 vestedAmount = (schedule.totalAmount * vestedTime) /
schedule.vestDuration;

return vestedAmount;
}

function claimVestedTokens() external nonReentrant whenNotPaused {
// TODO: Implement token claiming
function claimVestedTokens() external whenNotPaused nonReentrant {
// TODO: Implement token claiming
address beneficiary = _msgSender();

VestingSchedule memory schedule = vestingSchedules[beneficiary];
require(!schedule.revoked, "Vesting schedule revoked");

uint256 vestedAmount = calculateVestedAmount(beneficiary);
uint256 claimableAmount = vestedAmount - schedule.amountClaimed;
if (claimableAmount == 0) {
revert("No tokens to claim");
}

vestingSchedules[beneficiary].amountClaimed = vestedAmount;
emit TokensClaimed(beneficiary, claimableAmount);

IERC20(token).transfer(beneficiary, claimableAmount);
}

function revokeVesting(address beneficiary) external onlyOwner {
function revokeVesting(
address beneficiary
) external onlyOwner nonReentrant {
// TODO: Implement vesting revocation

VestingSchedule memory schedule = vestingSchedules[beneficiary];
require(!schedule.revoked, "Already revoked");

schedule.revoked = true;
vestingSchedules[beneficiary] = schedule;
emit VestingRevoked(beneficiary);

uint256 vestedAmount = calculateVestedAmount(beneficiary);
uint256 unclaimedAmount = vestedAmount - schedule.amountClaimed;
if (unclaimedAmount > 0) {
IERC20(token).transfer(beneficiary, unclaimedAmount);
}
if (schedule.totalAmount - vestedAmount > 0) {
IERC20(token).transfer(
owner(),
schedule.totalAmount - vestedAmount
);
}
}

function pause() external onlyOwner {
Expand Down Expand Up @@ -145,4 +239,4 @@ Solution template (key points to implement):
- Calculate and transfer unvested tokens back
- Mark schedule as revoked
- Emit event
*/
*/
115 changes: 108 additions & 7 deletions challenge-2-yield-farm/contracts/yeild.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,14 @@ contract YieldFarm is ReentrancyGuard, Ownable {
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 {
rewardPerTokenStored = rewardPerToken();
rewardPerTokenStored = rewardPerToken(); // get the current reward per token
lastUpdateTime = block.timestamp;

if (_user != address(0)) {
Expand All @@ -90,6 +94,15 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 1. Calculate rewards since last update
// 2. Apply boost multiplier
// 3. Return total pending rewards
if (totalStaked == 0) {
return 0;
}

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

function earned(address _user) public view returns (uint256) {
Expand All @@ -98,19 +111,47 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 1. Calculate rewards since last update
// 2. Apply boost multiplier
// 3. Return total pending rewards
UserInfo memory user = userInfo[_user];
uint256 boostMultiplier = calculateBoostMultiplier(_user);
uint256 newRewardPerToken = rewardPerToken();
uint256 newReward = (user.amount * newRewardPerToken) /
1e18 -
user.rewardDebt;

return (newReward * boostMultiplier) / 100;
}

/**
* @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");

address userAddress = msg.sender;
UserInfo storage user = userInfo[userAddress];

// Update user's staking info
if (user.amount == 0) {
// First time staking, set start time for boost calculation
user.startTime = block.timestamp;
}

uint256 claimableRewardTokens = earned(userAddress);
if (claimableRewardTokens > 0) {
rewardToken.transfer(userAddress, claimableRewardTokens);
}

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

// Update user's staked amount and total staked in contract
updateReward(userAddress);

user.amount += _amount;
totalStaked += _amount;

emit Staked(userAddress, _amount);
}

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

address userAddr = msg.sender;
UserInfo storage user = userInfo[userAddr];

require(user.amount >= _amount, "Insufficient balance");

uint256 claimableRewardTokens = earned(userAddr);
if (claimableRewardTokens > 0) {
user.pendingRewards = 0;
rewardToken.transfer(userAddr, claimableRewardTokens);
}

updateReward(userAddr);

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

if (user.amount == 0) {
user.startTime = 0;
}

emit Withdrawn(userAddr, _amount);

lpToken.transfer(userAddr, _amount);
}

/**
Expand All @@ -136,6 +201,14 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 2. Transfer rewards to user
// 3. Update user reward debt
// 4. Emit RewardsClaimed event
address userAddr = msg.sender;

uint256 claimableRewardTokens = earned(userAddr);
if (claimableRewardTokens > 0) {
updateReward(userAddr);
rewardToken.transfer(userAddr, claimableRewardTokens);
emit RewardsClaimed(userAddr, claimableRewardTokens);
}
}

/**
Expand All @@ -147,6 +220,18 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// 1. Transfer all LP tokens back to user
// 2. Reset user info
// 3. Emit EmergencyWithdrawn event
address userAddr = msg.sender;
UserInfo storage user = userInfo[userAddr];

lpToken.transfer(userAddr, user.amount);

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

emit EmergencyWithdrawn(userAddr, user.amount);
}

/**
Expand All @@ -161,6 +246,17 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// Requirements:
// 1. Calculate staking duration
// 2. Return appropriate multiplier based on duration thresholds
UserInfo memory user = userInfo[_user];
uint256 stakedDuration = block.timestamp - user.startTime;
if (stakedDuration > BOOST_THRESHOLD_3) {
return 200;
} else if (stakedDuration > BOOST_THRESHOLD_2) {
return 150;
} else if (stakedDuration > BOOST_THRESHOLD_1) {
return 125;
} else {
return 100;
}
}

/**
Expand All @@ -172,6 +268,11 @@ contract YieldFarm is ReentrancyGuard, Ownable {
// Requirements:
// 1. Update rewards before changing rate
// 2. Set new reward rate
// Update rewards before changing rate
updateReward(address(0));

// Set new reward rate
rewardRate = _newRate;
}

/**
Expand Down
14 changes: 14 additions & 0 deletions challenge-2-yield-farm/package-lock.json

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

1 change: 1 addition & 0 deletions challenge-2-yield-farm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"dotenv": "^16.4.7",
"hardhat": "^2.22.17"
},
"dependencies": {
Expand Down
Loading