Skip to content

Aji Guruh Prasetyo | Register for OpenGuild x Encode Club Solidity Challenges #12

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

</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 |
| ----- | -------- | ----------------------------------------------- | ---------------- |
| 🎅 | Aji Guruh Prasetyo | [ajiguruhprasetyo](https://github.com/ajiguruhprasetyo) | 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
121 changes: 104 additions & 17 deletions challenge-1-vesting/contracts/TokenVesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
struct VestingSchedule {
// TODO: Define the vesting schedule struct
uint256 amount;
uint256 startDate;
uint256 cliffDuration;
uint256 vestingDuration;
uint256 amountClaim;
bool revokeData;
}

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


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

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

// Events
event VestingScheduleCreated(address indexed beneficiary, uint256 amount);
Expand All @@ -51,8 +51,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 @@ -77,24 +77,111 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard {
uint256 amount,
uint256 cliffDuration,
uint256 vestingDuration,
uint256 startTime
uint256 startDate
) external onlyOwner onlyWhitelisted(beneficiary) whenNotPaused {
// TODO: Implement vesting schedule creation
require(beneficiary != address(0), "Invalid beneficiary address");
require(amount > 0, "Amount must be greather than 0");
require(
vestingDuration > 0,
"Vesting duration must be greather than 0"
);
require(
vestingDuration >= cliffDuration,
"Vesting duration must be >= cliff"
);
require(
vestingSchedules[beneficiary].amount == 0,
"Vesting Schedule already exists"
);
require(
startDate >= block.timestamp,
"Start time must be in the future"
);

vestingSchedules[beneficiary] = VestingSchedule({
amount: amount,
startDate: startDate,
cliffDuration: cliffDuration,
vestingDuration: vestingDuration,
amountClaim: 0,
revokeData: false
});

require(
token.transferFrom(msg.sender, address(this), amount),
"Transfer has failed"
);
emit VestingScheduleCreated(beneficiary, amount);
}

function calculateVestedAmount(
address beneficiary
) public view returns (uint256) {
// TODO: Implement vested amount calculation
VestingSchedule memory scheduleByBeneficiary = vestingSchedules[
beneficiary
];

if (
scheduleByBeneficiary.amount == 0 ||
scheduleByBeneficiary.revokeData
) {
return 0;
}

if (
block.timestamp <
scheduleByBeneficiary.startDate +
scheduleByBeneficiary.cliffDuration
) {
return 0;
}

if (
block.timestamp >=
scheduleByBeneficiary.startDate +
scheduleByBeneficiary.vestingDuration
) {
return scheduleByBeneficiary.amount;
}

uint256 timestampFromStart = block.timestamp -
scheduleByBeneficiary.startDate;
uint256 data = (scheduleByBeneficiary.amount * timestampFromStart) /
scheduleByBeneficiary.vestingDuration;

return data;
}

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

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

scheduleData.amountClaim += amountClaim;
require(token.transfer(msg.sender, amountClaim), "Transfer failed");

emit TokensClaimed(msg.sender, amountClaim);
}

function revokeVesting(address beneficiary) external onlyOwner {
// TODO: Implement vesting revocation
VestingSchedule storage scheduleData = vestingSchedules[beneficiary];
require(scheduleData.amount > 0, "No data vesting schedule");
require(!scheduleData.revokeData, "Already revoke vesting");

uint256 vestedAmount = calculateVestedAmount(beneficiary);
uint256 amountClaim = scheduleData.amount - vestedAmount;

scheduleData.revokeData = true;

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

emit VestingRevoked(beneficiary);
}

function pause() external onlyOwner {
Expand Down Expand Up @@ -145,4 +232,4 @@ Solution template (key points to implement):
- Calculate and transfer unvested tokens back
- Mark schedule as revoked
- Emit event
*/
*/
4 changes: 2 additions & 2 deletions challenge-1-vesting/test/vesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe("TokenVesting", function () {
);

const schedule = await vesting.vestingSchedules(beneficiary.address);
expect(schedule.totalAmount).to.equal(amount);
expect(schedule.amount).to.equal(amount);
});

it("Should fail for non-whitelisted beneficiary", async function () {
Expand Down Expand Up @@ -139,7 +139,7 @@ describe("TokenVesting", function () {
it("Should allow owner to revoke vesting", async function () {
await vesting.revokeVesting(beneficiary.address);
const schedule = await vesting.vestingSchedules(beneficiary.address);
expect(schedule.revoked).to.be.true;
expect(schedule.revokeData).to.be.true;
});

it("Should not allow non-owner to revoke vesting", async function () {
Expand Down
117 changes: 75 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,97 @@ 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;
}
uint256 timeElapsed = block.timestamp - lastUpdateTime;
return
rewardPerTokenStored +
(timeElapsed * 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 boost = calculateBoostMultiplier(_user);
uint256 rewardCalculation = rewardPerToken() - user.rewardDebt;
uint256 currentReward = ((user.amount * rewardCalculation) / 1e18);
uint256 boostedReward = (currentReward * boost) / 100;
return user.pendingRewards + boostedReward;
}

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

updateReward(msg.sender);

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

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

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

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
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);
}

/**
* @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);
UserInfo storage user = userInfo[msg.sender];

uint256 reward = user.pendingRewards;
require(reward > 0, "No rewards");

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

emit RewardsClaimed(msg.sender, reward);
}

/**
* @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 userData = userInfo[msg.sender];
uint256 amountWithdraw = userData.amount;

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

userData.amount = 0;
userData.rewardDebt = 0;
userData.pendingRewards = 0;
totalStaked -= amountWithdraw;

lpToken.transfer(msg.sender, amountWithdraw);

emit EmergencyWithdrawn(msg.sender, amountWithdraw);
}

/**
Expand All @@ -157,21 +189,22 @@ 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 durationStaking = block.timestamp - userInfo[_user].startTime;

if (durationStaking >= BOOST_THRESHOLD_3) return 200;
if (durationStaking >= BOOST_THRESHOLD_2) return 150;
if (durationStaking >= BOOST_THRESHOLD_1) return 125;

return 100;
}

/**
* @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
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.

Loading