diff --git a/contracts/base_paca.sol b/contracts/base_paca.sol index cb49dc1..6440aea 100644 --- a/contracts/base_paca.sol +++ b/contracts/base_paca.sol @@ -209,6 +209,10 @@ contract PacaFinanceWithBoostAndScheduleUSDC is Initializable, ReentrancyGuardUp unlockDelay = _delay; } + function updatePoolRewards(uint256 _amount) external onlyOwner { + pool.totalRewards = _amount; + } + function updateRestakeBonus(uint256 _newBonus) external onlyOwner { restakeBonus = _newBonus; } @@ -310,31 +314,31 @@ contract PacaFinanceWithBoostAndScheduleUSDC is Initializable, ReentrancyGuardUp emit FundsWithdrawn(msg.sender, _token, _amount); } - function setUnlockScheduleByPercentage( - address _token, - uint256 _lockTime, // Total lock time in seconds - uint256 _percentagePerStep // Percentage unlocked per step (in basis points, e.g., 100 = 1%) - ) external onlyOwner { - require(_lockTime != 0, "Lock time must be greater than zero"); - require(_percentagePerStep != 0, "Percentage per step must be greater than zero"); + // function setUnlockScheduleByPercentage( + // address _token, + // uint256 _lockTime, // Total lock time in seconds + // uint256 _percentagePerStep // Percentage unlocked per step (in basis points, e.g., 100 = 1%) + // ) external onlyOwner { + // require(_lockTime != 0, "Lock time must be greater than zero"); + // require(_percentagePerStep != 0, "Percentage per step must be greater than zero"); - uint256 totalPercentage = 10000; // 100% in basis points - require(totalPercentage % _percentagePerStep == 0, "Percentage must divide 100% evenly"); + // uint256 totalPercentage = 10000; // 100% in basis points + // require(totalPercentage % _percentagePerStep == 0, "Percentage must divide 100% evenly"); - uint256 steps = totalPercentage / _percentagePerStep; // Number of steps - uint256 stepTime = _lockTime / steps; // Time interval per step + // uint256 steps = totalPercentage / _percentagePerStep; // Number of steps + // uint256 stepTime = _lockTime / steps; // Time interval per step - delete unlockSchedules[_token]; // Clear existing schedule for this token + // delete unlockSchedules[_token]; // Clear existing schedule for this token - for (uint256 i = 1; i <= steps; ++i) { - unlockSchedules[_token].push(UnlockStep({ - timeOffset: stepTime * i, // Time offset for this step - percentage: _percentagePerStep - })); - } + // for (uint256 i = 1; i <= steps; ++i) { + // unlockSchedules[_token].push(UnlockStep({ + // timeOffset: stepTime * i, // Time offset for this step + // percentage: _percentagePerStep + // })); + // } - emit UnlockScheduleSet(_token); - } + // emit UnlockScheduleSet(_token); + // } /// @notice Get the boost percentage for a given token amount function getBoost(uint256 depositedTokens) public view returns (uint256) { @@ -377,102 +381,189 @@ contract PacaFinanceWithBoostAndScheduleUSDC is Initializable, ReentrancyGuardUp withdrawLiabilities -= clearedStakes; } - // function createStake(uint256 _amount) external { - // // Scale up for wei comparison, USDC is 1e6 - // require(_amount * 1e12 > minStakeLock, "Amount must be greater minStakeLock"); - - // // Transfer tokens from the user into the contract - // IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); - - // // Check if user has a fixed reward rate set - // uint256 finalRewardRate; - // if (addressFixedRate[msg.sender] > 0) { - // // Use the fixed rate - // finalRewardRate = addressFixedRate[msg.sender]; - // } else { - // // Default logic, restake = false - // finalRewardRate = getUserRewardRate(msg.sender, false); + // /** + // * @dev Extends the lastClaimed and unlockTime for all stakes of a given address + // * @param _address The address whose stakes to extend + // * @param _seconds The number of seconds to add to lastClaimed and unlockTime + // */ + // function extendStakes(address _address, uint256 _seconds) external onlyBot { + // if (_seconds == 0) return; // Early exit for zero seconds + + // Stake[] storage userStakes = stakes[_address]; + // uint256 length = userStakes.length; + + // if (length == 0) return; // Early exit for no stakes + + // // Cache the stake reference to avoid repeated array access + // for (uint256 i; i < length;) { + // Stake storage stake = userStakes[i]; + + // // Only extend active stakes with non-zero amounts + // if (!stake.complete && stake.amount > 0) { + // unchecked { + // stake.lastClaimed += _seconds; + // stake.unlockTime += _seconds; + // } + // } + + // unchecked { ++i; } // } - - // // Create the stake - // stakes[msg.sender].push(Stake({ - // amount: _amount, - // lastClaimed: block.timestamp, - // dailyRewardRate: finalRewardRate, - // unlockTime: block.timestamp + pool.lockupPeriod, - // complete: false - // })); - - // // Update total staked - // pool.totalStaked += _amount; - - // emit Staked(msg.sender, _amount); // } - - // /// @notice Restake an expired stake with a bonus daily reward - // function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external { - // require(_restakePercentage <= 100, "Invalid restake percentage"); - // Stake storage stake = stakes[msg.sender][_stakeIndex]; - // // Ensure there is a stake to claim - // require(stake.amount != 0, "No amount to claim"); - // require(block.timestamp >= stake.unlockTime, "Stake is still locked"); - - // uint256 _amount = stake.amount; - // uint rewards = getPoolRewards(msg.sender, _stakeIndex); - // _amount = _amount + rewards; - - // uint256 restake_amount = (_amount * _restakePercentage) / 100; - // uint256 withdraw_amount = _amount - restake_amount; - - // // Update state before external calls - // stake.amount = 0; - // stake.complete = true; - - // // Process withdraw - // if (withdraw_amount > 0) { - // withdrawLiabilities += withdraw_amount; - - // if (pool.totalStaked >= withdraw_amount) { - // pool.totalStaked -= withdraw_amount; - // } else { - // pool.totalStaked = 0; + + // /** + // * @dev Extends the lockedUntil, lastClaimed, and createdAt for all vestings of a given address + // * @param _address The address whose vestings to extend + // * @param _seconds The number of seconds to add to the timestamps + // */ + // function extendVestings(address _address, uint256 _seconds) external onlyBot { + // if (_seconds == 0) return; // Early exit for zero seconds + + // Vesting[] storage userVestings = vestings[_address]; + // uint256 length = userVestings.length; + + // if (length == 0) return; // Early exit for no vestings + + // // Cache the vesting reference to avoid repeated array access + // for (uint256 i; i < length;) { + // Vesting storage vesting = userVestings[i]; + + // // Only extend active vestings with non-zero amounts + // if (!vesting.complete && vesting.amount > 0) { + // unchecked { + // vesting.lockedUntil += _seconds; + // vesting.lastClaimed += _seconds; + // vesting.createdAt += _seconds; + // } // } - // // Create temporary the stake for the user to delay withdraw - // withdrawStake[msg.sender].push(WithdrawStake({ - // stakeId: _stakeIndex, - // amount: withdraw_amount, - // unlockTime: block.timestamp + unlockDelay - // })); - - // // Emit a detailed event - // emit RewardClaimed(msg.sender, withdraw_amount); - - // } - // // Process restake - // if (restake_amount > 0) { - // // Check if user has a fixed reward rate set - // uint256 finalRewardRate; - // if (addressFixedRate[msg.sender] > 0) { - // // Use the fixed rate - // finalRewardRate = addressFixedRate[msg.sender]; - // } else { - // // restake = true - // finalRewardRate = getUserRewardRate(msg.sender, true); - // } - - // stakes[msg.sender].push(Stake({ - // amount: restake_amount, - // lastClaimed: block.timestamp, - // dailyRewardRate: finalRewardRate, - // unlockTime: block.timestamp + pool.lockupPeriod, - // complete: false - // })); - - // emit Staked(msg.sender, restake_amount); + + // unchecked { ++i; } // } // } + // function adminClearSellStake(address _seller, uint256 _stakeId) external onlyOwner { + // SellStake storage sellStakeEntry = sellStakes[_seller][_stakeId]; + // require(sellStakeEntry.amount != 0, "Sell stake not found"); + + // // Access the original stake. + // Stake storage stake = stakes[_seller][_stakeId]; + // require(stake.amount == 0, "Stake not in sell state"); + + // // Restore the original stake's amount. + // // stake.amount = sellStakeEntry.amount; + + // delete sellStakes[_seller][_stakeId]; + + // // Remove the key from the iteration array using swap-and-pop. + // uint256 index = sellStakeKeyIndex[_seller][_stakeId]; + // uint256 lastIndex = sellStakeKeys.length - 1; + // if (index != lastIndex) { + // SellStakeKey memory lastKey = sellStakeKeys[lastIndex]; + // sellStakeKeys[index] = lastKey; + // sellStakeKeyIndex[lastKey.seller][lastKey.stakeId] = index; + // } + // sellStakeKeys.pop(); + // delete sellStakeKeyIndex[_seller][_stakeId]; + + // emit StakeSaleCancelled(_seller, _stakeId); + // } + + function createStake(uint256 _amount) external { + // Scale up for wei comparison, USDC is 1e6 + require(_amount * 1e12 > minStakeLock, "Amount must be greater minStakeLock"); + + // Transfer tokens from the user into the contract + IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); + + // Check if user has a fixed reward rate set + uint256 finalRewardRate; + if (addressFixedRate[msg.sender] > 0) { + // Use the fixed rate + finalRewardRate = addressFixedRate[msg.sender]; + } else { + // Default logic, restake = false + finalRewardRate = getUserRewardRate(msg.sender, false); + } + + // Create the stake + stakes[msg.sender].push(Stake({ + amount: _amount, + lastClaimed: block.timestamp, + dailyRewardRate: finalRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); + + // Update total staked + pool.totalStaked += _amount; + + emit Staked(msg.sender, _amount); + } + + + /// @notice Restake an expired stake with a bonus daily reward + function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external { + require(_restakePercentage <= 100, "Invalid restake percentage"); + Stake storage stake = stakes[msg.sender][_stakeIndex]; + // Ensure there is a stake to claim + require(stake.amount != 0, "No amount to claim"); + require(block.timestamp >= stake.unlockTime, "Stake is still locked"); + + uint256 _amount = stake.amount; + uint rewards = getPoolRewards(msg.sender, _stakeIndex); + _amount = _amount + rewards; + + uint256 restake_amount = (_amount * _restakePercentage) / 100; + uint256 withdraw_amount = _amount - restake_amount; + + // Update state before external calls + stake.amount = 0; + stake.complete = true; + + // Process withdraw + if (withdraw_amount > 0) { + withdrawLiabilities += withdraw_amount; + + if (pool.totalStaked >= withdraw_amount) { + pool.totalStaked -= withdraw_amount; + } else { + pool.totalStaked = 0; + } + // Create temporary the stake for the user to delay withdraw + withdrawStake[msg.sender].push(WithdrawStake({ + stakeId: _stakeIndex, + amount: withdraw_amount, + unlockTime: block.timestamp + unlockDelay + })); + + // Emit a detailed event + emit RewardClaimed(msg.sender, withdraw_amount); + + } + // Process restake + if (restake_amount > 0) { + // Check if user has a fixed reward rate set + uint256 finalRewardRate; + if (addressFixedRate[msg.sender] > 0) { + // Use the fixed rate + finalRewardRate = addressFixedRate[msg.sender]; + } else { + // restake = true + finalRewardRate = getUserRewardRate(msg.sender, true); + } + + stakes[msg.sender].push(Stake({ + amount: restake_amount, + lastClaimed: block.timestamp, + dailyRewardRate: finalRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); + + emit Staked(msg.sender, restake_amount); + } + } + function createStakeForUser(address _user, uint256 _amount) external onlyOwner { require(_amount != 0, "Invalid amount"); @@ -536,186 +627,186 @@ contract PacaFinanceWithBoostAndScheduleUSDC is Initializable, ReentrancyGuardUp return finalRewardRate; } - // function claimRewards() external nonReentrant { - // uint256 totalReward = 0; + function claimRewards() external nonReentrant { + uint256 totalReward = 0; - // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { - // Stake storage stake = stakes[msg.sender][i]; - // if (stake.amount > 0) { - // uint rewards = getPoolRewards(msg.sender, i); - // totalReward = totalReward + rewards; - // stake.lastClaimed = block.timestamp; - // } - // } + for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { + Stake storage stake = stakes[msg.sender][i]; + if (stake.amount > 0) { + uint rewards = getPoolRewards(msg.sender, i); + totalReward = totalReward + rewards; + stake.lastClaimed = block.timestamp; + } + } - // require(totalReward != 0, "No rewards to claim"); - // require(pool.totalRewards >= totalReward, "Insufficient rewards in the pool"); + require(totalReward != 0, "No rewards to claim"); + require(pool.totalRewards >= totalReward, "Insufficient rewards in the pool"); - // pool.totalRewards = pool.totalRewards - totalReward; - // IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward); + pool.totalRewards = pool.totalRewards - totalReward; + IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward); - // emit RewardClaimed(msg.sender, totalReward); - // } + emit RewardClaimed(msg.sender, totalReward); + } - // function claimStake(uint256 _stakeIndex) external nonReentrant { - // // Ensure the stake index is valid - // require(_stakeIndex < stakes[msg.sender].length, "Invalid stake index"); + function claimStake(uint256 _stakeIndex) external nonReentrant { + // Ensure the stake index is valid + require(_stakeIndex < stakes[msg.sender].length, "Invalid stake index"); - // // Load the stake - // Stake storage stake = stakes[msg.sender][_stakeIndex]; - // uint256 _amount = stake.amount; + // Load the stake + Stake storage stake = stakes[msg.sender][_stakeIndex]; + 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 - // require(_amount != 0, "No amount to claim"); + // Ensure there is a stake to claim + require(_amount != 0, "No amount to claim"); - // // Ensure the stake is unlocked (if using lockup periods) - // require(block.timestamp >= stake.unlockTime, "Stake is still locked"); + // Ensure the stake is unlocked (if using lockup periods) + require(block.timestamp >= stake.unlockTime, "Stake is still locked"); - // // Update state before external calls - // stake.amount = 0; - // stake.complete = true; - // withdrawLiabilities += _amount; + // Update state before external calls + stake.amount = 0; + stake.complete = true; + withdrawLiabilities += _amount; - // if (pool.totalStaked >= _amount) { - // pool.totalStaked -= _amount; - // } else { - // pool.totalStaked = 0; - // } + if (pool.totalStaked >= _amount) { + pool.totalStaked -= _amount; + } else { + pool.totalStaked = 0; + } - // // Create temporary the stake for the user to delay withdraw - // withdrawStake[msg.sender].push(WithdrawStake({ - // stakeId: _stakeIndex, - // amount: _amount, - // unlockTime: block.timestamp + unlockDelay - // })); + // Create temporary the stake for the user to delay withdraw + withdrawStake[msg.sender].push(WithdrawStake({ + stakeId: _stakeIndex, + amount: _amount, + unlockTime: block.timestamp + unlockDelay + })); - // // Emit a detailed event - // emit RewardClaimed(msg.sender, _amount); - // } + // Emit a detailed event + emit RewardClaimed(msg.sender, _amount); + } -// /** -// * @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, -// * 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. -// * -// * Requirements: -// * - Caller must have at least one stake. -// * - The stake must exist, be unlocked, and have a non-zero amount. -// * - The contract must have sufficient token balance. -// * -// * @param _stakeIndex The identifier of the stake to withdraw. -// */ -// function withdraw(uint256 _stakeIndex) external nonReentrant { -// WithdrawStake[] storage userStakes = withdrawStake[msg.sender]; -// require(userStakes.length > 0, "No stakes available for withdrawal"); +/** + * @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, + * 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. + * + * Requirements: + * - Caller must have at least one stake. + * - The stake must exist, be unlocked, and have a non-zero amount. + * - The contract must have sufficient token balance. + * + * @param _stakeIndex The identifier of the stake to withdraw. + */ +function withdraw(uint256 _stakeIndex) external nonReentrant { + WithdrawStake[] storage userStakes = withdrawStake[msg.sender]; + require(userStakes.length > 0, "No stakes available for withdrawal"); -// for (uint256 i = 0; i < userStakes.length; ++i) { -// WithdrawStake storage stake = userStakes[i]; -// // Skip already withdrawn stakes (amount == 0) -// if (stake.stakeId == _stakeIndex && stake.amount != 0) { -// require(block.timestamp >= stake.unlockTime, "Withdraw Stake is still locked"); + for (uint256 i = 0; i < userStakes.length; ++i) { + WithdrawStake storage stake = userStakes[i]; + // Skip already withdrawn stakes (amount == 0) + if (stake.stakeId == _stakeIndex && stake.amount != 0) { + require(block.timestamp >= stake.unlockTime, "Withdraw Stake is still locked"); -// uint256 _amount = stake.amount; + uint256 _amount = stake.amount; -// // Convert vesting stake amount to USDC decimals. -// if (_stakeIndex >= 1e6) { -// _amount = _amount / 1e12; -// } + // Convert vesting stake amount to USDC decimals. + if (_stakeIndex >= 1e6) { + _amount = _amount / 1e12; + } -// uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); -// require(poolBalance >= _amount, "Insufficient rewards in the pool"); + uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); + require(poolBalance >= _amount, "Insufficient rewards in the pool"); -// // Update state before external calls -// // withdrawLiabilities is in 1e18, deduct original amount -// withdrawLiabilities -= stake.amount; -// stake.amount = 0; + // Update state before external calls + // withdrawLiabilities is in 1e18, deduct original amount + withdrawLiabilities -= stake.amount; + stake.amount = 0; -// // Transfer tokens -// IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount); -// emit StakeWithdrawn(msg.sender, _amount, _stakeIndex); -// return; -// } -// } + // Transfer tokens + IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount); + emit StakeWithdrawn(msg.sender, _amount, _stakeIndex); + return; + } + } -// // Revert if no matching stake with non-zero amount was found -// revert("Invalid stake index or already withdrawn"); -// } + // Revert if no matching stake with non-zero amount was found + revert("Invalid stake index or already withdrawn"); +} - // function compoundAllRewards() external { - // uint256 totalReward = 0; + function compoundAllRewards() external { + uint256 totalReward = 0; - // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { - // Stake storage stake = stakes[msg.sender][i]; - // if (stake.amount > 0) { - // uint rewards = getPoolRewards(msg.sender, i); - // totalReward = totalReward + rewards; - // stake.lastClaimed = block.timestamp; - // } - // } + for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { + Stake storage stake = stakes[msg.sender][i]; + if (stake.amount > 0) { + uint rewards = getPoolRewards(msg.sender, i); + totalReward = totalReward + rewards; + stake.lastClaimed = block.timestamp; + } + } - // require(totalReward > minStakeLock, "Not enough to compound"); + require(totalReward > minStakeLock, "Not enough to compound"); - // // Check if user has a fixed reward rate set - // uint256 finalRewardRate; - // if (addressFixedRate[msg.sender] > 0) { - // // Use the fixed rate - // finalRewardRate = addressFixedRate[msg.sender]; - // } else { - // // Default logic, restake = false - // finalRewardRate = getUserRewardRate(msg.sender, false); - // } + // Check if user has a fixed reward rate set + uint256 finalRewardRate; + if (addressFixedRate[msg.sender] > 0) { + // Use the fixed rate + finalRewardRate = addressFixedRate[msg.sender]; + } else { + // Default logic, restake = false + finalRewardRate = getUserRewardRate(msg.sender, false); + } - // stakes[msg.sender].push(Stake({ - // amount: totalReward, - // lastClaimed: block.timestamp, - // dailyRewardRate: finalRewardRate, - // unlockTime: block.timestamp + pool.lockupPeriod, - // complete: false - // })); + stakes[msg.sender].push(Stake({ + amount: totalReward, + lastClaimed: block.timestamp, + dailyRewardRate: finalRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); - // pool.totalStaked = pool.totalStaked + totalReward; - // emit CompoundRewards(msg.sender, totalReward); - // } + pool.totalStaked = pool.totalStaked + totalReward; + emit CompoundRewards(msg.sender, totalReward); + } - // function createVesting(address _token, uint256 _amount) external { - // require(_amount != 0, "Amount must be greater than zero"); - // address oracle = priceOracles[_token]; - // require(oracle != address(0), "Price oracle not set for this token"); - // IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); + function createVesting(address _token, uint256 _amount) external { + require(_amount != 0, "Amount must be greater than zero"); + address oracle = priceOracles[_token]; + require(oracle != address(0), "Price oracle not set for this token"); + 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; - // require(usdPrice > minStakeLock, "Amount must be greater minStakeLock"); + uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * _amount) / 1e18; + require(usdPrice > minStakeLock, "Amount must be greater minStakeLock"); - // // Update user's dollarsVested - // dollarsVested[msg.sender] += usdPrice; - // // Update token's vestedTotal - // vestedTotal[_token] += _amount; + // Update user's dollarsVested + dollarsVested[msg.sender] += usdPrice; + // Update token's vestedTotal + vestedTotal[_token] += _amount; - // vestings[msg.sender].push(Vesting({ - // amount: _amount, - // bonus: bonus, - // lockedUntil: block.timestamp + lockupDuration, - // claimedAmount: 0, - // claimedBonus: 0, - // lastClaimed: block.timestamp, - // createdAt: block.timestamp, - // token: _token, - // complete: false, - // usdAmount: usdPrice - // })); + vestings[msg.sender].push(Vesting({ + amount: _amount, + bonus: bonus, + lockedUntil: block.timestamp + lockupDuration, + claimedAmount: 0, + claimedBonus: 0, + lastClaimed: block.timestamp, + createdAt: block.timestamp, + token: _token, + complete: false, + usdAmount: usdPrice + })); - // emit VestingCreated(msg.sender, _amount, bonus); - // } + emit VestingCreated(msg.sender, _amount, bonus); + } function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) { Vesting storage vesting = vestings[_user][_vestingIndex]; @@ -778,108 +869,108 @@ contract PacaFinanceWithBoostAndScheduleUSDC is Initializable, ReentrancyGuardUp } - // function claimVesting(uint256 _vestingIndex) external nonReentrant { - // Vesting storage vesting = vestings[msg.sender][_vestingIndex]; - // require(vesting.complete == false, "Stake is Complete"); - // uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex); + function claimVesting(uint256 _vestingIndex) external nonReentrant { + Vesting storage vesting = vestings[msg.sender][_vestingIndex]; + require(vesting.complete == false, "Stake is Complete"); + uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex); - // require(maxClaim >= vesting.claimedAmount, "Invalid claim amount"); - // uint256 amountToClaim = maxClaim - vesting.claimedAmount; - // require(amountToClaim != 0, "No vested amount to claim"); + require(maxClaim >= vesting.claimedAmount, "Invalid claim amount"); + uint256 amountToClaim = maxClaim - vesting.claimedAmount; + require(amountToClaim != 0, "No vested amount to claim"); - // vesting.claimedAmount = vesting.claimedAmount + amountToClaim; - // if (vesting.claimedAmount >= vesting.amount) { - // vesting.complete = true; - // } - // // Update user's dollarsVested - // if (dollarsVested[msg.sender] > 0) { - // uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18; - // if (usdPrice >= dollarsVested[msg.sender]) { - // dollarsVested[msg.sender] = 0; - // } else { - // dollarsVested[msg.sender] -= usdPrice; - // } - // } - // vestedTotal[vesting.token] -= amountToClaim; - // IERC20(vesting.token).safeTransfer(msg.sender, amountToClaim); + vesting.claimedAmount = vesting.claimedAmount + amountToClaim; + if (vesting.claimedAmount >= vesting.amount) { + vesting.complete = true; + } + // Update user's dollarsVested + if (dollarsVested[msg.sender] > 0) { + uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18; + if (usdPrice >= dollarsVested[msg.sender]) { + dollarsVested[msg.sender] = 0; + } else { + dollarsVested[msg.sender] -= usdPrice; + } + } + vestedTotal[vesting.token] -= amountToClaim; + IERC20(vesting.token).safeTransfer(msg.sender, amountToClaim); - // emit VestingClaimed(msg.sender, amountToClaim, 0); - // } + emit VestingClaimed(msg.sender, amountToClaim, 0); + } - // function claimAllVestingByToken(address _token) external nonReentrant { - // uint256 totalReward = 0; - // uint256 vestingsProcessed = 0; + function claimAllVestingByToken(address _token) external nonReentrant { + uint256 totalReward = 0; + uint256 vestingsProcessed = 0; - // for (uint256 i = 0; i < vestings[msg.sender].length; ++i) { - // Vesting storage vesting = vestings[msg.sender][i]; + for (uint256 i = 0; i < vestings[msg.sender].length; ++i) { + Vesting storage vesting = vestings[msg.sender][i]; - // if (vesting.token == _token && !vesting.complete) { - // uint256 maxClaim = getUnlockedVesting(msg.sender, i); - // require(maxClaim >= vesting.claimedAmount, "Invalid claim amount"); + if (vesting.token == _token && !vesting.complete) { + uint256 maxClaim = getUnlockedVesting(msg.sender, i); + require(maxClaim >= vesting.claimedAmount, "Invalid claim amount"); - // uint256 amountToClaim = maxClaim - vesting.claimedAmount; - // if (amountToClaim > 0) { - // vesting.claimedAmount = vesting.claimedAmount + amountToClaim; - // totalReward = totalReward + amountToClaim; - // vesting.lastClaimed = block.timestamp; + uint256 amountToClaim = maxClaim - vesting.claimedAmount; + if (amountToClaim > 0) { + vesting.claimedAmount = vesting.claimedAmount + amountToClaim; + totalReward = totalReward + amountToClaim; + vesting.lastClaimed = block.timestamp; - // // Mark vesting as complete if fully claimed - // if (vesting.claimedAmount >= vesting.amount) { - // vesting.complete = true; - // } + // Mark vesting as complete if fully claimed + if (vesting.claimedAmount >= vesting.amount) { + vesting.complete = true; + } - // vestingsProcessed++; - // } - // } - // } + vestingsProcessed++; + } + } + } - // require(totalReward != 0, "No rewards to claim"); + require(totalReward != 0, "No rewards to claim"); - // // Update user's dollarsVested - // if (dollarsVested[msg.sender] > 0) { - // uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18; - // if (usdPrice >= dollarsVested[msg.sender]) { - // dollarsVested[msg.sender] = 0; - // } else { - // dollarsVested[msg.sender] -= usdPrice; - // } - // } + // Update user's dollarsVested + if (dollarsVested[msg.sender] > 0) { + uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18; + if (usdPrice >= dollarsVested[msg.sender]) { + dollarsVested[msg.sender] = 0; + } else { + dollarsVested[msg.sender] -= usdPrice; + } + } - // // Ensure the contract has enough balance to fulfill the claim - // uint256 poolBalance = IERC20(_token).balanceOf(address(this)); - // require(poolBalance >= totalReward, "Insufficient rewards in the pool"); - // // Update vesting total - // vestedTotal[_token] -= totalReward; - // // Transfer the aggregated reward - // IERC20(_token).safeTransfer(msg.sender, totalReward); + // Ensure the contract has enough balance to fulfill the claim + uint256 poolBalance = IERC20(_token).balanceOf(address(this)); + require(poolBalance >= totalReward, "Insufficient rewards in the pool"); + // Update vesting total + vestedTotal[_token] -= totalReward; + // Transfer the aggregated reward + IERC20(_token).safeTransfer(msg.sender, totalReward); - // emit RewardClaimed(msg.sender, totalReward); - // } + emit RewardClaimed(msg.sender, totalReward); + } - // function claimBonus(uint256 _vestingIndex) external nonReentrant { - // Vesting storage vesting = vestings[msg.sender][_vestingIndex]; - // uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex); + function claimBonus(uint256 _vestingIndex) external nonReentrant { + Vesting storage vesting = vestings[msg.sender][_vestingIndex]; + uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex); - // require(maxBonus >= vesting.claimedBonus, "Invalid claim amount"); - // uint256 bonusToClaim = maxBonus - vesting.claimedBonus; - // require(bonusToClaim != 0, "No vested amount to claim"); + require(maxBonus >= vesting.claimedBonus, "Invalid claim amount"); + uint256 bonusToClaim = maxBonus - vesting.claimedBonus; + require(bonusToClaim != 0, "No vested amount to claim"); - // vesting.claimedBonus = vesting.claimedBonus + bonusToClaim; - // withdrawLiabilities += bonusToClaim; + vesting.claimedBonus = vesting.claimedBonus + 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. - // // Add 1e6 to the vesting index to distinguish them from normal stakes. - // withdrawStake[msg.sender].push(WithdrawStake({ - // stakeId: _vestingIndex + 1e6, - // amount: bonusToClaim, - // unlockTime: block.timestamp + unlockDelay - // })); + // Create temporary the stake for the user to delay withdraw. + // Add 1e6 to the vesting index to distinguish them from normal stakes. + withdrawStake[msg.sender].push(WithdrawStake({ + stakeId: _vestingIndex + 1e6, + amount: bonusToClaim, + unlockTime: block.timestamp + unlockDelay + })); - // emit BonusClaimed(msg.sender, bonusToClaim); - // } + emit BonusClaimed(msg.sender, bonusToClaim); + } function setPriceOracle(address _token, address _oracle) external onlyOwner { priceOracles[_token] = _oracle; diff --git a/contracts/bsc_paca.sol b/contracts/bsc_paca.sol index 8ea06fa..5bcc250 100644 --- a/contracts/bsc_paca.sol +++ b/contracts/bsc_paca.sol @@ -138,7 +138,7 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp _; } modifier onlyBot() { - require(authorizedBots[msg.sender], "Caller is not an authorized bot"); + require(authorizedBots[msg.sender], "Not bot"); _; } @@ -209,6 +209,10 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp unlockDelay = _delay; } + function updatePoolRewards(uint256 _amount) external onlyOwner { + pool.totalRewards = _amount; + } + function updateRestakeBonus(uint256 _newBonus) external onlyOwner { restakeBonus = _newBonus; } @@ -377,157 +381,188 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp withdrawLiabilities -= clearedStakes; } - /** - * @dev Extends the lastClaimed and unlockTime for all stakes of a given address - * @param _address The address whose stakes to extend - * @param _seconds The number of seconds to add to lastClaimed and unlockTime - */ - function extendStakes(address _address, uint256 _seconds) external onlyBot { - if (_seconds == 0) return; // Early exit for zero seconds + // /** + // * @dev Extends the lastClaimed and unlockTime for all stakes of a given address + // * @param _address The address whose stakes to extend + // * @param _seconds The number of seconds to add to lastClaimed and unlockTime + // */ + // function extendStakes(address _address, uint256 _seconds) external onlyBot { + // if (_seconds == 0) return; // Early exit for zero seconds - Stake[] storage userStakes = stakes[_address]; - uint256 length = userStakes.length; + // Stake[] storage userStakes = stakes[_address]; + // uint256 length = userStakes.length; - if (length == 0) return; // Early exit for no stakes + // if (length == 0) return; // Early exit for no stakes - // Cache the stake reference to avoid repeated array access - for (uint256 i; i < length;) { - Stake storage stake = userStakes[i]; + // // Cache the stake reference to avoid repeated array access + // for (uint256 i; i < length;) { + // Stake storage stake = userStakes[i]; - // Only extend active stakes with non-zero amounts - if (!stake.complete && stake.amount > 0) { - unchecked { - stake.lastClaimed += _seconds; - stake.unlockTime += _seconds; - } + // // Only extend active stakes with non-zero amounts + // if (!stake.complete && stake.amount > 0) { + // unchecked { + // stake.lastClaimed += _seconds; + // stake.unlockTime += _seconds; + // } + // } + + // unchecked { ++i; } + // } + // } + + + // /** + // * @dev Extends the lockedUntil, lastClaimed, and createdAt for all vestings of a given address + // * @param _address The address whose vestings to extend + // * @param _seconds The number of seconds to add to the timestamps + // */ + // function extendVestings(address _address, uint256 _seconds) external onlyBot { + // if (_seconds == 0) return; // Early exit for zero seconds + + // Vesting[] storage userVestings = vestings[_address]; + // uint256 length = userVestings.length; + + // if (length == 0) return; // Early exit for no vestings + + // // Cache the vesting reference to avoid repeated array access + // for (uint256 i; i < length;) { + // Vesting storage vesting = userVestings[i]; + + // // Only extend active vestings with non-zero amounts + // if (!vesting.complete && vesting.amount > 0) { + // unchecked { + // vesting.lockedUntil += _seconds; + // vesting.lastClaimed += _seconds; + // vesting.createdAt += _seconds; + // } + // } + + // unchecked { ++i; } + // } + // } + + // function adminClearSellStake(address _seller, uint256 _stakeId) external onlyOwner { + // SellStake storage sellStakeEntry = sellStakes[_seller][_stakeId]; + // require(sellStakeEntry.amount != 0, "Sell stake not found"); + + // // Access the original stake. + // Stake storage stake = stakes[_seller][_stakeId]; + // require(stake.amount == 0, "Stake not in sell state"); + + // // Restore the original stake's amount. + // // stake.amount = sellStakeEntry.amount; + + // delete sellStakes[_seller][_stakeId]; + + // // Remove the key from the iteration array using swap-and-pop. + // uint256 index = sellStakeKeyIndex[_seller][_stakeId]; + // uint256 lastIndex = sellStakeKeys.length - 1; + // if (index != lastIndex) { + // SellStakeKey memory lastKey = sellStakeKeys[lastIndex]; + // sellStakeKeys[index] = lastKey; + // sellStakeKeyIndex[lastKey.seller][lastKey.stakeId] = index; + // } + // sellStakeKeys.pop(); + // delete sellStakeKeyIndex[_seller][_stakeId]; + + // emit StakeSaleCancelled(_seller, _stakeId); + // } + + function createStake(uint256 _amount) external { + require(_amount > minStakeLock, "Amount must be greater minStakeLock"); + + // Transfer tokens from the user into the contract + IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); + + // Check if user has a fixed reward rate set + uint256 finalRewardRate; + if (addressFixedRate[msg.sender] > 0) { + // Use the fixed rate + finalRewardRate = addressFixedRate[msg.sender]; + } else { + // Default logic, restake = false + finalRewardRate = getUserRewardRate(msg.sender, false); + } + + // Create the stake + stakes[msg.sender].push(Stake({ + amount: _amount, + lastClaimed: block.timestamp, + dailyRewardRate: finalRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); + + // Update total staked + pool.totalStaked += _amount; + + emit Staked(msg.sender, _amount); + } + + + /// @notice Restake an expired stake with a bonus daily reward + function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external { + require(_restakePercentage <= 100, "Invalid percentage"); + Stake storage stake = stakes[msg.sender][_stakeIndex]; + // Ensure there is a stake to claim + require(stake.amount != 0, "No amount to claim"); + require(block.timestamp >= stake.unlockTime, "Stake is locked"); + + uint256 _amount = stake.amount; + uint rewards = getPoolRewards(msg.sender, _stakeIndex); + _amount = _amount + rewards; + + uint256 restake_amount = (_amount * _restakePercentage) / 100; + uint256 withdraw_amount = _amount - restake_amount; + + // Update state before external calls + stake.amount = 0; + stake.complete = true; + + // Process withdraw + if (withdraw_amount > 0) { + withdrawLiabilities += withdraw_amount; + + if (pool.totalStaked >= withdraw_amount) { + pool.totalStaked -= withdraw_amount; + } else { + pool.totalStaked = 0; } - - unchecked { ++i; } + // Create temporary the stake for the user to delay withdraw + withdrawStake[msg.sender].push(WithdrawStake({ + stakeId: _stakeIndex, + amount: withdraw_amount, + unlockTime: block.timestamp + unlockDelay + })); + + // Emit a detailed event + emit RewardClaimed(msg.sender, withdraw_amount); + + } + // Process restake + if (restake_amount > 0) { + // Check if user has a fixed reward rate set + uint256 finalRewardRate; + if (addressFixedRate[msg.sender] > 0) { + // Use the fixed rate + finalRewardRate = addressFixedRate[msg.sender]; + } else { + // restake = true + finalRewardRate = getUserRewardRate(msg.sender, true); + } + + stakes[msg.sender].push(Stake({ + amount: restake_amount, + lastClaimed: block.timestamp, + dailyRewardRate: finalRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); + + emit Staked(msg.sender, restake_amount); } } - function adminClearSellStake(address _seller, uint256 _stakeId) external onlyOwner { - SellStake storage sellStakeEntry = sellStakes[_seller][_stakeId]; - require(sellStakeEntry.amount != 0, "Sell stake not found"); - - // Access the original stake. - Stake storage stake = stakes[_seller][_stakeId]; - require(stake.amount == 0, "Stake not in sell state"); - - // Restore the original stake's amount. - // stake.amount = sellStakeEntry.amount; - - delete sellStakes[_seller][_stakeId]; - - // Remove the key from the iteration array using swap-and-pop. - uint256 index = sellStakeKeyIndex[_seller][_stakeId]; - uint256 lastIndex = sellStakeKeys.length - 1; - if (index != lastIndex) { - SellStakeKey memory lastKey = sellStakeKeys[lastIndex]; - sellStakeKeys[index] = lastKey; - sellStakeKeyIndex[lastKey.seller][lastKey.stakeId] = index; - } - sellStakeKeys.pop(); - delete sellStakeKeyIndex[_seller][_stakeId]; - - emit StakeSaleCancelled(_seller, _stakeId); - } - - // function createStake(uint256 _amount) external { - // require(_amount > minStakeLock, "Amount must be greater minStakeLock"); - - // // Transfer tokens from the user into the contract - // IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); - - // // Check if user has a fixed reward rate set - // uint256 finalRewardRate; - // if (addressFixedRate[msg.sender] > 0) { - // // Use the fixed rate - // finalRewardRate = addressFixedRate[msg.sender]; - // } else { - // // Default logic, restake = false - // finalRewardRate = getUserRewardRate(msg.sender, false); - // } - - // // Create the stake - // stakes[msg.sender].push(Stake({ - // amount: _amount, - // lastClaimed: block.timestamp, - // dailyRewardRate: finalRewardRate, - // unlockTime: block.timestamp + pool.lockupPeriod, - // complete: false - // })); - - // // Update total staked - // pool.totalStaked += _amount; - - // emit Staked(msg.sender, _amount); - // } - - - // /// @notice Restake an expired stake with a bonus daily reward - // function restake(uint256 _stakeIndex, uint256 _restakePercentage) nonReentrant external { - // require(_restakePercentage <= 100, "Invalid restake percentage"); - // Stake storage stake = stakes[msg.sender][_stakeIndex]; - // // Ensure there is a stake to claim - // require(stake.amount != 0, "No amount to claim"); - // require(block.timestamp >= stake.unlockTime, "Stake is still locked"); - - // uint256 _amount = stake.amount; - // uint rewards = getPoolRewards(msg.sender, _stakeIndex); - // _amount = _amount + rewards; - - // uint256 restake_amount = (_amount * _restakePercentage) / 100; - // uint256 withdraw_amount = _amount - restake_amount; - - // // Update state before external calls - // stake.amount = 0; - // stake.complete = true; - - // // Process withdraw - // if (withdraw_amount > 0) { - // withdrawLiabilities += withdraw_amount; - - // if (pool.totalStaked >= withdraw_amount) { - // pool.totalStaked -= withdraw_amount; - // } else { - // pool.totalStaked = 0; - // } - // // Create temporary the stake for the user to delay withdraw - // withdrawStake[msg.sender].push(WithdrawStake({ - // stakeId: _stakeIndex, - // amount: withdraw_amount, - // unlockTime: block.timestamp + unlockDelay - // })); - - // // Emit a detailed event - // emit RewardClaimed(msg.sender, withdraw_amount); - - // } - // // Process restake - // if (restake_amount > 0) { - // // Check if user has a fixed reward rate set - // uint256 finalRewardRate; - // if (addressFixedRate[msg.sender] > 0) { - // // Use the fixed rate - // finalRewardRate = addressFixedRate[msg.sender]; - // } else { - // // restake = true - // finalRewardRate = getUserRewardRate(msg.sender, true); - // } - - // stakes[msg.sender].push(Stake({ - // amount: restake_amount, - // lastClaimed: block.timestamp, - // dailyRewardRate: finalRewardRate, - // unlockTime: block.timestamp + pool.lockupPeriod, - // complete: false - // })); - - // emit Staked(msg.sender, restake_amount); - // } - // } - function createStakeForUser(address _user, uint256 _amount) external onlyOwner { require(_amount != 0, "Invalid amount"); @@ -591,177 +626,177 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp return finalRewardRate; } - // function claimRewards() external nonReentrant { - // uint256 totalReward = 0; + function claimRewards() external nonReentrant { + uint256 totalReward = 0; - // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { - // Stake storage stake = stakes[msg.sender][i]; - // if (stake.amount > 0) { - // uint rewards = getPoolRewards(msg.sender, i); - // totalReward = totalReward + rewards; - // stake.lastClaimed = block.timestamp; - // } - // } + for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { + Stake storage stake = stakes[msg.sender][i]; + if (stake.amount > 0) { + uint rewards = getPoolRewards(msg.sender, i); + totalReward = totalReward + rewards; + stake.lastClaimed = block.timestamp; + } + } - // require(totalReward != 0, "No rewards to claim"); - // require(pool.totalRewards >= totalReward, "Insufficient rewards in the pool"); + require(totalReward != 0, "No rewards"); + require(pool.totalRewards >= totalReward, "Insufficient rewards"); - // pool.totalRewards = pool.totalRewards - totalReward; - // IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward); + pool.totalRewards = pool.totalRewards - totalReward; + IERC20(pool.tokenAddress).safeTransfer(msg.sender, totalReward); - // emit RewardClaimed(msg.sender, totalReward); - // } + emit RewardClaimed(msg.sender, totalReward); + } - // function claimStake(uint256 _stakeIndex) external nonReentrant { - // // Ensure the stake index is valid - // require(_stakeIndex < stakes[msg.sender].length, "Invalid stake index"); + function claimStake(uint256 _stakeIndex) external nonReentrant { + // Ensure the stake index is valid + require(_stakeIndex < stakes[msg.sender].length, "Invalid stake index"); - // // Load the stake - // Stake storage stake = stakes[msg.sender][_stakeIndex]; - // uint256 _amount = stake.amount; + // Load the stake + Stake storage stake = stakes[msg.sender][_stakeIndex]; + 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 - // require(_amount != 0, "No amount to claim"); + // Ensure there is a stake to claim + require(_amount != 0, "amount 0"); - // // Ensure the stake is unlocked (if using lockup periods) - // require(block.timestamp >= stake.unlockTime, "Stake is still locked"); + // Ensure the stake is unlocked (if using lockup periods) + require(block.timestamp >= stake.unlockTime, "Stake locked"); - // // Update state before external calls - // stake.amount = 0; - // stake.complete = true; - // withdrawLiabilities += _amount; + // Update state before external calls + stake.amount = 0; + stake.complete = true; + withdrawLiabilities += _amount; - // if (pool.totalStaked >= _amount) { - // pool.totalStaked -= _amount; - // } else { - // pool.totalStaked = 0; - // } + if (pool.totalStaked >= _amount) { + pool.totalStaked -= _amount; + } else { + pool.totalStaked = 0; + } - // // Create temporary the stake for the user to delay withdraw - // withdrawStake[msg.sender].push(WithdrawStake({ - // stakeId: _stakeIndex, - // amount: _amount, - // unlockTime: block.timestamp + unlockDelay - // })); + // Create temporary the stake for the user to delay withdraw + withdrawStake[msg.sender].push(WithdrawStake({ + stakeId: _stakeIndex, + amount: _amount, + unlockTime: block.timestamp + unlockDelay + })); - // // Emit a detailed event - // emit RewardClaimed(msg.sender, _amount); - // } + // Emit a detailed event + emit RewardClaimed(msg.sender, _amount); + } - // /** - // * @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, - // * and transfers tokens to the caller. - // * - // * Requirements: - // * - Caller must have at least one stake. - // * - The stake must exist, be unlocked, and have a non-zero amount. - // * - The contract must have sufficient token balance. - // * - // * @param _stakeIndex The identifier of the stake to withdraw. - // */ - // function withdraw(uint256 _stakeIndex) external nonReentrant { - // WithdrawStake[] storage userStakes = withdrawStake[msg.sender]; - // require(userStakes.length > 0, "No stakes available for withdrawal"); + /** + * @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, + * and transfers tokens to the caller. + * + * Requirements: + * - Caller must have at least one stake. + * - The stake must exist, be unlocked, and have a non-zero amount. + * - The contract must have sufficient token balance. + * + * @param _stakeIndex The identifier of the stake to withdraw. + */ + function withdraw(uint256 _stakeIndex) external nonReentrant { + WithdrawStake[] storage userStakes = withdrawStake[msg.sender]; + require(userStakes.length > 0, "No stakes"); - // for (uint256 i = 0; i < userStakes.length; ++i) { - // WithdrawStake storage stake = userStakes[i]; - // if (stake.stakeId == _stakeIndex && stake.amount != 0) { - // require(block.timestamp >= stake.unlockTime, "Withdraw Stake is still locked"); + for (uint256 i = 0; i < userStakes.length; ++i) { + WithdrawStake storage stake = userStakes[i]; + if (stake.stakeId == _stakeIndex && stake.amount != 0) { + require(block.timestamp >= stake.unlockTime, "Withdraw locked"); - // uint256 _amount = stake.amount; - // uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); - // require(poolBalance >= _amount, "Insufficient rewards in the pool"); + uint256 _amount = stake.amount; + uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); + require(poolBalance >= _amount, "Insufficient rewards"); - // // Update state before external calls - // withdrawLiabilities -= _amount; - // stake.amount = 0; + // Update state before external calls + withdrawLiabilities -= _amount; + stake.amount = 0; - // // Transfer tokens - // IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount); - // emit StakeWithdrawn(msg.sender, _amount, _stakeIndex); - // return; - // } - // } + // Transfer tokens + IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount); + emit StakeWithdrawn(msg.sender, _amount, _stakeIndex); + return; + } + } - // // Revert if no matching stake with non-zero amount was found - // revert("Invalid stake index or already withdrawn"); - // } + // Revert if no matching stake with non-zero amount was found + revert("Invalid stake"); + } - // function compoundAllRewards() external { - // uint256 totalReward = 0; + function compoundAllRewards() external { + uint256 totalReward = 0; - // for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { - // Stake storage stake = stakes[msg.sender][i]; - // if (stake.amount > 0) { - // uint rewards = getPoolRewards(msg.sender, i); - // totalReward = totalReward + rewards; - // stake.lastClaimed = block.timestamp; - // } - // } + for (uint256 i = 0; i < stakes[msg.sender].length; ++i) { + Stake storage stake = stakes[msg.sender][i]; + if (stake.amount > 0) { + uint rewards = getPoolRewards(msg.sender, i); + totalReward = totalReward + rewards; + stake.lastClaimed = block.timestamp; + } + } - // require(totalReward > minStakeLock, "Not enough to compound"); + require(totalReward > minStakeLock, "Not enough to compound"); - // // Check if user has a fixed reward rate set - // uint256 finalRewardRate; - // if (addressFixedRate[msg.sender] > 0) { - // // Use the fixed rate - // finalRewardRate = addressFixedRate[msg.sender]; - // } else { - // // Default logic, restake = false - // finalRewardRate = getUserRewardRate(msg.sender, false); - // } + // Check if user has a fixed reward rate set + uint256 finalRewardRate; + if (addressFixedRate[msg.sender] > 0) { + // Use the fixed rate + finalRewardRate = addressFixedRate[msg.sender]; + } else { + // Default logic, restake = false + finalRewardRate = getUserRewardRate(msg.sender, false); + } - // stakes[msg.sender].push(Stake({ - // amount: totalReward, - // lastClaimed: block.timestamp, - // dailyRewardRate: finalRewardRate, - // unlockTime: block.timestamp + pool.lockupPeriod, - // complete: false - // })); + stakes[msg.sender].push(Stake({ + amount: totalReward, + lastClaimed: block.timestamp, + dailyRewardRate: finalRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); - // pool.totalStaked = pool.totalStaked + totalReward; - // emit CompoundRewards(msg.sender, totalReward); - // } + pool.totalStaked = pool.totalStaked + totalReward; + emit CompoundRewards(msg.sender, totalReward); + } - // function createVesting(address _token, uint256 _amount) external { - // require(_amount != 0, "Amount must be greater than zero"); - // address oracle = priceOracles[_token]; - // require(oracle != address(0), "Price oracle not set for this token"); - // IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); + function createVesting(address _token, uint256 _amount) external { + require(_amount != 0, "Amount must be greater than zero"); + address oracle = priceOracles[_token]; + require(oracle != address(0), "Oracle not set"); + 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; - // require(usdPrice > minStakeLock, "Amount must be greater minStakeLock"); + uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * _amount) / 1e18; + require(usdPrice > minStakeLock, "Amount must be greater minStakeLock"); - // // Update user's dollarsVested - // dollarsVested[msg.sender] += usdPrice; - // // Update token's vestedTotal - // vestedTotal[_token] += _amount; + // Update user's dollarsVested + dollarsVested[msg.sender] += usdPrice; + // Update token's vestedTotal + vestedTotal[_token] += _amount; - // vestings[msg.sender].push(Vesting({ - // amount: _amount, - // bonus: bonus, - // lockedUntil: block.timestamp + lockupDuration, - // claimedAmount: 0, - // claimedBonus: 0, - // lastClaimed: block.timestamp, - // createdAt: block.timestamp, - // token: _token, - // complete: false, - // usdAmount: usdPrice - // })); + vestings[msg.sender].push(Vesting({ + amount: _amount, + bonus: bonus, + lockedUntil: block.timestamp + lockupDuration, + claimedAmount: 0, + claimedBonus: 0, + lastClaimed: block.timestamp, + createdAt: block.timestamp, + token: _token, + complete: false, + usdAmount: usdPrice + })); - // emit VestingCreated(msg.sender, _amount, bonus); - // } + emit VestingCreated(msg.sender, _amount, bonus); + } function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) { Vesting storage vesting = vestings[_user][_vestingIndex]; @@ -824,108 +859,108 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp } - // function claimVesting(uint256 _vestingIndex) external nonReentrant { - // Vesting storage vesting = vestings[msg.sender][_vestingIndex]; - // require(vesting.complete == false, "Stake is Complete"); - // uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex); + function claimVesting(uint256 _vestingIndex) external nonReentrant { + Vesting storage vesting = vestings[msg.sender][_vestingIndex]; + require(vesting.complete == false, "Stake Complete"); + uint256 maxClaim = getUnlockedVesting(msg.sender, _vestingIndex); - // require(maxClaim >= vesting.claimedAmount, "Invalid claim amount"); - // uint256 amountToClaim = maxClaim - vesting.claimedAmount; - // require(amountToClaim != 0, "No vested amount to claim"); + require(maxClaim >= vesting.claimedAmount, "Invalid claim"); + uint256 amountToClaim = maxClaim - vesting.claimedAmount; + require(amountToClaim != 0, "Claim 0"); - // vesting.claimedAmount = vesting.claimedAmount + amountToClaim; - // if (vesting.claimedAmount >= vesting.amount) { - // vesting.complete = true; - // } - // // Update user's dollarsVested - // if (dollarsVested[msg.sender] > 0) { - // uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18; - // if (usdPrice >= dollarsVested[msg.sender]) { - // dollarsVested[msg.sender] = 0; - // } else { - // dollarsVested[msg.sender] -= usdPrice; - // } - // } - // vestedTotal[vesting.token] -= amountToClaim; - // IERC20(vesting.token).safeTransfer(msg.sender, amountToClaim); + vesting.claimedAmount = vesting.claimedAmount + amountToClaim; + if (vesting.claimedAmount >= vesting.amount) { + vesting.complete = true; + } + // Update user's dollarsVested + if (dollarsVested[msg.sender] > 0) { + uint256 usdPrice = (iPriceOracle(priceOracles[vesting.token]).getLatestPrice(vesting.token) * amountToClaim) / 1e18; + if (usdPrice >= dollarsVested[msg.sender]) { + dollarsVested[msg.sender] = 0; + } else { + dollarsVested[msg.sender] -= usdPrice; + } + } + vestedTotal[vesting.token] -= amountToClaim; + IERC20(vesting.token).safeTransfer(msg.sender, amountToClaim); - // emit VestingClaimed(msg.sender, amountToClaim, 0); - // } + emit VestingClaimed(msg.sender, amountToClaim, 0); + } - // function claimAllVestingByToken(address _token) external nonReentrant { - // uint256 totalReward = 0; - // uint256 vestingsProcessed = 0; + function claimAllVestingByToken(address _token) external nonReentrant { + uint256 totalReward = 0; + uint256 vestingsProcessed = 0; - // for (uint256 i = 0; i < vestings[msg.sender].length; ++i) { - // Vesting storage vesting = vestings[msg.sender][i]; + for (uint256 i = 0; i < vestings[msg.sender].length; ++i) { + Vesting storage vesting = vestings[msg.sender][i]; - // if (vesting.token == _token && !vesting.complete) { - // uint256 maxClaim = getUnlockedVesting(msg.sender, i); - // require(maxClaim >= vesting.claimedAmount, "Invalid claim amount"); + if (vesting.token == _token && !vesting.complete) { + uint256 maxClaim = getUnlockedVesting(msg.sender, i); + require(maxClaim >= vesting.claimedAmount, "Invalid claim"); - // uint256 amountToClaim = maxClaim - vesting.claimedAmount; - // if (amountToClaim > 0) { - // vesting.claimedAmount = vesting.claimedAmount + amountToClaim; - // totalReward = totalReward + amountToClaim; - // vesting.lastClaimed = block.timestamp; + uint256 amountToClaim = maxClaim - vesting.claimedAmount; + if (amountToClaim > 0) { + vesting.claimedAmount = vesting.claimedAmount + amountToClaim; + totalReward = totalReward + amountToClaim; + vesting.lastClaimed = block.timestamp; - // // Mark vesting as complete if fully claimed - // if (vesting.claimedAmount >= vesting.amount) { - // vesting.complete = true; - // } + // Mark vesting as complete if fully claimed + if (vesting.claimedAmount >= vesting.amount) { + vesting.complete = true; + } - // vestingsProcessed++; - // } - // } - // } + vestingsProcessed++; + } + } + } - // require(totalReward != 0, "No rewards to claim"); + require(totalReward != 0, "No rewards to claim"); - // // Update user's dollarsVested - // if (dollarsVested[msg.sender] > 0) { - // uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18; - // if (usdPrice >= dollarsVested[msg.sender]) { - // dollarsVested[msg.sender] = 0; - // } else { - // dollarsVested[msg.sender] -= usdPrice; - // } - // } + // Update user's dollarsVested + if (dollarsVested[msg.sender] > 0) { + uint256 usdPrice = (iPriceOracle(priceOracles[_token]).getLatestPrice(_token) * totalReward) / 1e18; + if (usdPrice >= dollarsVested[msg.sender]) { + dollarsVested[msg.sender] = 0; + } else { + dollarsVested[msg.sender] -= usdPrice; + } + } - // // Ensure the contract has enough balance to fulfill the claim - // uint256 poolBalance = IERC20(_token).balanceOf(address(this)); - // require(poolBalance >= totalReward, "Insufficient rewards in the pool"); - // // Update vesting total - // vestedTotal[_token] -= totalReward; - // // Transfer the aggregated reward - // IERC20(_token).safeTransfer(msg.sender, totalReward); + // Ensure the contract has enough balance to fulfill the claim + uint256 poolBalance = IERC20(_token).balanceOf(address(this)); + require(poolBalance >= totalReward, "Insufficient rewards"); + // Update vesting total + vestedTotal[_token] -= totalReward; + // Transfer the aggregated reward + IERC20(_token).safeTransfer(msg.sender, totalReward); - // emit RewardClaimed(msg.sender, totalReward); - // } + emit RewardClaimed(msg.sender, totalReward); + } - // function claimBonus(uint256 _vestingIndex) external nonReentrant { - // Vesting storage vesting = vestings[msg.sender][_vestingIndex]; - // uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex); + function claimBonus(uint256 _vestingIndex) external nonReentrant { + Vesting storage vesting = vestings[msg.sender][_vestingIndex]; + uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex); - // require(maxBonus >= vesting.claimedBonus, "Invalid claim amount"); - // uint256 bonusToClaim = maxBonus - vesting.claimedBonus; - // require(bonusToClaim != 0, "No vested amount to claim"); + require(maxBonus >= vesting.claimedBonus, "Invalid claim amount"); + uint256 bonusToClaim = maxBonus - vesting.claimedBonus; + require(bonusToClaim != 0, "No claim"); - // vesting.claimedBonus = vesting.claimedBonus + bonusToClaim; - // withdrawLiabilities += bonusToClaim; + vesting.claimedBonus = vesting.claimedBonus + 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. - // // Add 1e6 to the vesting index to distinguish them from normal stakes. - // withdrawStake[msg.sender].push(WithdrawStake({ - // stakeId: _vestingIndex + 1e6, - // amount: bonusToClaim, - // unlockTime: block.timestamp + unlockDelay - // })); + // Create temporary the stake for the user to delay withdraw. + // Add 1e6 to the vesting index to distinguish them from normal stakes. + withdrawStake[msg.sender].push(WithdrawStake({ + stakeId: _vestingIndex + 1e6, + amount: bonusToClaim, + unlockTime: block.timestamp + unlockDelay + })); - // emit BonusClaimed(msg.sender, bonusToClaim); - // } + emit BonusClaimed(msg.sender, bonusToClaim); + } function setPriceOracle(address _token, address _oracle) external onlyOwner { priceOracles[_token] = _oracle; @@ -1039,7 +1074,7 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp return userStakes[i]; } } - revert("WithdrawStake with the specified stakeId not found for this user."); + revert("WithdrawStake not found"); } /// @notice Function that lets you look up an address’s stake by vestingId. @@ -1053,7 +1088,7 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp return userStakes[i]; } } - revert("WithdrawStake with the specified stakeId not found for this user."); + revert("WithdrawStake not found"); } /// @notice Function that returns an array of all the user's withdrawStakes. @@ -1069,11 +1104,11 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp /// @param price The price of the stake. function sellStake(uint256 _stakeId, uint256 price) external { Stake storage stake = stakes[msg.sender][_stakeId]; - require(!stake.complete, "Stake already complete"); - require(stake.amount != 0, "Stake amount is 0"); + require(!stake.complete, "Stake complete"); + require(stake.amount != 0, "Amount 0"); // Ensure the stake isn't already on sale. require(sellStakes[msg.sender][_stakeId].amount == 0, "Stake already on sale"); - require(price >= (stake.amount * sellMin) / 100, "Price is too low"); + require(price >= (stake.amount * sellMin) / 100, "Price too low"); // Create a SellStake entry directly in the mapping. sellStakes[msg.sender][_stakeId] = SellStake({ @@ -1100,11 +1135,11 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp /// @param _stakeId The stake ID to cancel the sale. function cancelSellStake(uint256 _stakeId) external { SellStake storage sellStakeEntry = sellStakes[msg.sender][_stakeId]; - require(sellStakeEntry.amount != 0, "Sell stake not found"); + require(sellStakeEntry.amount != 0, "Stake not found"); // Access the original stake. Stake storage stake = stakes[msg.sender][_stakeId]; - require(stake.amount == 0, "Stake not in sell state"); + require(stake.amount == 0, "Stake not for sale"); // Restore the original stake's amount. stake.amount = sellStakeEntry.amount; @@ -1130,8 +1165,8 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp /// @param newPrice The new price of the stake. function updateSellStake(uint256 _stakeId, uint256 newPrice) external { SellStake storage sellStakeEntry = sellStakes[msg.sender][_stakeId]; - require(sellStakeEntry.amount != 0, "Sell stake not found"); - require(newPrice >= (sellStakeEntry.amount * sellMin) / 100, "New price is too low"); + require(sellStakeEntry.amount != 0, "Stake not found"); + require(newPrice >= (sellStakeEntry.amount * sellMin) / 100, "New price too low"); sellStakeEntry.bonusAmount = (newPrice * sellKickBack) / 100; sellStakeEntry.price = newPrice; @@ -1149,7 +1184,7 @@ contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUp /// @param _stakeId The original stake id associated with the sell stake. function buySellStake(address seller, uint256 _stakeId) external nonReentrant { SellStake storage sellStakeEntry = sellStakes[seller][_stakeId]; - require(sellStakeEntry.amount != 0, "Sell stake not available"); + require(sellStakeEntry.amount != 0, "Stake not available"); // Transfer the sale price from the buyer to this contract. IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), sellStakeEntry.price);