Commented out stuff to pause functions

This commit is contained in:
2025-08-04 23:00:31 +02:00
parent 945b69deda
commit 7e55515063
3 changed files with 1127 additions and 1079 deletions

View File

@@ -553,101 +553,101 @@ contract PacaFinanceWithBoostAndScheduleBase is Initializable, ReentrancyGuardUp
// emit StakeSaleCancelled(_seller, _stakeId); // emit StakeSaleCancelled(_seller, _stakeId);
// } // }
function createStake(uint256 _amount) external { // function createStake(uint256 _amount) external {
// Scale up for wei comparison, USDC is 1e6 // // Scale up for wei comparison, USDC is 1e6
if (_amount * 1e12 <= minStakeLock) revert AmountBelowMinimum(); // if (_amount * 1e12 <= minStakeLock) revert AmountBelowMinimum();
// Transfer tokens from the user into the contract // // Transfer tokens from the user into the contract
IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); // IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount);
// Check if user has a fixed reward rate set // // Check if user has a fixed reward rate set
uint256 finalRewardRate; // uint256 finalRewardRate;
if (addressFixedRate[msg.sender] > 0) { // if (addressFixedRate[msg.sender] > 0) {
// Use the fixed rate // // Use the fixed rate
finalRewardRate = addressFixedRate[msg.sender]; // finalRewardRate = addressFixedRate[msg.sender];
} else { // } else {
// Default logic, restake = false // // Default logic, restake = false
finalRewardRate = getUserRewardRate(msg.sender, false); // finalRewardRate = getUserRewardRate(msg.sender, false);
} // }
// Create the stake // // Create the stake
stakes[msg.sender].push(Stake({ // stakes[msg.sender].push(Stake({
amount: _amount, // amount: _amount,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
dailyRewardRate: finalRewardRate, // dailyRewardRate: finalRewardRate,
unlockTime: block.timestamp + pool.lockupPeriod, // unlockTime: block.timestamp + pool.lockupPeriod,
complete: false // complete: false
})); // }));
// Update total staked // // Update total staked
pool.totalStaked += _amount; // pool.totalStaked += _amount;
emit Staked(msg.sender, _amount); // emit Staked(msg.sender, _amount);
} // }
/// @notice Restake an expired stake with a bonus daily reward // /// @notice Restake an expired stake with a bonus daily reward
function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external { // function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external {
if (_restakePercentage > 100) revert InvalidRestakePercentage(); // if (_restakePercentage > 100) revert InvalidRestakePercentage();
Stake storage stake = stakes[msg.sender][_stakeIndex]; // Stake storage stake = stakes[msg.sender][_stakeIndex];
// Ensure there is a stake to claim // // Ensure there is a stake to claim
if (stake.amount == 0) revert NothingToClaim(); // if (stake.amount == 0) revert NothingToClaim();
if (block.timestamp < stake.unlockTime) revert StakeLocked(); // if (block.timestamp < stake.unlockTime) revert StakeLocked();
uint256 _amount = stake.amount; // uint256 _amount = stake.amount;
uint rewards = getPoolRewards(msg.sender, _stakeIndex); // uint rewards = getPoolRewards(msg.sender, _stakeIndex);
_amount = _amount + rewards; // _amount = _amount + rewards;
uint256 restake_amount = (_amount * _restakePercentage) / 100; // uint256 restake_amount = (_amount * _restakePercentage) / 100;
uint256 withdraw_amount = _amount - restake_amount; // uint256 withdraw_amount = _amount - restake_amount;
// Update state before external calls // // Update state before external calls
stake.amount = 0; // stake.amount = 0;
stake.complete = true; // stake.complete = true;
// Process withdraw // // Process withdraw
if (withdraw_amount > 0) { // if (withdraw_amount > 0) {
withdrawLiabilities += withdraw_amount; // withdrawLiabilities += withdraw_amount;
if (pool.totalStaked >= withdraw_amount) { // if (pool.totalStaked >= withdraw_amount) {
pool.totalStaked -= withdraw_amount; // pool.totalStaked -= withdraw_amount;
} else { // } else {
pool.totalStaked = 0; // pool.totalStaked = 0;
} // }
// Create temporary the stake for the user to delay withdraw // // Create temporary the stake for the user to delay withdraw
withdrawStake[msg.sender].push(WithdrawStake({ // withdrawStake[msg.sender].push(WithdrawStake({
stakeId: _stakeIndex, // stakeId: _stakeIndex,
amount: withdraw_amount, // amount: withdraw_amount,
unlockTime: block.timestamp + unlockDelay // unlockTime: block.timestamp + unlockDelay
})); // }));
// Emit a detailed event // // Emit a detailed event
emit RewardClaimed(msg.sender, withdraw_amount); // emit RewardClaimed(msg.sender, withdraw_amount);
} // }
// Process restake // // Process restake
if (restake_amount > 0) { // if (restake_amount > 0) {
// Check if user has a fixed reward rate set // // Check if user has a fixed reward rate set
uint256 finalRewardRate; // uint256 finalRewardRate;
if (addressFixedRate[msg.sender] > 0) { // if (addressFixedRate[msg.sender] > 0) {
// Use the fixed rate // // Use the fixed rate
finalRewardRate = addressFixedRate[msg.sender]; // finalRewardRate = addressFixedRate[msg.sender];
} else { // } else {
// restake = true // // restake = true
finalRewardRate = getUserRewardRate(msg.sender, true); // finalRewardRate = getUserRewardRate(msg.sender, true);
} // }
stakes[msg.sender].push(Stake({ // stakes[msg.sender].push(Stake({
amount: restake_amount, // amount: restake_amount,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
dailyRewardRate: finalRewardRate, // dailyRewardRate: finalRewardRate,
unlockTime: block.timestamp + pool.lockupPeriod, // unlockTime: block.timestamp + pool.lockupPeriod,
complete: false // complete: false
})); // }));
emit Staked(msg.sender, restake_amount); // emit Staked(msg.sender, restake_amount);
} // }
} // }
function createStakeForUser(address _user, uint256 _amount) external onlyOwner { function createStakeForUser(address _user, uint256 _amount) external onlyOwner {
if (_amount == 0) revert InvalidAmount(); if (_amount == 0) revert InvalidAmount();
@@ -712,222 +712,222 @@ contract PacaFinanceWithBoostAndScheduleBase is Initializable, ReentrancyGuardUp
return finalRewardRate; return finalRewardRate;
} }
function claimRewards() external nonReentrant { // function claimRewards() external nonReentrant {
uint256 totalReward = 0; // uint256 totalReward = 0;
for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) {
Stake storage stake = stakes[msg.sender][i]; // Stake storage stake = stakes[msg.sender][i];
if (stake.amount > 0) { // if (stake.amount > 0) {
uint rewards = getPoolRewards(msg.sender, i); // uint rewards = getPoolRewards(msg.sender, i);
totalReward = totalReward + rewards; // totalReward = totalReward + rewards;
stake.lastClaimed = block.timestamp; // stake.lastClaimed = block.timestamp;
} // }
} // }
if (totalReward == 0) revert NothingToClaim(); // if (totalReward == 0) revert NothingToClaim();
if (pool.totalRewards < totalReward) revert InsufficientRewards(); // if (pool.totalRewards < totalReward) revert InsufficientRewards();
pool.totalRewards = pool.totalRewards - totalReward; // pool.totalRewards = pool.totalRewards - totalReward;
IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward); // IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward);
emit RewardClaimed(msg.sender, totalReward); // emit RewardClaimed(msg.sender, totalReward);
} // }
function claimStake(uint256 _stakeIndex) external nonReentrant { // function claimStake(uint256 _stakeIndex) external nonReentrant {
// Ensure the stake index is valid // // Ensure the stake index is valid
if (_stakeIndex >= stakes[msg.sender].length) revert InvalidStakeIndex(); // if (_stakeIndex >= stakes[msg.sender].length) revert InvalidStakeIndex();
// Load the stake // // Load the stake
Stake storage stake = stakes[msg.sender][_stakeIndex]; // Stake storage stake = stakes[msg.sender][_stakeIndex];
uint256 _amount = stake.amount; // uint256 _amount = stake.amount;
uint rewards = getPoolRewards(msg.sender, _stakeIndex); // uint rewards = getPoolRewards(msg.sender, _stakeIndex);
_amount = _amount + rewards; // _amount = _amount + rewards;
// Ensure there is a stake to claim // // Ensure there is a stake to claim
if (_amount == 0) revert NothingToClaim(); // if (_amount == 0) revert NothingToClaim();
// Ensure the stake is unlocked (if using lockup periods) // // Ensure the stake is unlocked (if using lockup periods)
if (block.timestamp < stake.unlockTime) revert StakeLocked(); // if (block.timestamp < stake.unlockTime) revert StakeLocked();
// Update state before external calls // // Update state before external calls
stake.amount = 0; // stake.amount = 0;
stake.complete = true; // stake.complete = true;
withdrawLiabilities += _amount; // withdrawLiabilities += _amount;
if (pool.totalStaked >= _amount) { // if (pool.totalStaked >= _amount) {
pool.totalStaked -= _amount; // pool.totalStaked -= _amount;
} else { // } else {
pool.totalStaked = 0; // pool.totalStaked = 0;
} // }
// Create temporary the stake for the user to delay withdraw // // Create temporary the stake for the user to delay withdraw
withdrawStake[msg.sender].push(WithdrawStake({ // withdrawStake[msg.sender].push(WithdrawStake({
stakeId: _stakeIndex, // stakeId: _stakeIndex,
amount: _amount, // amount: _amount,
unlockTime: block.timestamp + unlockDelay // unlockTime: block.timestamp + unlockDelay
})); // }));
// Emit a detailed event // // Emit a detailed event
emit RewardClaimed(msg.sender, _amount); // emit RewardClaimed(msg.sender, _amount);
} // }
/** // /**
* @notice Withdraw a staked amount after its unlock time has passed. // * @notice Withdraw a staked amount after its unlock time has passed.
* @dev Locates the stake by `_stakeIndex`, checks that it's unlocked and non-zero, // * @dev Locates the stake by `_stakeIndex`, checks that it's unlocked and non-zero,
* and transfers tokens to the caller. For vesting stakes (where `_stakeIndex` >= 1e6), // * and transfers tokens to the caller. For vesting stakes (where `_stakeIndex` >= 1e6),
* the stored amount (in 1e18 decimals) is scaled to USDC's 1e6 decimals by dividing by 1e12. // * the stored amount (in 1e18 decimals) is scaled to USDC's 1e6 decimals by dividing by 1e12.
* // *
* Requirements: // * Requirements:
* - Caller must have at least one stake. // * - Caller must have at least one stake.
* - The stake must exist, be unlocked, and have a non-zero amount. // * - The stake must exist, be unlocked, and have a non-zero amount.
* - The contract must have sufficient token balance. // * - The contract must have sufficient token balance.
* // *
* @param _stakeIndex The identifier of the stake to withdraw. // * @param _stakeIndex The identifier of the stake to withdraw.
*/ // */
function withdraw(uint256 _stakeIndex) external nonReentrant { // function withdraw(uint256 _stakeIndex) external nonReentrant {
WithdrawStake[] storage userStakes = withdrawStake[msg.sender]; // WithdrawStake[] storage userStakes = withdrawStake[msg.sender];
if (userStakes.length == 0) revert NoStakesAvailable(); // if (userStakes.length == 0) revert NoStakesAvailable();
//
for (uint256 i = 0; i < userStakes.length; ++i) { // for (uint256 i = 0; i < userStakes.length; ++i) {
WithdrawStake storage stake = userStakes[i]; // WithdrawStake storage stake = userStakes[i];
// Skip already withdrawn stakes (amount == 0) // // Skip already withdrawn stakes (amount == 0)
if (stake.stakeId == _stakeIndex && stake.amount != 0) { // if (stake.stakeId == _stakeIndex && stake.amount != 0) {
if (block.timestamp < stake.unlockTime) revert StakeLocked(); // if (block.timestamp < stake.unlockTime) revert StakeLocked();
//
uint256 _amount = stake.amount; // uint256 _amount = stake.amount;
//
// Convert vesting stake amount to USDC decimals. // // Convert vesting stake amount to USDC decimals.
if (_stakeIndex >= 1e6) { // if (_stakeIndex >= 1e6) {
_amount = _amount / 1e12; // _amount = _amount / 1e12;
} // }
//
uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); // uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this));
if (poolBalance < _amount) revert InsufficientRewards(); // if (poolBalance < _amount) revert InsufficientRewards();
//
// Update state before external calls // // Update state before external calls
// withdrawLiabilities is in 1e18, deduct original amount // // withdrawLiabilities is in 1e18, deduct original amount
withdrawLiabilities -= stake.amount; // withdrawLiabilities -= stake.amount;
stake.amount = 0; // stake.amount = 0;
//
// Transfer tokens // // Transfer tokens
IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount); // IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount);
emit StakeWithdrawn(msg.sender, _amount, _stakeIndex); // emit StakeWithdrawn(msg.sender, _amount, _stakeIndex);
return; // return;
} // }
} // }
//
// Revert if no matching stake with non-zero amount was found // // Revert if no matching stake with non-zero amount was found
revert StakeNotFound(); // revert StakeNotFound();
} // }
/** // /**
* @notice Withdraws vesting tokens after cooldown period // * @notice Withdraws vesting tokens after cooldown period
* @param _vestingId The vesting ID to withdraw // * @param _vestingId The vesting ID to withdraw
*/ // */
function withdrawVestingToken(uint256 _vestingId) external nonReentrant { // function withdrawVestingToken(uint256 _vestingId) external nonReentrant {
WithdrawVesting[] storage userVestings = withdrawVestingActual[msg.sender]; // WithdrawVesting[] storage userVestings = withdrawVestingActual[msg.sender];
if (userVestings.length == 0) revert NoStakesAvailable(); // if (userVestings.length == 0) revert NoStakesAvailable();
//
for (uint256 i = 0; i < userVestings.length; ++i) { // for (uint256 i = 0; i < userVestings.length; ++i) {
WithdrawVesting storage vestingWithdraw = userVestings[i]; // WithdrawVesting storage vestingWithdraw = userVestings[i];
if (vestingWithdraw.vestingId == _vestingId && vestingWithdraw.amount != 0) { // if (vestingWithdraw.vestingId == _vestingId && vestingWithdraw.amount != 0) {
if (block.timestamp < vestingWithdraw.unlockTime) revert StakeLocked(); // if (block.timestamp < vestingWithdraw.unlockTime) revert StakeLocked();
//
uint256 _amount = vestingWithdraw.amount; // uint256 _amount = vestingWithdraw.amount;
address _token = vestingWithdraw.token; // address _token = vestingWithdraw.token;
//
// Check contract has sufficient balance // // Check contract has sufficient balance
uint256 tokenBalance = IERC20(_token).balanceOf(address(this)); // uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
if (tokenBalance < _amount) revert InsufficientRewards(); // if (tokenBalance < _amount) revert InsufficientRewards();
//
// Update state before external calls // // Update state before external calls
vestingWithdraw.amount = 0; // vestingWithdraw.amount = 0;
//
// Decrement withdraw vesting liabilities for this token // // Decrement withdraw vesting liabilities for this token
withdrawVestingLiabilities[_token] -= _amount; // withdrawVestingLiabilities[_token] -= _amount;
//
// Transfer tokens // // Transfer tokens
IERC20(_token).safeTransfer(msg.sender, _amount); // IERC20(_token).safeTransfer(msg.sender, _amount);
emit StakeWithdrawn(msg.sender, _amount, _vestingId); // emit StakeWithdrawn(msg.sender, _amount, _vestingId);
return; // return;
} // }
} // }
//
revert StakeNotFound(); // revert StakeNotFound();
} // }
function compoundAllRewards() external { // function compoundAllRewards() external {
uint256 totalReward = 0; // uint256 totalReward = 0;
for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) {
Stake storage stake = stakes[msg.sender][i]; // Stake storage stake = stakes[msg.sender][i];
if (stake.amount > 0) { // if (stake.amount > 0) {
uint rewards = getPoolRewards(msg.sender, i); // uint rewards = getPoolRewards(msg.sender, i);
totalReward = totalReward + rewards; // totalReward = totalReward + rewards;
stake.lastClaimed = block.timestamp; // stake.lastClaimed = block.timestamp;
} // }
} // }
if (totalReward <= minStakeLock) revert NotEnoughToCompound(); // if (totalReward <= minStakeLock) revert NotEnoughToCompound();
//
// Check if user has a fixed reward rate set // // Check if user has a fixed reward rate set
uint256 finalRewardRate; // uint256 finalRewardRate;
if (addressFixedRate[msg.sender] > 0) { // if (addressFixedRate[msg.sender] > 0) {
// Use the fixed rate // // Use the fixed rate
finalRewardRate = addressFixedRate[msg.sender]; // finalRewardRate = addressFixedRate[msg.sender];
} else { // } else {
// Default logic, restake = false // // Default logic, restake = false
finalRewardRate = getUserRewardRate(msg.sender, false); // finalRewardRate = getUserRewardRate(msg.sender, false);
} // }
stakes[msg.sender].push(Stake({ // stakes[msg.sender].push(Stake({
amount: totalReward, // amount: totalReward,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
dailyRewardRate: finalRewardRate, // dailyRewardRate: finalRewardRate,
unlockTime: block.timestamp + pool.lockupPeriod, // unlockTime: block.timestamp + pool.lockupPeriod,
complete: false // complete: false
})); // }));
pool.totalStaked = pool.totalStaked + totalReward; // pool.totalStaked = pool.totalStaked + totalReward;
emit CompoundRewards(msg.sender, totalReward); // emit CompoundRewards(msg.sender, totalReward);
} // }
function createVesting(address _token, uint256 _amount) external { // function createVesting(address _token, uint256 _amount) external {
if (_amount == 0) revert InvalidAmount(); // if (_amount == 0) revert InvalidAmount();
address oracle = priceOracles[_token]; // address oracle = priceOracles[_token];
if (oracle == address(0)) revert PriceOracleNotSet(); // if (oracle == address(0)) revert PriceOracleNotSet();
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); // IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
uint256 bonus = (_amount * BONUS_PERCENTAGE) / 100; // uint256 bonus = (_amount * BONUS_PERCENTAGE) / 100;
uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * _amount) / 1e18; // uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * _amount) / 1e18;
if (usdPrice <= minStakeLock) revert AmountBelowMinimum(); // if (usdPrice <= minStakeLock) revert AmountBelowMinimum();
// Update user's dollarsVested // // Update user's dollarsVested
dollarsVested[msg.sender] += usdPrice; // dollarsVested[msg.sender] += usdPrice;
// Update token's vestedTotal // // Update token's vestedTotal
vestedTotal[_token] += _amount; // vestedTotal[_token] += _amount;
vestings[msg.sender].push(Vesting({ // vestings[msg.sender].push(Vesting({
amount: _amount, // amount: _amount,
bonus: bonus, // bonus: bonus,
lockedUntil: block.timestamp + lockupDuration, // lockedUntil: block.timestamp + lockupDuration,
claimedAmount: 0, // claimedAmount: 0,
claimedBonus: 0, // claimedBonus: 0,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
createdAt: block.timestamp, // createdAt: block.timestamp,
token: _token, // token: _token,
complete: false, // complete: false,
usdAmount: usdPrice // usdAmount: usdPrice
})); // }));
emit VestingCreated(msg.sender, _amount, bonus); // emit VestingCreated(msg.sender, _amount, bonus);
} // }
function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) { function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) {
Vesting storage vesting = vestings[_user][_vestingIndex]; Vesting storage vesting = vestings[_user][_vestingIndex];
@@ -990,124 +990,124 @@ function withdrawVestingToken(uint256 _vestingId) external nonReentrant {
} }
function claimVesting(uint256 _vestingIndex) external nonReentrant { // function claimVesting(uint256 _vestingIndex) external nonReentrant {
Vesting storage vesting = vestings[msg.sender][_vestingIndex]; // Vesting storage vesting = vestings[msg.sender][_vestingIndex];
if (vesting.complete) revert StakeComplete(); // if (vesting.complete) revert StakeComplete();
uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex); // uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex);
if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount(); // if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount();
uint256 amountToClaim = maxClaim - vesting.claimedAmount; // uint256 amountToClaim = maxClaim - vesting.claimedAmount;
if (amountToClaim == 0) revert NothingToClaim(); // if (amountToClaim == 0) revert NothingToClaim();
vesting.claimedAmount = vesting.claimedAmount + amountToClaim; // vesting.claimedAmount = vesting.claimedAmount + amountToClaim;
if (vesting.claimedAmount >= vesting.amount) { // if (vesting.claimedAmount >= vesting.amount) {
vesting.complete = true; // vesting.complete = true;
} // }
// Update user's dollarsVested // // Update user's dollarsVested
if (dollarsVested[msg.sender] > 0) { // if (dollarsVested[msg.sender] > 0) {
uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18; // uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18;
if (usdPrice >= dollarsVested[msg.sender]) { // if (usdPrice >= dollarsVested[msg.sender]) {
dollarsVested[msg.sender] = 0; // dollarsVested[msg.sender] = 0;
} else { // } else {
dollarsVested[msg.sender] -= usdPrice; // dollarsVested[msg.sender] -= usdPrice;
} // }
} // }
vestedTotal[vesting.token] -= amountToClaim; // vestedTotal[vesting.token] -= amountToClaim;
//
// Add vesting claims to cooldown queue // // Add vesting claims to cooldown queue
withdrawVestingActual[msg.sender].push(WithdrawVesting({ // withdrawVestingActual[msg.sender].push(WithdrawVesting({
vestingId: withdrawVestingCounterActual++, // vestingId: withdrawVestingCounterActual++,
amount: amountToClaim, // amount: amountToClaim,
unlockTime: block.timestamp + unlockDelay, // unlockTime: block.timestamp + unlockDelay,
token: vesting.token // token: vesting.token
})); // }));
//
// Increment withdraw vesting liabilities for this token // // Increment withdraw vesting liabilities for this token
withdrawVestingLiabilities[vesting.token] += amountToClaim; // withdrawVestingLiabilities[vesting.token] += amountToClaim;
emit VestingClaimed(msg.sender, amountToClaim, 0); // emit VestingClaimed(msg.sender, amountToClaim, 0);
} // }
function claimAllVestingByToken(address _token) external nonReentrant { // function claimAllVestingByToken(address _token) external nonReentrant {
uint256 totalReward = 0; // uint256 totalReward = 0;
uint256 vestingsProcessed = 0; // uint256 vestingsProcessed = 0;
for (uint256 i = 0; i < vestings[msg.sender].length; ++i) { // for (uint256 i = 0; i < vestings[msg.sender].length; ++i) {
Vesting storage vesting = vestings[msg.sender][i]; // Vesting storage vesting = vestings[msg.sender][i];
if (vesting.token == _token && !vesting.complete) { // if (vesting.token == _token && !vesting.complete) {
uint256 maxClaim = getUnlockedVesting(msg.sender, i); // uint256 maxClaim = getUnlockedVesting(msg.sender, i);
if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount(); // if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount();
uint256 amountToClaim = maxClaim - vesting.claimedAmount; // uint256 amountToClaim = maxClaim - vesting.claimedAmount;
if (amountToClaim > 0) { // if (amountToClaim > 0) {
vesting.claimedAmount = vesting.claimedAmount + amountToClaim; // vesting.claimedAmount = vesting.claimedAmount + amountToClaim;
totalReward = totalReward + amountToClaim; // totalReward = totalReward + amountToClaim;
vesting.lastClaimed = block.timestamp; // vesting.lastClaimed = block.timestamp;
// Mark vesting as complete if fully claimed // // Mark vesting as complete if fully claimed
if (vesting.claimedAmount >= vesting.amount) { // if (vesting.claimedAmount >= vesting.amount) {
vesting.complete = true; // vesting.complete = true;
} // }
vestingsProcessed++; // vestingsProcessed++;
} // }
} // }
} // }
if (totalReward == 0) revert NothingToClaim(); // if (totalReward == 0) revert NothingToClaim();
// Update user's dollarsVested // // Update user's dollarsVested
if (dollarsVested[msg.sender] > 0) { // if (dollarsVested[msg.sender] > 0) {
uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18; // uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18;
if (usdPrice >= dollarsVested[msg.sender]) { // if (usdPrice >= dollarsVested[msg.sender]) {
dollarsVested[msg.sender] = 0; // dollarsVested[msg.sender] = 0;
} else { // } else {
dollarsVested[msg.sender] -= usdPrice; // dollarsVested[msg.sender] -= usdPrice;
} // }
} // }
// Update vesting total // // Update vesting total
vestedTotal[_token] -= totalReward; // vestedTotal[_token] -= totalReward;
//
// Add vesting claims to cooldown queue // // Add vesting claims to cooldown queue
withdrawVestingActual[msg.sender].push(WithdrawVesting({ // withdrawVestingActual[msg.sender].push(WithdrawVesting({
vestingId: withdrawVestingCounterActual++, // vestingId: withdrawVestingCounterActual++,
amount: totalReward, // amount: totalReward,
unlockTime: block.timestamp + unlockDelay, // unlockTime: block.timestamp + unlockDelay,
token: _token // token: _token
})); // }));
//
// Increment withdraw vesting liabilities for this token // // Increment withdraw vesting liabilities for this token
withdrawVestingLiabilities[_token] += totalReward; // withdrawVestingLiabilities[_token] += totalReward;
emit RewardClaimed(msg.sender, totalReward); // emit RewardClaimed(msg.sender, totalReward);
} // }
function claimBonus(uint256 _vestingIndex) external nonReentrant { // function claimBonus(uint256 _vestingIndex) external nonReentrant {
Vesting storage vesting = vestings[msg.sender][_vestingIndex]; // Vesting storage vesting = vestings[msg.sender][_vestingIndex];
uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex); // uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex);
if (maxBonus < vesting.claimedBonus) revert InvalidClaimAmount(); // if (maxBonus < vesting.claimedBonus) revert InvalidClaimAmount();
uint256 bonusToClaim = maxBonus - vesting.claimedBonus; // uint256 bonusToClaim = maxBonus - vesting.claimedBonus;
if (bonusToClaim == 0) revert NothingToClaim(); // if (bonusToClaim == 0) revert NothingToClaim();
vesting.claimedBonus = vesting.claimedBonus + bonusToClaim; // vesting.claimedBonus = vesting.claimedBonus + bonusToClaim;
withdrawLiabilities += bonusToClaim; // withdrawLiabilities += bonusToClaim;
// IERC20(vesting.token).safeTransfer(msg.sender, bonusToClaim); // // IERC20(vesting.token).safeTransfer(msg.sender, bonusToClaim);
// Create temporary the stake for the user to delay withdraw. // // Create temporary the stake for the user to delay withdraw.
// Add 1e6 to the vesting index to distinguish them from normal stakes. // // Add 1e6 to the vesting index to distinguish them from normal stakes.
withdrawStake[msg.sender].push(WithdrawStake({ // withdrawStake[msg.sender].push(WithdrawStake({
stakeId: _vestingIndex + 1e6, // stakeId: _vestingIndex + 1e6,
amount: bonusToClaim, // amount: bonusToClaim,
unlockTime: block.timestamp + unlockDelay // unlockTime: block.timestamp + unlockDelay
})); // }));
emit BonusClaimed(msg.sender, bonusToClaim); // emit BonusClaimed(msg.sender, bonusToClaim);
} // }
function setPriceOracle(address _token, address _oracle) external onlyOwner { function setPriceOracle(address _token, address _oracle) external onlyOwner {
priceOracles[_token] = _oracle; priceOracles[_token] = _oracle;
@@ -1265,7 +1265,6 @@ function withdrawVestingToken(uint256 _vestingId) external nonReentrant {
} }
/// @notice Function to put a stake for sale. /// @notice Function to put a stake for sale.
/// Sets the original stake amount to 0 to prevent any alterations while for sale. /// Sets the original stake amount to 0 to prevent any alterations while for sale.
/// @param _stakeId The stake to sell. /// @param _stakeId The stake to sell.

View File

@@ -553,100 +553,100 @@ contract PacaFinanceWithBoostAndScheduleBsc is Initializable, ReentrancyGuardUpg
// emit StakeSaleCancelled(_seller, _stakeId); // emit StakeSaleCancelled(_seller, _stakeId);
// } // }
function createStake(uint256 _amount) external { // function createStake(uint256 _amount) external {
if (_amount <= minStakeLock) revert AmountBelowMinimum(); // if (_amount <= minStakeLock) revert AmountBelowMinimum();
// Transfer tokens from the user into the contract // // Transfer tokens from the user into the contract
IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); // IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount);
// Check if user has a fixed reward rate set // // Check if user has a fixed reward rate set
uint256 finalRewardRate; // uint256 finalRewardRate;
if (addressFixedRate[msg.sender] > 0) { // if (addressFixedRate[msg.sender] > 0) {
// Use the fixed rate // // Use the fixed rate
finalRewardRate = addressFixedRate[msg.sender]; // finalRewardRate = addressFixedRate[msg.sender];
} else { // } else {
// Default logic, restake = false // // Default logic, restake = false
finalRewardRate = getUserRewardRate(msg.sender, false); // finalRewardRate = getUserRewardRate(msg.sender, false);
} // }
// Create the stake // // Create the stake
stakes[msg.sender].push(Stake({ // stakes[msg.sender].push(Stake({
amount: _amount, // amount: _amount,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
dailyRewardRate: finalRewardRate, // dailyRewardRate: finalRewardRate,
unlockTime: block.timestamp + pool.lockupPeriod, // unlockTime: block.timestamp + pool.lockupPeriod,
complete: false // complete: false
})); // }));
// Update total staked // // Update total staked
pool.totalStaked += _amount; // pool.totalStaked += _amount;
emit Staked(msg.sender, _amount); // emit Staked(msg.sender, _amount);
} // }
/// @notice Restake an expired stake with a bonus daily reward // /// @notice Restake an expired stake with a bonus daily reward
function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external { // function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external {
if (_restakePercentage > 100) revert InvalidRestakePercentage(); // if (_restakePercentage > 100) revert InvalidRestakePercentage();
Stake storage stake = stakes[msg.sender][_stakeIndex]; // Stake storage stake = stakes[msg.sender][_stakeIndex];
// Ensure there is a stake to claim // // Ensure there is a stake to claim
if (stake.amount == 0) revert NothingToClaim(); // if (stake.amount == 0) revert NothingToClaim();
if (block.timestamp < stake.unlockTime) revert StakeLocked(); // if (block.timestamp < stake.unlockTime) revert StakeLocked();
uint256 _amount = stake.amount; // uint256 _amount = stake.amount;
uint rewards = getPoolRewards(msg.sender, _stakeIndex); // uint rewards = getPoolRewards(msg.sender, _stakeIndex);
_amount = _amount + rewards; // _amount = _amount + rewards;
uint256 restake_amount = (_amount * _restakePercentage) / 100; // uint256 restake_amount = (_amount * _restakePercentage) / 100;
uint256 withdraw_amount = _amount - restake_amount; // uint256 withdraw_amount = _amount - restake_amount;
// Update state before external calls // // Update state before external calls
stake.amount = 0; // stake.amount = 0;
stake.complete = true; // stake.complete = true;
// Process withdraw // // Process withdraw
if (withdraw_amount > 0) { // if (withdraw_amount > 0) {
withdrawLiabilities += withdraw_amount; // withdrawLiabilities += withdraw_amount;
if (pool.totalStaked >= withdraw_amount) { // if (pool.totalStaked >= withdraw_amount) {
pool.totalStaked -= withdraw_amount; // pool.totalStaked -= withdraw_amount;
} else { // } else {
pool.totalStaked = 0; // pool.totalStaked = 0;
} // }
// Create temporary the stake for the user to delay withdraw // // Create temporary the stake for the user to delay withdraw
withdrawStake[msg.sender].push(WithdrawStake({ // withdrawStake[msg.sender].push(WithdrawStake({
stakeId: _stakeIndex, // stakeId: _stakeIndex,
amount: withdraw_amount, // amount: withdraw_amount,
unlockTime: block.timestamp + unlockDelay // unlockTime: block.timestamp + unlockDelay
})); // }));
// Emit a detailed event // // Emit a detailed event
emit RewardClaimed(msg.sender, withdraw_amount); // emit RewardClaimed(msg.sender, withdraw_amount);
} // }
// Process restake // // Process restake
if (restake_amount > 0) { // if (restake_amount > 0) {
// Check if user has a fixed reward rate set // // Check if user has a fixed reward rate set
uint256 finalRewardRate; // uint256 finalRewardRate;
if (addressFixedRate[msg.sender] > 0) { // if (addressFixedRate[msg.sender] > 0) {
// Use the fixed rate // // Use the fixed rate
finalRewardRate = addressFixedRate[msg.sender]; // finalRewardRate = addressFixedRate[msg.sender];
} else { // } else {
// restake = true // // restake = true
finalRewardRate = getUserRewardRate(msg.sender, true); // finalRewardRate = getUserRewardRate(msg.sender, true);
} // }
stakes[msg.sender].push(Stake({ // stakes[msg.sender].push(Stake({
amount: restake_amount, // amount: restake_amount,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
dailyRewardRate: finalRewardRate, // dailyRewardRate: finalRewardRate,
unlockTime: block.timestamp + pool.lockupPeriod, // unlockTime: block.timestamp + pool.lockupPeriod,
complete: false // complete: false
})); // }));
emit Staked(msg.sender, restake_amount); // emit Staked(msg.sender, restake_amount);
} // }
} // }
function createStakeForUser(address _user, uint256 _amount) external onlyOwner { function createStakeForUser(address _user, uint256 _amount) external onlyOwner {
if (_amount == 0) revert InvalidAmount(); if (_amount == 0) revert InvalidAmount();
@@ -711,213 +711,213 @@ contract PacaFinanceWithBoostAndScheduleBsc is Initializable, ReentrancyGuardUpg
return finalRewardRate; return finalRewardRate;
} }
function claimRewards() external nonReentrant { // function claimRewards() external nonReentrant {
uint256 totalReward = 0; // uint256 totalReward = 0;
for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) {
Stake storage stake = stakes[msg.sender][i]; // Stake storage stake = stakes[msg.sender][i];
if (stake.amount > 0) { // if (stake.amount > 0) {
uint rewards = getPoolRewards(msg.sender, i); // uint rewards = getPoolRewards(msg.sender, i);
totalReward = totalReward + rewards; // totalReward = totalReward + rewards;
stake.lastClaimed = block.timestamp; // stake.lastClaimed = block.timestamp;
} // }
} // }
if (totalReward == 0) revert NothingToClaim(); // if (totalReward == 0) revert NothingToClaim();
if (pool.totalRewards < totalReward) revert InsufficientRewards(); // if (pool.totalRewards < totalReward) revert InsufficientRewards();
pool.totalRewards = pool.totalRewards - totalReward; // pool.totalRewards = pool.totalRewards - totalReward;
IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward); // IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward);
emit RewardClaimed(msg.sender, totalReward); // emit RewardClaimed(msg.sender, totalReward);
} // }
function claimStake(uint256 _stakeIndex) external nonReentrant { // function claimStake(uint256 _stakeIndex) external nonReentrant {
// Ensure the stake index is valid // // Ensure the stake index is valid
if (_stakeIndex >= stakes[msg.sender].length) revert InvalidStakeIndex(); // if (_stakeIndex >= stakes[msg.sender].length) revert InvalidStakeIndex();
// Load the stake // // Load the stake
Stake storage stake = stakes[msg.sender][_stakeIndex]; // Stake storage stake = stakes[msg.sender][_stakeIndex];
uint256 _amount = stake.amount; // uint256 _amount = stake.amount;
uint rewards = getPoolRewards(msg.sender, _stakeIndex); // uint rewards = getPoolRewards(msg.sender, _stakeIndex);
_amount = _amount + rewards; // _amount = _amount + rewards;
// Ensure there is a stake to claim // // Ensure there is a stake to claim
if (_amount == 0) revert NothingToClaim(); // if (_amount == 0) revert NothingToClaim();
// Ensure the stake is unlocked (if using lockup periods) // // Ensure the stake is unlocked (if using lockup periods)
if (block.timestamp < stake.unlockTime) revert StakeLocked(); // if (block.timestamp < stake.unlockTime) revert StakeLocked();
// Update state before external calls // // Update state before external calls
stake.amount = 0; // stake.amount = 0;
stake.complete = true; // stake.complete = true;
withdrawLiabilities += _amount; // withdrawLiabilities += _amount;
if (pool.totalStaked >= _amount) { // if (pool.totalStaked >= _amount) {
pool.totalStaked -= _amount; // pool.totalStaked -= _amount;
} else { // } else {
pool.totalStaked = 0; // pool.totalStaked = 0;
} // }
// Create temporary the stake for the user to delay withdraw // // Create temporary the stake for the user to delay withdraw
withdrawStake[msg.sender].push(WithdrawStake({ // withdrawStake[msg.sender].push(WithdrawStake({
stakeId: _stakeIndex, // stakeId: _stakeIndex,
amount: _amount, // amount: _amount,
unlockTime: block.timestamp + unlockDelay // unlockTime: block.timestamp + unlockDelay
})); // }));
// Emit a detailed event // // Emit a detailed event
emit RewardClaimed(msg.sender, _amount); // emit RewardClaimed(msg.sender, _amount);
} // }
/** // /**
* @notice Withdraw a staked amount after its unlock time has passed. // * @notice Withdraw a staked amount after its unlock time has passed.
* @dev Locates the stake by `_stakeIndex`, checks that it's unlocked and non-zero, // * @dev Locates the stake by `_stakeIndex`, checks that it's unlocked and non-zero,
* and transfers tokens to the caller. // * and transfers tokens to the caller.
* // *
* Requirements: // * Requirements:
* - Caller must have at least one stake. // * - Caller must have at least one stake.
* - The stake must exist, be unlocked, and have a non-zero amount. // * - The stake must exist, be unlocked, and have a non-zero amount.
* - The contract must have sufficient token balance. // * - The contract must have sufficient token balance.
* // *
* @param _stakeIndex The identifier of the stake to withdraw. // * @param _stakeIndex The identifier of the stake to withdraw.
*/ // */
function withdraw(uint256 _stakeIndex) external nonReentrant { // function withdraw(uint256 _stakeIndex) external nonReentrant {
WithdrawStake[] storage userStakes = withdrawStake[msg.sender]; // WithdrawStake[] storage userStakes = withdrawStake[msg.sender];
if (userStakes.length == 0) revert NoStakesAvailable(); // if (userStakes.length == 0) revert NoStakesAvailable();
//
for (uint256 i = 0; i < userStakes.length; ++i) { // for (uint256 i = 0; i < userStakes.length; ++i) {
WithdrawStake storage stake = userStakes[i]; // WithdrawStake storage stake = userStakes[i];
if (stake.stakeId == _stakeIndex && stake.amount != 0) { // if (stake.stakeId == _stakeIndex && stake.amount != 0) {
if (block.timestamp < stake.unlockTime) revert StakeLocked(); // if (block.timestamp < stake.unlockTime) revert StakeLocked();
//
uint256 _amount = stake.amount; // uint256 _amount = stake.amount;
uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); // uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this));
if (poolBalance < _amount) revert InsufficientRewards(); // if (poolBalance < _amount) revert InsufficientRewards();
//
// Update state before external calls // // Update state before external calls
withdrawLiabilities -= _amount; // withdrawLiabilities -= _amount;
stake.amount = 0; // stake.amount = 0;
//
// Transfer tokens // // Transfer tokens
IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount); // IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount);
emit StakeWithdrawn(msg.sender, _amount, _stakeIndex); // emit StakeWithdrawn(msg.sender, _amount, _stakeIndex);
return; // return;
} // }
} // }
//
// Revert if no matching stake with non-zero amount was found // // Revert if no matching stake with non-zero amount was found
revert StakeNotFound(); // revert StakeNotFound();
} // }
/** // /**
* @notice Withdraws vesting tokens after cooldown period // * @notice Withdraws vesting tokens after cooldown period
* @param _vestingId The vesting ID to withdraw // * @param _vestingId The vesting ID to withdraw
*/ // */
function withdrawVestingToken(uint256 _vestingId) external nonReentrant { // function withdrawVestingToken(uint256 _vestingId) external nonReentrant {
WithdrawVesting[] storage userVestings = withdrawVestingActual[msg.sender]; // WithdrawVesting[] storage userVestings = withdrawVestingActual[msg.sender];
if (userVestings.length == 0) revert NoStakesAvailable(); // if (userVestings.length == 0) revert NoStakesAvailable();
//
for (uint256 i = 0; i < userVestings.length; ++i) { // for (uint256 i = 0; i < userVestings.length; ++i) {
WithdrawVesting storage vestingWithdraw = userVestings[i]; // WithdrawVesting storage vestingWithdraw = userVestings[i];
if (vestingWithdraw.vestingId == _vestingId && vestingWithdraw.amount != 0) { // if (vestingWithdraw.vestingId == _vestingId && vestingWithdraw.amount != 0) {
if (block.timestamp < vestingWithdraw.unlockTime) revert StakeLocked(); // if (block.timestamp < vestingWithdraw.unlockTime) revert StakeLocked();
//
uint256 _amount = vestingWithdraw.amount; // uint256 _amount = vestingWithdraw.amount;
address _token = vestingWithdraw.token; // address _token = vestingWithdraw.token;
//
// Check contract has sufficient balance // // Check contract has sufficient balance
uint256 tokenBalance = IERC20(_token).balanceOf(address(this)); // uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
if (tokenBalance < _amount) revert InsufficientRewards(); // if (tokenBalance < _amount) revert InsufficientRewards();
//
// Update state before external calls // // Update state before external calls
vestingWithdraw.amount = 0; // vestingWithdraw.amount = 0;
//
// Decrement withdraw vesting liabilities for this token // // Decrement withdraw vesting liabilities for this token
withdrawVestingLiabilities[_token] -= _amount; // withdrawVestingLiabilities[_token] -= _amount;
//
// Transfer tokens // // Transfer tokens
IERC20(_token).safeTransfer(msg.sender, _amount); // IERC20(_token).safeTransfer(msg.sender, _amount);
emit StakeWithdrawn(msg.sender, _amount, _vestingId); // emit StakeWithdrawn(msg.sender, _amount, _vestingId);
return; // return;
} // }
} // }
//
revert StakeNotFound(); // revert StakeNotFound();
} // }
function compoundAllRewards() external { // function compoundAllRewards() external {
uint256 totalReward = 0; // uint256 totalReward = 0;
for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) {
Stake storage stake = stakes[msg.sender][i]; // Stake storage stake = stakes[msg.sender][i];
if (stake.amount > 0) { // if (stake.amount > 0) {
uint rewards = getPoolRewards(msg.sender, i); // uint rewards = getPoolRewards(msg.sender, i);
totalReward = totalReward + rewards; // totalReward = totalReward + rewards;
stake.lastClaimed = block.timestamp; // stake.lastClaimed = block.timestamp;
} // }
} // }
if (totalReward <= minStakeLock) revert NotEnoughToCompound(); // if (totalReward <= minStakeLock) revert NotEnoughToCompound();
// Check if user has a fixed reward rate set // // Check if user has a fixed reward rate set
uint256 finalRewardRate; // uint256 finalRewardRate;
if (addressFixedRate[msg.sender] > 0) { // if (addressFixedRate[msg.sender] > 0) {
// Use the fixed rate // // Use the fixed rate
finalRewardRate = addressFixedRate[msg.sender]; // finalRewardRate = addressFixedRate[msg.sender];
} else { // } else {
// Default logic, restake = false // // Default logic, restake = false
finalRewardRate = getUserRewardRate(msg.sender, false); // finalRewardRate = getUserRewardRate(msg.sender, false);
} // }
stakes[msg.sender].push(Stake({ // stakes[msg.sender].push(Stake({
amount: totalReward, // amount: totalReward,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
dailyRewardRate: finalRewardRate, // dailyRewardRate: finalRewardRate,
unlockTime: block.timestamp + pool.lockupPeriod, // unlockTime: block.timestamp + pool.lockupPeriod,
complete: false // complete: false
})); // }));
pool.totalStaked = pool.totalStaked + totalReward; // pool.totalStaked = pool.totalStaked + totalReward;
emit CompoundRewards(msg.sender, totalReward); // emit CompoundRewards(msg.sender, totalReward);
} // }
function createVesting(address _token, uint256 _amount) external { // function createVesting(address _token, uint256 _amount) external {
if (_amount == 0) revert InvalidAmount(); // if (_amount == 0) revert InvalidAmount();
address oracle = priceOracles[_token]; // address oracle = priceOracles[_token];
if (oracle == address(0)) revert PriceOracleNotSet(); // if (oracle == address(0)) revert PriceOracleNotSet();
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); // IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
uint256 bonus = (_amount * BONUS_PERCENTAGE) / 100; // uint256 bonus = (_amount * BONUS_PERCENTAGE) / 100;
uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * _amount) / 1e18; // uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * _amount) / 1e18;
if (usdPrice <= minStakeLock) revert AmountBelowMinimum(); // if (usdPrice <= minStakeLock) revert AmountBelowMinimum();
// Update user's dollarsVested // // Update user's dollarsVested
dollarsVested[msg.sender] += usdPrice; // dollarsVested[msg.sender] += usdPrice;
// Update token's vestedTotal // // Update token's vestedTotal
vestedTotal[_token] += _amount; // vestedTotal[_token] += _amount;
vestings[msg.sender].push(Vesting({ // vestings[msg.sender].push(Vesting({
amount: _amount, // amount: _amount,
bonus: bonus, // bonus: bonus,
lockedUntil: block.timestamp + lockupDuration, // lockedUntil: block.timestamp + lockupDuration,
claimedAmount: 0, // claimedAmount: 0,
claimedBonus: 0, // claimedBonus: 0,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
createdAt: block.timestamp, // createdAt: block.timestamp,
token: _token, // token: _token,
complete: false, // complete: false,
usdAmount: usdPrice // usdAmount: usdPrice
})); // }));
emit VestingCreated(msg.sender, _amount, bonus); // emit VestingCreated(msg.sender, _amount, bonus);
} // }
function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) { function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) {
Vesting storage vesting = vestings[_user][_vestingIndex]; Vesting storage vesting = vestings[_user][_vestingIndex];
@@ -980,124 +980,124 @@ function withdrawVestingToken(uint256 _vestingId) external nonReentrant {
} }
function claimVesting(uint256 _vestingIndex) external nonReentrant { // function claimVesting(uint256 _vestingIndex) external nonReentrant {
Vesting storage vesting = vestings[msg.sender][_vestingIndex]; // Vesting storage vesting = vestings[msg.sender][_vestingIndex];
if (vesting.complete) revert StakeComplete(); // if (vesting.complete) revert StakeComplete();
uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex); // uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex);
if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount(); // if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount();
uint256 amountToClaim = maxClaim - vesting.claimedAmount; // uint256 amountToClaim = maxClaim - vesting.claimedAmount;
if (amountToClaim == 0) revert NothingToClaim(); // if (amountToClaim == 0) revert NothingToClaim();
vesting.claimedAmount = vesting.claimedAmount + amountToClaim; // vesting.claimedAmount = vesting.claimedAmount + amountToClaim;
if (vesting.claimedAmount >= vesting.amount) { // if (vesting.claimedAmount >= vesting.amount) {
vesting.complete = true; // vesting.complete = true;
} // }
// Update user's dollarsVested // // Update user's dollarsVested
if (dollarsVested[msg.sender] > 0) { // if (dollarsVested[msg.sender] > 0) {
uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18; // uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18;
if (usdPrice >= dollarsVested[msg.sender]) { // if (usdPrice >= dollarsVested[msg.sender]) {
dollarsVested[msg.sender] = 0; // dollarsVested[msg.sender] = 0;
} else { // } else {
dollarsVested[msg.sender] -= usdPrice; // dollarsVested[msg.sender] -= usdPrice;
} // }
} // }
vestedTotal[vesting.token] -= amountToClaim; // vestedTotal[vesting.token] -= amountToClaim;
//
// Add vesting claims to cooldown queue // // Add vesting claims to cooldown queue
withdrawVestingActual[msg.sender].push(WithdrawVesting({ // withdrawVestingActual[msg.sender].push(WithdrawVesting({
vestingId: withdrawVestingCounterActual++, // vestingId: withdrawVestingCounterActual++,
amount: amountToClaim, // amount: amountToClaim,
unlockTime: block.timestamp + unlockDelay, // unlockTime: block.timestamp + unlockDelay,
token: vesting.token // token: vesting.token
})); // }));
//
// Increment withdraw vesting liabilities for this token // // Increment withdraw vesting liabilities for this token
withdrawVestingLiabilities[vesting.token] += amountToClaim; // withdrawVestingLiabilities[vesting.token] += amountToClaim;
emit VestingClaimed(msg.sender, amountToClaim, 0); // emit VestingClaimed(msg.sender, amountToClaim, 0);
} // }
function claimAllVestingByToken(address _token) external nonReentrant { // function claimAllVestingByToken(address _token) external nonReentrant {
uint256 totalReward = 0; // uint256 totalReward = 0;
uint256 vestingsProcessed = 0; // uint256 vestingsProcessed = 0;
for (uint256 i = 0; i < vestings[msg.sender].length; ++i) { // for (uint256 i = 0; i < vestings[msg.sender].length; ++i) {
Vesting storage vesting = vestings[msg.sender][i]; // Vesting storage vesting = vestings[msg.sender][i];
if (vesting.token == _token && !vesting.complete) { // if (vesting.token == _token && !vesting.complete) {
uint256 maxClaim = getUnlockedVesting(msg.sender, i); // uint256 maxClaim = getUnlockedVesting(msg.sender, i);
if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount(); // if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount();
uint256 amountToClaim = maxClaim - vesting.claimedAmount; // uint256 amountToClaim = maxClaim - vesting.claimedAmount;
if (amountToClaim > 0) { // if (amountToClaim > 0) {
vesting.claimedAmount = vesting.claimedAmount + amountToClaim; // vesting.claimedAmount = vesting.claimedAmount + amountToClaim;
totalReward = totalReward + amountToClaim; // totalReward = totalReward + amountToClaim;
vesting.lastClaimed = block.timestamp; // vesting.lastClaimed = block.timestamp;
// Mark vesting as complete if fully claimed // // Mark vesting as complete if fully claimed
if (vesting.claimedAmount >= vesting.amount) { // if (vesting.claimedAmount >= vesting.amount) {
vesting.complete = true; // vesting.complete = true;
} // }
vestingsProcessed++; // vestingsProcessed++;
} // }
} // }
} // }
if (totalReward == 0) revert NothingToClaim(); // if (totalReward == 0) revert NothingToClaim();
// Update user's dollarsVested // // Update user's dollarsVested
if (dollarsVested[msg.sender] > 0) { // if (dollarsVested[msg.sender] > 0) {
uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18; // uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18;
if (usdPrice >= dollarsVested[msg.sender]) { // if (usdPrice >= dollarsVested[msg.sender]) {
dollarsVested[msg.sender] = 0; // dollarsVested[msg.sender] = 0;
} else { // } else {
dollarsVested[msg.sender] -= usdPrice; // dollarsVested[msg.sender] -= usdPrice;
} // }
} // }
// Update vesting total // // Update vesting total
vestedTotal[_token] -= totalReward; // vestedTotal[_token] -= totalReward;
//
// Add vesting claims to cooldown queue // // Add vesting claims to cooldown queue
withdrawVestingActual[msg.sender].push(WithdrawVesting({ // withdrawVestingActual[msg.sender].push(WithdrawVesting({
vestingId: withdrawVestingCounterActual++, // vestingId: withdrawVestingCounterActual++,
amount: totalReward, // amount: totalReward,
unlockTime: block.timestamp + unlockDelay, // unlockTime: block.timestamp + unlockDelay,
token: _token // token: _token
})); // }));
//
// Increment withdraw vesting liabilities for this token // // Increment withdraw vesting liabilities for this token
withdrawVestingLiabilities[_token] += totalReward; // withdrawVestingLiabilities[_token] += totalReward;
emit RewardClaimed(msg.sender, totalReward); // emit RewardClaimed(msg.sender, totalReward);
} // }
function claimBonus(uint256 _vestingIndex) external nonReentrant { // function claimBonus(uint256 _vestingIndex) external nonReentrant {
Vesting storage vesting = vestings[msg.sender][_vestingIndex]; // Vesting storage vesting = vestings[msg.sender][_vestingIndex];
uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex); // uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex);
if (maxBonus < vesting.claimedBonus) revert InvalidClaimAmount(); // if (maxBonus < vesting.claimedBonus) revert InvalidClaimAmount();
uint256 bonusToClaim = maxBonus - vesting.claimedBonus; // uint256 bonusToClaim = maxBonus - vesting.claimedBonus;
if (bonusToClaim == 0) revert NothingToClaim(); // if (bonusToClaim == 0) revert NothingToClaim();
vesting.claimedBonus = vesting.claimedBonus + bonusToClaim; // vesting.claimedBonus = vesting.claimedBonus + bonusToClaim;
withdrawLiabilities += bonusToClaim; // withdrawLiabilities += bonusToClaim;
// IERC20(vesting.token).safeTransfer(msg.sender, bonusToClaim); // // IERC20(vesting.token).safeTransfer(msg.sender, bonusToClaim);
// Create temporary the stake for the user to delay withdraw. // // Create temporary the stake for the user to delay withdraw.
// Add 1e6 to the vesting index to distinguish them from normal stakes. // // Add 1e6 to the vesting index to distinguish them from normal stakes.
withdrawStake[msg.sender].push(WithdrawStake({ // withdrawStake[msg.sender].push(WithdrawStake({
stakeId: _vestingIndex + 1e6, // stakeId: _vestingIndex + 1e6,
amount: bonusToClaim, // amount: bonusToClaim,
unlockTime: block.timestamp + unlockDelay // unlockTime: block.timestamp + unlockDelay
})); // }));
emit BonusClaimed(msg.sender, bonusToClaim); // emit BonusClaimed(msg.sender, bonusToClaim);
} // }
function setPriceOracle(address _token, address _oracle) external onlyOwner { function setPriceOracle(address _token, address _oracle) external onlyOwner {
priceOracles[_token] = _oracle; priceOracles[_token] = _oracle;

View File

@@ -556,101 +556,101 @@ contract PacaFinanceWithBoostAndScheduleSonic is Initializable, ReentrancyGuardU
// emit StakeSaleCancelled(_seller, _stakeId); // emit StakeSaleCancelled(_seller, _stakeId);
// } // }
function createStake(uint256 _amount) external { // function createStake(uint256 _amount) external {
// Scale up for wei comparison, USDC is 1e6 // // Scale up for wei comparison, USDC is 1e6
if (_amount * 1e12 <= minStakeLock) revert AmountBelowMinimum(); // if (_amount * 1e12 <= minStakeLock) revert AmountBelowMinimum();
// Transfer tokens from the user into the contract // // Transfer tokens from the user into the contract
IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); // IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount);
// Check if user has a fixed reward rate set // // Check if user has a fixed reward rate set
uint256 finalRewardRate; // uint256 finalRewardRate;
if (addressFixedRate[msg.sender] > 0) { // if (addressFixedRate[msg.sender] > 0) {
// Use the fixed rate // // Use the fixed rate
finalRewardRate = addressFixedRate[msg.sender]; // finalRewardRate = addressFixedRate[msg.sender];
} else { // } else {
// Default logic, restake = false // // Default logic, restake = false
finalRewardRate = getUserRewardRate(msg.sender, false); // finalRewardRate = getUserRewardRate(msg.sender, false);
} // }
// Create the stake // // Create the stake
stakes[msg.sender].push(Stake({ // stakes[msg.sender].push(Stake({
amount: _amount, // amount: _amount,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
dailyRewardRate: finalRewardRate, // dailyRewardRate: finalRewardRate,
unlockTime: block.timestamp + pool.lockupPeriod, // unlockTime: block.timestamp + pool.lockupPeriod,
complete: false // complete: false
})); // }));
// Update total staked // // Update total staked
pool.totalStaked += _amount; // pool.totalStaked += _amount;
emit Staked(msg.sender, _amount); // emit Staked(msg.sender, _amount);
} // }
/// @notice Restake an expired stake with a bonus daily reward // /// @notice Restake an expired stake with a bonus daily reward
function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external { // function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external {
if (_restakePercentage > 100) revert InvalidRestakePercentage(); // if (_restakePercentage > 100) revert InvalidRestakePercentage();
Stake storage stake = stakes[msg.sender][_stakeIndex]; // Stake storage stake = stakes[msg.sender][_stakeIndex];
// Ensure there is a stake to claim // // Ensure there is a stake to claim
if (stake.amount == 0) revert NothingToClaim(); // if (stake.amount == 0) revert NothingToClaim();
if (block.timestamp < stake.unlockTime) revert StakeLocked(); // if (block.timestamp < stake.unlockTime) revert StakeLocked();
uint256 _amount = stake.amount; // uint256 _amount = stake.amount;
uint rewards = getPoolRewards(msg.sender, _stakeIndex); // uint rewards = getPoolRewards(msg.sender, _stakeIndex);
_amount = _amount + rewards; // _amount = _amount + rewards;
uint256 restake_amount = (_amount * _restakePercentage) / 100; // uint256 restake_amount = (_amount * _restakePercentage) / 100;
uint256 withdraw_amount = _amount - restake_amount; // uint256 withdraw_amount = _amount - restake_amount;
// Update state before external calls // // Update state before external calls
stake.amount = 0; // stake.amount = 0;
stake.complete = true; // stake.complete = true;
// Process withdraw // // Process withdraw
if (withdraw_amount > 0) { // if (withdraw_amount > 0) {
withdrawLiabilities += withdraw_amount; // withdrawLiabilities += withdraw_amount;
if (pool.totalStaked >= withdraw_amount) { // if (pool.totalStaked >= withdraw_amount) {
pool.totalStaked -= withdraw_amount; // pool.totalStaked -= withdraw_amount;
} else { // } else {
pool.totalStaked = 0; // pool.totalStaked = 0;
} // }
// Create temporary the stake for the user to delay withdraw // // Create temporary the stake for the user to delay withdraw
withdrawStake[msg.sender].push(WithdrawStake({ // withdrawStake[msg.sender].push(WithdrawStake({
stakeId: _stakeIndex, // stakeId: _stakeIndex,
amount: withdraw_amount, // amount: withdraw_amount,
unlockTime: block.timestamp + unlockDelay // unlockTime: block.timestamp + unlockDelay
})); // }));
// Emit a detailed event // // Emit a detailed event
emit RewardClaimed(msg.sender, withdraw_amount); // emit RewardClaimed(msg.sender, withdraw_amount);
} // }
// Process restake // // Process restake
if (restake_amount > 0) { // if (restake_amount > 0) {
// Check if user has a fixed reward rate set // // Check if user has a fixed reward rate set
uint256 finalRewardRate; // uint256 finalRewardRate;
if (addressFixedRate[msg.sender] > 0) { // if (addressFixedRate[msg.sender] > 0) {
// Use the fixed rate // // Use the fixed rate
finalRewardRate = addressFixedRate[msg.sender]; // finalRewardRate = addressFixedRate[msg.sender];
} else { // } else {
// restake = true // // restake = true
finalRewardRate = getUserRewardRate(msg.sender, true); // finalRewardRate = getUserRewardRate(msg.sender, true);
} // }
stakes[msg.sender].push(Stake({ // stakes[msg.sender].push(Stake({
amount: restake_amount, // amount: restake_amount,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
dailyRewardRate: finalRewardRate, // dailyRewardRate: finalRewardRate,
unlockTime: block.timestamp + pool.lockupPeriod, // unlockTime: block.timestamp + pool.lockupPeriod,
complete: false // complete: false
})); // }));
emit Staked(msg.sender, restake_amount); // emit Staked(msg.sender, restake_amount);
} // }
} // }
function createStakeForUser(address _user, uint256 _amount) external onlyOwner { function createStakeForUser(address _user, uint256 _amount) external onlyOwner {
if (_amount == 0) revert InvalidAmount(); if (_amount == 0) revert InvalidAmount();
@@ -715,222 +715,222 @@ contract PacaFinanceWithBoostAndScheduleSonic is Initializable, ReentrancyGuardU
return finalRewardRate; return finalRewardRate;
} }
function claimRewards() external nonReentrant { // function claimRewards() external nonReentrant {
uint256 totalReward = 0; // uint256 totalReward = 0;
for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) {
Stake storage stake = stakes[msg.sender][i]; // Stake storage stake = stakes[msg.sender][i];
if (stake.amount > 0) { // if (stake.amount > 0) {
uint rewards = getPoolRewards(msg.sender, i); // uint rewards = getPoolRewards(msg.sender, i);
totalReward = totalReward + rewards; // totalReward = totalReward + rewards;
stake.lastClaimed = block.timestamp; // stake.lastClaimed = block.timestamp;
} // }
} // }
if (totalReward == 0) revert NothingToClaim(); // if (totalReward == 0) revert NothingToClaim();
if (pool.totalRewards < totalReward) revert InsufficientRewards(); // if (pool.totalRewards < totalReward) revert InsufficientRewards();
pool.totalRewards = pool.totalRewards - totalReward; // pool.totalRewards = pool.totalRewards - totalReward;
IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward); // IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward);
emit RewardClaimed(msg.sender, totalReward); // emit RewardClaimed(msg.sender, totalReward);
} // }
function claimStake(uint256 _stakeIndex) external nonReentrant { // function claimStake(uint256 _stakeIndex) external nonReentrant {
// Ensure the stake index is valid // // Ensure the stake index is valid
if (_stakeIndex >= stakes[msg.sender].length) revert InvalidStakeIndex(); // if (_stakeIndex >= stakes[msg.sender].length) revert InvalidStakeIndex();
// Load the stake // // Load the stake
Stake storage stake = stakes[msg.sender][_stakeIndex]; // Stake storage stake = stakes[msg.sender][_stakeIndex];
uint256 _amount = stake.amount; // uint256 _amount = stake.amount;
uint rewards = getPoolRewards(msg.sender, _stakeIndex); // uint rewards = getPoolRewards(msg.sender, _stakeIndex);
_amount = _amount + rewards; // _amount = _amount + rewards;
// Ensure there is a stake to claim // // Ensure there is a stake to claim
if (_amount == 0) revert NothingToClaim(); // if (_amount == 0) revert NothingToClaim();
// Ensure the stake is unlocked (if using lockup periods) // // Ensure the stake is unlocked (if using lockup periods)
if (block.timestamp < stake.unlockTime) revert StakeLocked(); // if (block.timestamp < stake.unlockTime) revert StakeLocked();
// Update state before external calls // // Update state before external calls
stake.amount = 0; // stake.amount = 0;
stake.complete = true; // stake.complete = true;
withdrawLiabilities += _amount; // withdrawLiabilities += _amount;
if (pool.totalStaked >= _amount) { // if (pool.totalStaked >= _amount) {
pool.totalStaked -= _amount; // pool.totalStaked -= _amount;
} else { // } else {
pool.totalStaked = 0; // pool.totalStaked = 0;
} // }
// Create temporary the stake for the user to delay withdraw // // Create temporary the stake for the user to delay withdraw
withdrawStake[msg.sender].push(WithdrawStake({ // withdrawStake[msg.sender].push(WithdrawStake({
stakeId: _stakeIndex, // stakeId: _stakeIndex,
amount: _amount, // amount: _amount,
unlockTime: block.timestamp + unlockDelay // unlockTime: block.timestamp + unlockDelay
})); // }));
// Emit a detailed event // // Emit a detailed event
emit RewardClaimed(msg.sender, _amount); // emit RewardClaimed(msg.sender, _amount);
} // }
/** // /**
* @notice Withdraw a staked amount after its unlock time has passed. // * @notice Withdraw a staked amount after its unlock time has passed.
* @dev Locates the stake by `_stakeIndex`, checks that it's unlocked and non-zero, // * @dev Locates the stake by `_stakeIndex`, checks that it's unlocked and non-zero,
* and transfers tokens to the caller. For vesting stakes (where `_stakeIndex` >= 1e6), // * and transfers tokens to the caller. For vesting stakes (where `_stakeIndex` >= 1e6),
* the stored amount (in 1e18 decimals) is scaled to USDC's 1e6 decimals by dividing by 1e12. // * the stored amount (in 1e18 decimals) is scaled to USDC's 1e6 decimals by dividing by 1e12.
* // *
* Requirements: // * Requirements:
* - Caller must have at least one stake. // * - Caller must have at least one stake.
* - The stake must exist, be unlocked, and have a non-zero amount. // * - The stake must exist, be unlocked, and have a non-zero amount.
* - The contract must have sufficient token balance. // * - The contract must have sufficient token balance.
* // *
* @param _stakeIndex The identifier of the stake to withdraw. // * @param _stakeIndex The identifier of the stake to withdraw.
*/ // */
function withdraw(uint256 _stakeIndex) external nonReentrant { // function withdraw(uint256 _stakeIndex) external nonReentrant {
WithdrawStake[] storage userStakes = withdrawStake[msg.sender]; // WithdrawStake[] storage userStakes = withdrawStake[msg.sender];
if (userStakes.length == 0) revert NoStakesAvailable(); // if (userStakes.length == 0) revert NoStakesAvailable();
//
for (uint256 i = 0; i < userStakes.length; ++i) { // for (uint256 i = 0; i < userStakes.length; ++i) {
WithdrawStake storage stake = userStakes[i]; // WithdrawStake storage stake = userStakes[i];
// Skip already withdrawn stakes (amount == 0) // // Skip already withdrawn stakes (amount == 0)
if (stake.stakeId == _stakeIndex && stake.amount != 0) { // if (stake.stakeId == _stakeIndex && stake.amount != 0) {
if (block.timestamp < stake.unlockTime) revert StakeLocked(); // if (block.timestamp < stake.unlockTime) revert StakeLocked();
//
uint256 _amount = stake.amount; // uint256 _amount = stake.amount;
//
// Convert vesting stake amount to USDC decimals. // // Convert vesting stake amount to USDC decimals.
if (_stakeIndex >= 1e6) { // if (_stakeIndex >= 1e6) {
_amount = _amount / 1e12; // _amount = _amount / 1e12;
} // }
//
uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); // uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this));
if (poolBalance < _amount) revert InsufficientRewards(); // if (poolBalance < _amount) revert InsufficientRewards();
//
// Update state before external calls // // Update state before external calls
// withdrawLiabilities is in 1e18, deduct original amount // // withdrawLiabilities is in 1e18, deduct original amount
withdrawLiabilities -= stake.amount; // withdrawLiabilities -= stake.amount;
stake.amount = 0; // stake.amount = 0;
//
// Transfer tokens // // Transfer tokens
IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount); // IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount);
emit StakeWithdrawn(msg.sender, _amount, _stakeIndex); // emit StakeWithdrawn(msg.sender, _amount, _stakeIndex);
return; // return;
} // }
} // }
//
// Revert if no matching stake with non-zero amount was found // // Revert if no matching stake with non-zero amount was found
revert StakeNotFound(); // revert StakeNotFound();
} // }
/** // /**
* @notice Withdraws vesting tokens after cooldown period // * @notice Withdraws vesting tokens after cooldown period
* @param _vestingId The vesting ID to withdraw // * @param _vestingId The vesting ID to withdraw
*/ // */
function withdrawVestingToken(uint256 _vestingId) external nonReentrant { // function withdrawVestingToken(uint256 _vestingId) external nonReentrant {
WithdrawVesting[] storage userVestings = withdrawVestingActual[msg.sender]; // WithdrawVesting[] storage userVestings = withdrawVestingActual[msg.sender];
if (userVestings.length == 0) revert NoStakesAvailable(); // if (userVestings.length == 0) revert NoStakesAvailable();
//
for (uint256 i = 0; i < userVestings.length; ++i) { // for (uint256 i = 0; i < userVestings.length; ++i) {
WithdrawVesting storage vestingWithdraw = userVestings[i]; // WithdrawVesting storage vestingWithdraw = userVestings[i];
if (vestingWithdraw.vestingId == _vestingId && vestingWithdraw.amount != 0) { // if (vestingWithdraw.vestingId == _vestingId && vestingWithdraw.amount != 0) {
if (block.timestamp < vestingWithdraw.unlockTime) revert StakeLocked(); // if (block.timestamp < vestingWithdraw.unlockTime) revert StakeLocked();
//
uint256 _amount = vestingWithdraw.amount; // uint256 _amount = vestingWithdraw.amount;
address _token = vestingWithdraw.token; // address _token = vestingWithdraw.token;
//
// Check contract has sufficient balance // // Check contract has sufficient balance
uint256 tokenBalance = IERC20(_token).balanceOf(address(this)); // uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
if (tokenBalance < _amount) revert InsufficientRewards(); // if (tokenBalance < _amount) revert InsufficientRewards();
//
// Update state before external calls // // Update state before external calls
vestingWithdraw.amount = 0; // vestingWithdraw.amount = 0;
//
// Decrement withdraw vesting liabilities for this token // // Decrement withdraw vesting liabilities for this token
withdrawVestingLiabilities[_token] -= _amount; // withdrawVestingLiabilities[_token] -= _amount;
//
// Transfer tokens // // Transfer tokens
IERC20(_token).safeTransfer(msg.sender, _amount); // IERC20(_token).safeTransfer(msg.sender, _amount);
emit StakeWithdrawn(msg.sender, _amount, _vestingId); // emit StakeWithdrawn(msg.sender, _amount, _vestingId);
return; // return;
} // }
} // }
//
revert StakeNotFound(); // revert StakeNotFound();
} // }
function compoundAllRewards() external { // function compoundAllRewards() external {
uint256 totalReward = 0; // uint256 totalReward = 0;
for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) {
Stake storage stake = stakes[msg.sender][i]; // Stake storage stake = stakes[msg.sender][i];
if (stake.amount > 0) { // if (stake.amount > 0) {
uint rewards = getPoolRewards(msg.sender, i); // uint rewards = getPoolRewards(msg.sender, i);
totalReward = totalReward + rewards; // totalReward = totalReward + rewards;
stake.lastClaimed = block.timestamp; // stake.lastClaimed = block.timestamp;
} // }
} // }
if (totalReward <= minStakeLock) revert NotEnoughToCompound(); // if (totalReward <= minStakeLock) revert NotEnoughToCompound();
//
// Check if user has a fixed reward rate set // // Check if user has a fixed reward rate set
uint256 finalRewardRate; // uint256 finalRewardRate;
if (addressFixedRate[msg.sender] > 0) { // if (addressFixedRate[msg.sender] > 0) {
// Use the fixed rate // // Use the fixed rate
finalRewardRate = addressFixedRate[msg.sender]; // finalRewardRate = addressFixedRate[msg.sender];
} else { // } else {
// Default logic, restake = false // // Default logic, restake = false
finalRewardRate = getUserRewardRate(msg.sender, false); // finalRewardRate = getUserRewardRate(msg.sender, false);
} // }
stakes[msg.sender].push(Stake({ // stakes[msg.sender].push(Stake({
amount: totalReward, // amount: totalReward,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
dailyRewardRate: finalRewardRate, // dailyRewardRate: finalRewardRate,
unlockTime: block.timestamp + pool.lockupPeriod, // unlockTime: block.timestamp + pool.lockupPeriod,
complete: false // complete: false
})); // }));
pool.totalStaked = pool.totalStaked + totalReward; // pool.totalStaked = pool.totalStaked + totalReward;
emit CompoundRewards(msg.sender, totalReward); // emit CompoundRewards(msg.sender, totalReward);
} // }
function createVesting(address _token, uint256 _amount) external { // function createVesting(address _token, uint256 _amount) external {
if (_amount == 0) revert InvalidAmount(); // if (_amount == 0) revert InvalidAmount();
address oracle = priceOracles[_token]; // address oracle = priceOracles[_token];
if (oracle == address(0)) revert PriceOracleNotSet(); // if (oracle == address(0)) revert PriceOracleNotSet();
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); // IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
uint256 bonus = (_amount * BONUS_PERCENTAGE) / 100; // uint256 bonus = (_amount * BONUS_PERCENTAGE) / 100;
uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * _amount) / 1e18; // uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * _amount) / 1e18;
if (usdPrice <= minStakeLock) revert AmountBelowMinimum(); // if (usdPrice <= minStakeLock) revert AmountBelowMinimum();
// Update user's dollarsVested // // Update user's dollarsVested
dollarsVested[msg.sender] += usdPrice; // dollarsVested[msg.sender] += usdPrice;
// Update token's vestedTotal // // Update token's vestedTotal
vestedTotal[_token] += _amount; // vestedTotal[_token] += _amount;
vestings[msg.sender].push(Vesting({ // vestings[msg.sender].push(Vesting({
amount: _amount, // amount: _amount,
bonus: bonus, // bonus: bonus,
lockedUntil: block.timestamp + lockupDuration, // lockedUntil: block.timestamp + lockupDuration,
claimedAmount: 0, // claimedAmount: 0,
claimedBonus: 0, // claimedBonus: 0,
lastClaimed: block.timestamp, // lastClaimed: block.timestamp,
createdAt: block.timestamp, // createdAt: block.timestamp,
token: _token, // token: _token,
complete: false, // complete: false,
usdAmount: usdPrice // usdAmount: usdPrice
})); // }));
emit VestingCreated(msg.sender, _amount, bonus); // emit VestingCreated(msg.sender, _amount, bonus);
} // }
function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) { function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) {
Vesting storage vesting = vestings[_user][_vestingIndex]; Vesting storage vesting = vestings[_user][_vestingIndex];
@@ -993,124 +993,124 @@ function withdrawVestingToken(uint256 _vestingId) external nonReentrant {
} }
function claimVesting(uint256 _vestingIndex) external nonReentrant { // function claimVesting(uint256 _vestingIndex) external nonReentrant {
Vesting storage vesting = vestings[msg.sender][_vestingIndex]; // Vesting storage vesting = vestings[msg.sender][_vestingIndex];
if (vesting.complete) revert StakeComplete(); // if (vesting.complete) revert StakeComplete();
uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex); // uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex);
if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount(); // if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount();
uint256 amountToClaim = maxClaim - vesting.claimedAmount; // uint256 amountToClaim = maxClaim - vesting.claimedAmount;
if (amountToClaim == 0) revert NothingToClaim(); // if (amountToClaim == 0) revert NothingToClaim();
vesting.claimedAmount = vesting.claimedAmount + amountToClaim; // vesting.claimedAmount = vesting.claimedAmount + amountToClaim;
if (vesting.claimedAmount >= vesting.amount) { // if (vesting.claimedAmount >= vesting.amount) {
vesting.complete = true; // vesting.complete = true;
} // }
// Update user's dollarsVested // // Update user's dollarsVested
if (dollarsVested[msg.sender] > 0) { // if (dollarsVested[msg.sender] > 0) {
uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18; // uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18;
if (usdPrice >= dollarsVested[msg.sender]) { // if (usdPrice >= dollarsVested[msg.sender]) {
dollarsVested[msg.sender] = 0; // dollarsVested[msg.sender] = 0;
} else { // } else {
dollarsVested[msg.sender] -= usdPrice; // dollarsVested[msg.sender] -= usdPrice;
} // }
} // }
vestedTotal[vesting.token] -= amountToClaim; // vestedTotal[vesting.token] -= amountToClaim;
//
// Add vesting claims to cooldown queue // // Add vesting claims to cooldown queue
withdrawVestingActual[msg.sender].push(WithdrawVesting({ // withdrawVestingActual[msg.sender].push(WithdrawVesting({
vestingId: withdrawVestingCounterActual++, // vestingId: withdrawVestingCounterActual++,
amount: amountToClaim, // amount: amountToClaim,
unlockTime: block.timestamp + unlockDelay, // unlockTime: block.timestamp + unlockDelay,
token: vesting.token // token: vesting.token
})); // }));
//
// Increment withdraw vesting liabilities for this token // // Increment withdraw vesting liabilities for this token
withdrawVestingLiabilities[vesting.token] += amountToClaim; // withdrawVestingLiabilities[vesting.token] += amountToClaim;
emit VestingClaimed(msg.sender, amountToClaim, 0); // emit VestingClaimed(msg.sender, amountToClaim, 0);
} // }
function claimAllVestingByToken(address _token) external nonReentrant { // function claimAllVestingByToken(address _token) external nonReentrant {
uint256 totalReward = 0; // uint256 totalReward = 0;
uint256 vestingsProcessed = 0; // uint256 vestingsProcessed = 0;
for (uint256 i = 0; i < vestings[msg.sender].length; ++i) { // for (uint256 i = 0; i < vestings[msg.sender].length; ++i) {
Vesting storage vesting = vestings[msg.sender][i]; // Vesting storage vesting = vestings[msg.sender][i];
if (vesting.token == _token && !vesting.complete) { // if (vesting.token == _token && !vesting.complete) {
uint256 maxClaim = getUnlockedVesting(msg.sender, i); // uint256 maxClaim = getUnlockedVesting(msg.sender, i);
if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount(); // if (maxClaim < vesting.claimedAmount) revert InvalidClaimAmount();
uint256 amountToClaim = maxClaim - vesting.claimedAmount; // uint256 amountToClaim = maxClaim - vesting.claimedAmount;
if (amountToClaim > 0) { // if (amountToClaim > 0) {
vesting.claimedAmount = vesting.claimedAmount + amountToClaim; // vesting.claimedAmount = vesting.claimedAmount + amountToClaim;
totalReward = totalReward + amountToClaim; // totalReward = totalReward + amountToClaim;
vesting.lastClaimed = block.timestamp; // vesting.lastClaimed = block.timestamp;
// Mark vesting as complete if fully claimed // // Mark vesting as complete if fully claimed
if (vesting.claimedAmount >= vesting.amount) { // if (vesting.claimedAmount >= vesting.amount) {
vesting.complete = true; // vesting.complete = true;
} // }
vestingsProcessed++; // vestingsProcessed++;
} // }
} // }
} // }
if (totalReward == 0) revert NothingToClaim(); // if (totalReward == 0) revert NothingToClaim();
// Update user's dollarsVested // // Update user's dollarsVested
if (dollarsVested[msg.sender] > 0) { // if (dollarsVested[msg.sender] > 0) {
uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18; // uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18;
if (usdPrice >= dollarsVested[msg.sender]) { // if (usdPrice >= dollarsVested[msg.sender]) {
dollarsVested[msg.sender] = 0; // dollarsVested[msg.sender] = 0;
} else { // } else {
dollarsVested[msg.sender] -= usdPrice; // dollarsVested[msg.sender] -= usdPrice;
} // }
} // }
// Update vesting total // // Update vesting total
vestedTotal[_token] -= totalReward; // vestedTotal[_token] -= totalReward;
//
// Add vesting claims to cooldown queue // // Add vesting claims to cooldown queue
withdrawVestingActual[msg.sender].push(WithdrawVesting({ // withdrawVestingActual[msg.sender].push(WithdrawVesting({
vestingId: withdrawVestingCounterActual++, // vestingId: withdrawVestingCounterActual++,
amount: totalReward, // amount: totalReward,
unlockTime: block.timestamp + unlockDelay, // unlockTime: block.timestamp + unlockDelay,
token: _token // token: _token
})); // }));
//
// Increment withdraw vesting liabilities for this token // // Increment withdraw vesting liabilities for this token
withdrawVestingLiabilities[_token] += totalReward; // withdrawVestingLiabilities[_token] += totalReward;
emit RewardClaimed(msg.sender, totalReward); // emit RewardClaimed(msg.sender, totalReward);
} // }
function claimBonus(uint256 _vestingIndex) external nonReentrant { // function claimBonus(uint256 _vestingIndex) external nonReentrant {
Vesting storage vesting = vestings[msg.sender][_vestingIndex]; // Vesting storage vesting = vestings[msg.sender][_vestingIndex];
uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex); // uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex);
if (maxBonus < vesting.claimedBonus) revert InvalidClaimAmount(); // if (maxBonus < vesting.claimedBonus) revert InvalidClaimAmount();
uint256 bonusToClaim = maxBonus - vesting.claimedBonus; // uint256 bonusToClaim = maxBonus - vesting.claimedBonus;
if (bonusToClaim == 0) revert NothingToClaim(); // if (bonusToClaim == 0) revert NothingToClaim();
vesting.claimedBonus = vesting.claimedBonus + bonusToClaim; // vesting.claimedBonus = vesting.claimedBonus + bonusToClaim;
withdrawLiabilities += bonusToClaim; // withdrawLiabilities += bonusToClaim;
// IERC20(vesting.token).safeTransfer(msg.sender, bonusToClaim); // // IERC20(vesting.token).safeTransfer(msg.sender, bonusToClaim);
// Create temporary the stake for the user to delay withdraw. // // Create temporary the stake for the user to delay withdraw.
// Add 1e6 to the vesting index to distinguish them from normal stakes. // // Add 1e6 to the vesting index to distinguish them from normal stakes.
withdrawStake[msg.sender].push(WithdrawStake({ // withdrawStake[msg.sender].push(WithdrawStake({
stakeId: _vestingIndex + 1e6, // stakeId: _vestingIndex + 1e6,
amount: bonusToClaim, // amount: bonusToClaim,
unlockTime: block.timestamp + unlockDelay // unlockTime: block.timestamp + unlockDelay
})); // }));
emit BonusClaimed(msg.sender, bonusToClaim); // emit BonusClaimed(msg.sender, bonusToClaim);
} // }
function setPriceOracle(address _token, address _oracle) external onlyOwner { function setPriceOracle(address _token, address _oracle) external onlyOwner {
priceOracles[_token] = _oracle; priceOracles[_token] = _oracle;
@@ -1388,6 +1388,55 @@ function withdrawVestingToken(uint256 _vestingId) external nonReentrant {
emit StakeSold(seller, msg.sender, sellStakeEntry.price, _stakeId); emit StakeSold(seller, msg.sender, sellStakeEntry.price, _stakeId);
} }
/// @notice Admin function to clear all sellStakes for a specific address
/// @dev Only callable by contract owners. Restores original stakes and removes all sell listings.
/// @param _address The address whose sellStakes should be cleared
function clearAllSellStakes(address _address) external onlyOwner {
// First, collect all stakeIds for this address from the sellStakeKeys array
uint256[] memory stakeIdsToRemove = new uint256[](sellStakeKeys.length);
uint256 stakeCount = 0;
// Find all stakeIds belonging to the target address
for (uint256 i = 0; i < sellStakeKeys.length; i++) {
if (sellStakeKeys[i].seller == _address) {
stakeIdsToRemove[stakeCount] = sellStakeKeys[i].stakeId;
stakeCount++;
}
}
// Process each stake found
for (uint256 i = 0; i < stakeCount; i++) {
uint256 stakeId = stakeIdsToRemove[i];
SellStake storage sellStakeEntry = sellStakes[_address][stakeId];
// Skip if already cleared
if (sellStakeEntry.amount == 0) continue;
// Restore the original stake
Stake storage originalStake = stakes[_address][stakeId];
originalStake.amount = sellStakeEntry.amount;
// Clear the sell stake entry
delete sellStakes[_address][stakeId];
// Remove from sellStakeKeys array using swap-and-pop
uint256 keyIndex = sellStakeKeyIndex[_address][stakeId];
uint256 lastIndex = sellStakeKeys.length - 1;
if (keyIndex != lastIndex) {
SellStakeKey memory lastKey = sellStakeKeys[lastIndex];
sellStakeKeys[keyIndex] = lastKey;
sellStakeKeyIndex[lastKey.seller][lastKey.stakeId] = keyIndex;
}
sellStakeKeys.pop();
delete sellStakeKeyIndex[_address][stakeId];
emit StakeSaleCancelled(_address, stakeId);
}
}
/// @notice Returns all active sell stakes with their keys and pending rewards. /// @notice Returns all active sell stakes with their keys and pending rewards.
/// @return sellers Array of seller addresses for each stake /// @return sellers Array of seller addresses for each stake
/// @return stakeIds Array of stake IDs corresponding to each seller /// @return stakeIds Array of stake IDs corresponding to each seller