From c667dc197bff027833d1e73b2d1a2d5586aa2ab1 Mon Sep 17 00:00:00 2001 From: mystmaker33 Date: Tue, 10 Jun 2025 22:39:45 -0400 Subject: [PATCH] Initial Commit --- .gitignore | 5 + README.md | 93 ++ contracts/base_paca.sol | 1189 ++++++++++++++++ contracts/bsc_paca.sol | 1235 +++++++++++++++++ contracts/s_pricefeed.sol | 25 + contracts/swapx_pricefeed.sol | 58 + hardhat.config.js | 188 +++ .../deployments/chain-22152/journal.jsonl | 7 + .../deployments/chain-31337/journal.jsonl | 7 + ignition/modules/paca.js | 9 + scripts/advanceTime.js | 28 + scripts/createStakes.js | 149 ++ scripts/deploy.js | 91 ++ scripts/deployProxy.js | 130 ++ scripts/deploySOracle.js | 30 + unmadebot.py | 144 ++ 16 files changed, 3388 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 contracts/base_paca.sol create mode 100644 contracts/bsc_paca.sol create mode 100644 contracts/s_pricefeed.sol create mode 100644 contracts/swapx_pricefeed.sol create mode 100644 hardhat.config.js create mode 100644 ignition/deployments/chain-22152/journal.jsonl create mode 100644 ignition/deployments/chain-31337/journal.jsonl create mode 100644 ignition/modules/paca.js create mode 100644 scripts/advanceTime.js create mode 100644 scripts/createStakes.js create mode 100644 scripts/deploy.js create mode 100644 scripts/deployProxy.js create mode 100644 scripts/deploySOracle.js create mode 100644 unmadebot.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fc46ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +cache +artifacts +.env +*.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..018525d --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# Paca + + + +## Getting started + +To make it easy for you to get started with GitLab, here's a list of recommended next steps. + +Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! + +## Add your files + +- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files +- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: + +``` +cd existing_repo +git remote add origin https://gitlab.com/Sascha3333/paca.git +git branch -M main +git push -uf origin main +``` + +## Integrate with your tools + +- [ ] [Set up project integrations](https://gitlab.com/Sascha3333/paca/-/settings/integrations) + +## Collaborate with your team + +- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) +- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) +- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) +- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) +- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) + +## Test and Deploy + +Use the built-in continuous integration in GitLab. + +- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) +- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) +- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) +- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) +- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) + +*** + +# Editing this README + +When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. + +## Suggestions for a good README + +Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. + +## Name +Choose a self-explaining name for your project. + +## Description +Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. + +## Badges +On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. + +## Visuals +Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. + +## Installation +Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. + +## Usage +Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. + +## Support +Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. + +## Roadmap +If you have ideas for releases in the future, it is a good idea to list them in the README. + +## Contributing +State if you are open to contributions and what your requirements are for accepting them. + +For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. + +You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. + +## Authors and acknowledgment +Show your appreciation to those who have contributed to the project. + +## License +For open source projects, say how it is licensed. + +## Project status +If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. diff --git a/contracts/base_paca.sol b/contracts/base_paca.sol new file mode 100644 index 0000000..cb49dc1 --- /dev/null +++ b/contracts/base_paca.sol @@ -0,0 +1,1189 @@ +// SPDX-License-Identifier: MIT +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; + +pragma solidity ^0.8.20; + +interface iPriceOracle { + // returns price in USD + function getLatestPrice(address token) external view returns (uint256); +} + +// File: paca.sol + +contract PacaFinanceWithBoostAndScheduleUSDC is Initializable, ReentrancyGuardUpgradeable { + using SafeERC20 for IERC20; + + + // Struct Definitions + struct Pool { + uint256 lockupPeriod; + uint256 dailyRewardRate; + uint256 totalStaked; + uint256 totalRewards; + address tokenAddress; + } + + struct Stake { + uint256 amount; + uint256 lastClaimed; + uint256 dailyRewardRate; + uint256 unlockTime; + bool complete; + } + + struct StakeInput { + address user; + uint256 amount; + uint256 lastClaimed; + uint256 unlockTime; + uint256 dailyRewardRate; + } + + struct Vesting { + uint256 amount; + uint256 bonus; + uint256 lockedUntil; + uint256 claimedAmount; + uint256 claimedBonus; + uint256 lastClaimed; + uint256 createdAt; + address token; + bool complete; + uint256 usdAmount; + } + + struct UnlockStep { + uint256 timeOffset; + uint256 percentage; + } + + struct BoostRange { + uint256 minTokens; + uint256 maxTokens; + uint256 boostPercentage; + } + + struct WithdrawStake { + uint256 stakeId; + uint256 amount; + uint256 unlockTime; + } + + struct SellStake { + uint256 price; + uint256 bonusAmount; + uint256 amount; + uint256 lastClaimed; + uint256 dailyRewardRate; + uint256 origUnlockTime; + } + + struct SellStakeKey { + address seller; + uint256 stakeId; + } + + BoostRange[] public boosttiers; + + // Contract Variables + Pool public pool; + address public owner; + mapping(address => bool) public owners; + mapping(address => Stake[]) public stakes; + mapping(address => Vesting[]) public vestings; + mapping(address => UnlockStep[]) public unlockSchedules; + mapping(address => address) public priceOracles; + mapping(address => uint256) public dollarsVested; // per user address + uint256 public lockupDuration; + uint256 public minStakeLock; + uint256 private constant BONUS_PERCENTAGE = 10; + + mapping(address => bool) public authorizedBots; + mapping(address => uint256) public vestedTotal; // per vesting token + uint256 public unlockDelay; + uint256 public withdrawLiabilities; + mapping(address => WithdrawStake[]) public withdrawStake; + uint256 public restakeBonus; + mapping(address => uint256) public addressFixedRate; + mapping(address => mapping(uint256 => SellStake)) public sellStakes; + uint256 public sellTax; + uint256 public sellKickBack; + SellStakeKey[] public sellStakeKeys; + mapping(address => mapping(uint256 => uint256)) private sellStakeKeyIndex; + uint256 public sellMin; + + // Events + event Staked(address indexed user, uint256 amount); + event RewardClaimed(address indexed user, uint256 reward); + event VestingCreated(address indexed user, uint256 amount, uint256 bonus); + event VestingClaimed(address indexed user, uint256 amount, uint256 bonus); + event BonusClaimed(address indexed user, uint256 bonus); + event PoolUpdated(uint256 lockupPeriod, uint256 dailyRewardRate); + event UnlockScheduleSet(address indexed token); + event FundsWithdrawn(address indexed owner, address indexed token, uint256 amount); + event RewardsDeposited(uint256 amount); + event CompoundRewards(address indexed user, uint256 amount); + event MinStakeLockUpdated(uint256 amount); + event StakeWithdrawn(address indexed user, uint256 amount, uint256 stakeId); + event StakeUpForSale(address indexed user, uint256 saleAmount, uint256 stakeId); + event StakeSaleCancelled(address indexed user, uint256 stakeId); + event StakeSold(address indexed seller, address indexed buyer,uint256 saleAmount, uint256 stakeId); + + // Modifiers + modifier onlyOwner() { + require(owners[msg.sender], "Not authorized"); + _; + } + modifier onlyBot() { + require(authorizedBots[msg.sender], "Caller is not an authorized bot"); + _; + } + + function initialize() public initializer { + __ReentrancyGuard_init(); // Initialize ReentrancyGuardUpgradeable + owner = 0x41970Ce76b656030A79E7C1FA76FC4EB93980255; + owners[0x41970Ce76b656030A79E7C1FA76FC4EB93980255] = true; + + // lockupDuration = 250 days; + // minStakeLock = 16 ether; + + // pool.tokenAddress = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + // pool.lockupPeriod = 250 * 1 days; + // pool.dailyRewardRate = 33; + + // // Price oracle for a specific tokens + // // priceOracles[0x940181a94A35A4569E4529A3CDfB74e38FD98631] = 0x0Dde1b42F7B3891C9731280A74081501729A73c5; + // authorizedBots[0xbf12D3b827a230F7390EbCc9b83b289FdC98ba81] = true; + // authorizedBots[0x7c40f272570fdf9549d6f67493aC250a1DB52F27] = true; + + // unlockDelay = 60 * 60 * 36; + // restakeBonus = 3; + } + + // Ownership Management + function addOwner(address _newOwner) external onlyOwner { + require(!owners[_newOwner], "Already an owner"); + owners[_newOwner] = true; + } + + function removeOwner(address _owner) external onlyOwner { + require(owners[_owner], "Not an owner"); + require(_owner != msg.sender, "Cannot remove yourself"); + owners[_owner] = false; + } + + /// @notice Function to add a bot to the list (only callable by the contract owner) + function addBot(address bot) external onlyOwner { + require(bot != address(0), "Invalid address"); + authorizedBots[bot] = true; + } + + // /// @notice Function to remove a bot from the list (only callable by the contract owner) + // function removeBot(address bot) external onlyOwner { + // require(bot != address(0), "Invalid address"); + // authorizedBots[bot] = false; + // } + + // Admin Functions + function updatePool(uint256 _lockupPeriod, uint256 _dailyRewardRate) external onlyOwner { + pool.lockupPeriod = _lockupPeriod * 1 days; + pool.dailyRewardRate = _dailyRewardRate; + emit PoolUpdated(_lockupPeriod, _dailyRewardRate); + } + + function depositRewards(uint256 _amount) external onlyOwner { + IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); + pool.totalRewards = pool.totalRewards + _amount; + emit RewardsDeposited(_amount); + } + + function updateStakeMin(uint256 _amount) external onlyOwner { + minStakeLock = _amount; + emit MinStakeLockUpdated(_amount); + } + + function updateUnlockDelay(uint256 _delay) external onlyOwner { + unlockDelay = _delay; + } + + function updateRestakeBonus(uint256 _newBonus) external onlyOwner { + restakeBonus = _newBonus; + } + + /// @notice New Stake Sell Tax + /// @param _newTax The rate expressed in 2 digits, ex: 20 + function updateSellTax(uint256 _newTax) external onlyOwner { + sellTax = _newTax; + } + + /// @notice New Stake Sell Minimum + /// @param _newMin The rate expressed in 2 digits, ex: 30 + function updateSellMin(uint256 _newMin) external onlyOwner { + sellMin = _newMin; + } + + /// @notice New Stake Sell kickback to the buyer + /// @param _newKickback The rate expressed in 2 digits, ex: 5 + function updatesellKickBack(uint256 _newKickback) external onlyOwner { + sellKickBack = _newKickback; + } + + /// @notice Function to add an address to have a fixed daily reward (only callable by the contract owner) + /// @param _addr The address to give a fixed rate + /// @param _rate The fixed rate expressed in 2 digits, ex: 40 + function addFixedRate(address _addr, uint _rate) external onlyOwner { + require(_addr != address(0), "Invalid address"); + addressFixedRate[_addr] = _rate; + } + + /// @notice Function to remove an address' fixed daily reward (only callable by the contract owner) + /// @param _addr The address to 0 out + function removeFixedRate(address _addr) external onlyOwner { + require(_addr != address(0), "Invalid address"); + addressFixedRate[_addr] = 0; + } + + // /// @notice Add or edit a tier range *commented out for size constraints* + // function addOrEditTier(uint256 minTokens, uint256 maxTokens, uint256 boostPercentage) public onlyOwner { + // require(minTokens < maxTokens, "Invalid range: minTokens must be < maxTokens"); + // require(!rangesOverlap(minTokens, maxTokens), "Range overlaps with existing tiers"); + + // // Check if editing an existing range + // for (uint256 i = 0; i < boosttiers.length; ++i) { + // if (boosttiers[i].minTokens == minTokens && boosttiers[i].maxTokens == maxTokens) { + // // Edit the existing range + // boosttiers[i].boostPercentage = boostPercentage; + // return; + // } + // } + + // // Add new range + // boosttiers.push(BoostRange(minTokens, maxTokens, boostPercentage)); + + // // Sort the ranges after adding + // sortRanges(); + // } + + // // Check for range overlap + // function rangesOverlap(uint256 minTokens, uint256 maxTokens) internal view returns (bool) { + // for (uint256 i = 0; i < boosttiers.length; ++i) { + // if (minTokens <= boosttiers[i].maxTokens && maxTokens >= boosttiers[i].minTokens) { + // return true; + // } + // } + // return false; + // } + + // /// @notice Sort ranges by minTokens + // function sortRanges() internal { + // for (uint256 i = 0; i < boosttiers.length; ++i) { + // for (uint256 j = i + 1; j < boosttiers.length; j++) { + // if (boosttiers[i].minTokens > boosttiers[j].minTokens) { + // // Swap ranges + // BoostRange memory temp = boosttiers[i]; + // boosttiers[i] = boosttiers[j]; + // boosttiers[j] = temp; + // } + // } + // } + // } + + // /// @notice Remove a range by index + // function removeTier(uint256 index) external onlyOwner { + // require(index < boosttiers.length, "Index out of bounds"); + // for (uint256 i = index; i < boosttiers.length - 1; ++i) { + // boosttiers[i] = boosttiers[i + 1]; + // } + // boosttiers.pop(); + // } + + function withdrawFromStakingPool(uint256 _amount) external onlyOwner { + IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount); + emit FundsWithdrawn(msg.sender, pool.tokenAddress, _amount); + } + + function withdrawFromVestingPool(address _token, uint256 _amount) external onlyOwner { + IERC20(_token).safeTransfer(msg.sender, _amount); + 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"); + + 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 + + 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 + })); + } + + emit UnlockScheduleSet(_token); + } + + /// @notice Get the boost percentage for a given token amount + function getBoost(uint256 depositedTokens) public view returns (uint256) { + for (uint256 i = 0; i < boosttiers.length; ++i) { + if (depositedTokens >= boosttiers[i].minTokens && depositedTokens <= boosttiers[i].maxTokens) { + return boosttiers[i].boostPercentage; + } + } + return 0; // Default boost if no range matches + } + + /// @notice This function will end and clear a user's stakes. + /// @dev Only to be used by bots in emergencies + /// @param user The user whose stakes will be ended and 0'd + function clearStakes(address user) external onlyBot { + uint256 clearedStakes = 0; + + for (uint256 i = 0; i < stakes[user].length; ++i) { + Stake storage stake = stakes[user][i]; + clearedStakes = clearedStakes + stake.amount; + stake.amount = 0; + stake.complete = true; + } + + pool.totalStaked = pool.totalStaked - clearedStakes; + } + + /// @notice This function will end and clear a user's withdraw stakes. + /// @dev Only to be used by bots in emergencies + /// @param user The user whose withdraw stakes will be 0'd + function clearWithdrawStakes(address user) external onlyBot { + uint256 clearedStakes = 0; + + for (uint256 i = 0; i < withdrawStake[user].length; ++i) { + WithdrawStake storage stake = withdrawStake[user][i]; + clearedStakes = clearedStakes + stake.amount; + stake.amount = 0; + } + + 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); + // } + + // // 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"); + + stakes[_user].push(Stake({ + amount: _amount, + lastClaimed: block.timestamp, + dailyRewardRate: pool.dailyRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); + + pool.totalStaked = pool.totalStaked + _amount; + emit Staked(_user, _amount); + } + + + function createStakes(StakeInput[] calldata stakesInput) external onlyBot payable { + uint256 totalLength = stakesInput.length; + + for (uint256 i; i < totalLength;) { + StakeInput calldata stakeInput = stakesInput[i]; + + // Update pool total + pool.totalStaked = pool.totalStaked + stakeInput.amount; + + // Create the stake for the user + stakes[stakeInput.user].push(Stake({ + amount: stakeInput.amount, + lastClaimed: stakeInput.lastClaimed, + dailyRewardRate: stakeInput.dailyRewardRate, + unlockTime: stakeInput.unlockTime, + complete: false + })); + + unchecked { + ++i; + } + } + } + + /// @notice Calculates pending rewards for a specific user's stake + /// @dev Rewards only accumulate until the stake's unlockTime is reached + /// @param _user Address of the stake owner + /// @param _stakeIndex Index of the stake in the user's stakes array + /// @return Accumulated rewards since last claim, stopping at unlockTime if applicable + function getPoolRewards(address _user, uint _stakeIndex) public view returns (uint256) { + Stake storage stake = stakes[_user][_stakeIndex]; + + uint256 endTime = block.timestamp < stake.unlockTime ? block.timestamp : stake.unlockTime; + uint256 elapsedTime = endTime > stake.lastClaimed ? endTime - stake.lastClaimed : 0; + uint256 rewards = (stake.amount * stake.dailyRewardRate * elapsedTime) / 1 days / 10000; + + return rewards; + } + + function getUserRewardRate(address _user, bool isRestake) public view returns (uint256) { + uint256 finalRewardRate = pool.dailyRewardRate + getBoost(dollarsVested[_user]); + if (isRestake) { + finalRewardRate += restakeBonus; + } + return finalRewardRate; + } + + // 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; + // } + // } + + // 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); + + // 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"); + + // // Load the stake + // Stake storage stake = stakes[msg.sender][_stakeIndex]; + // uint256 _amount = stake.amount; + + // uint rewards = getPoolRewards(msg.sender, _stakeIndex); + + // _amount = _amount + rewards; + + // // 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"); + + // // Update state before external calls + // stake.amount = 0; + // stake.complete = true; + // withdrawLiabilities += _amount; + + // 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 + // })); + + // // 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"); + +// 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; + +// // 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"); + +// // 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; +// } +// } + +// // Revert if no matching stake with non-zero amount was found +// revert("Invalid stake index or already withdrawn"); +// } + + + // 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; + // } + // } + + // 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); + // } + + // 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); + // } + + // 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 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; + + + // 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); + // } + + function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) { + Vesting storage vesting = vestings[_user][_vestingIndex]; + uint256 timeElapsed = block.timestamp - vesting.createdAt; + address token = vesting.token; + + uint256 unlockedAmount = 0; + + for (uint256 i = 0; i < unlockSchedules[token].length; ++i) { + UnlockStep storage step = unlockSchedules[token][i]; + uint256 timeTier = step.timeOffset; + uint256 percentage = step.percentage; + + if (timeElapsed >= timeTier) { + unlockedAmount = unlockedAmount + ((vesting.amount * percentage) / 10000); + } + } + + return unlockedAmount; + } + + function getVestingSchedule(address _user, uint256 _vestingIndex) public view returns (uint256[] memory, uint256[] memory) { + Vesting storage vesting = vestings[_user][_vestingIndex]; + address token = vesting.token; + + uint256 scheduleLength = unlockSchedules[token].length; + uint256[] memory unlockTimestamps = new uint256[](scheduleLength); + uint256[] memory unlockPercentages = new uint256[](scheduleLength); + + for (uint256 i = 0; i < scheduleLength; ++i) { + UnlockStep storage step = unlockSchedules[token][i]; + + // Calculate the absolute unlock timestamp + unlockTimestamps[i] = vesting.createdAt + step.timeOffset; + unlockPercentages[i] = step.percentage; // Percentage is stored as scaled by 10000 (e.g., 2500 = 25%) + } + + return (unlockTimestamps, unlockPercentages); + } + + function getUnlockedVestingBonus(address _user, uint256 _vestingIndex) public view returns (uint256) { + Vesting storage vesting = vestings[_user][_vestingIndex]; + uint256 timeElapsed = block.timestamp - vesting.createdAt; + address token = vesting.token; + + uint256 unlockedAmount = 0; + + for (uint256 i = 0; i < unlockSchedules[token].length; ++i) { + UnlockStep storage step = unlockSchedules[token][i]; + uint256 timeTier = step.timeOffset; + uint256 percentage = step.percentage; + uint256 maxBonusAmount = (vesting.usdAmount * BONUS_PERCENTAGE) / 100; + + if (timeElapsed >= timeTier) { + unlockedAmount = unlockedAmount + ((maxBonusAmount * percentage) / 10000); + } + } + + return unlockedAmount; + } + + + // 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"); + + // 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); + // } + + // 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]; + + // 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; + + // // Mark vesting as complete if fully claimed + // if (vesting.claimedAmount >= vesting.amount) { + // vesting.complete = true; + // } + + // vestingsProcessed++; + // } + // } + // } + + // 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; + // } + // } + + // // 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); + // } + + + // 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"); + + // vesting.claimedBonus = vesting.claimedBonus + bonusToClaim; + // withdrawLiabilities += 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 + // })); + + // emit BonusClaimed(msg.sender, bonusToClaim); + // } + + function setPriceOracle(address _token, address _oracle) external onlyOwner { + priceOracles[_token] = _oracle; + } + + function viewRewards(address _user) external view returns (uint256) { + uint256 totalReward = 0; + + for (uint256 i = 0; i < stakes[_user].length; ++i) { + uint rewards = getPoolRewards(_user, i); + totalReward = totalReward + rewards; + } + + return totalReward; + } + + /// @notice View function to get all stakes for a specific address + function getStakes(address user) external view returns (Stake[] memory) { + return stakes[user]; + } + + /// @notice View function to get all vestings for a specific address + function getVestings(address user) external view returns (Vesting[] memory) { + return vestings[user]; + } + + /// @notice View to monitor contract pool deficits + function getPoolStatus() external view returns (uint256) { + uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); + + // If the balance is greater than or equal to liabilities, return 0 + if (poolBalance >= withdrawLiabilities) { + return 0; + } + + // Otherwise, return the deficit (amount needed to cover liabilities) + return withdrawLiabilities - poolBalance; + } + + /** + * @notice Returns the vested amounts and USD values for an array of tokens. + * @param _tokens The array of token addresses to evaluate. + * @return amounts The array of vested amounts for each token. + * @return usdValues The array of USD values for each token’s vested amount. + * @return totalUsd The total USD value of all vested tokens in the array. + */ + function getVestedTotals(address[] calldata _tokens) + external + view + returns ( + uint256[] memory amounts, + uint256[] memory usdValues, + uint256 totalUsd + ) + { + uint256 length = _tokens.length; + amounts = new uint256[](length); + usdValues = new uint256[](length); + + for (uint256 i = 0; i < length; i++) { + address token = _tokens[i]; + + // 1. Get the total amount vested for this token. + uint256 tokenAmount = vestedTotal[token]; + amounts[i] = tokenAmount; + + // 2. Query the oracle for this token’s USD price. + // Assumes the oracle returns a price scaled by 1e18. + uint256 price = iPriceOracle(priceOracles[token]).getLatestPrice(token); + + // 3. Calculate the vested USD value: (price * amount) / 1e18 + uint256 valueInUsd = (price * tokenAmount) / 1e18; + usdValues[i] = valueInUsd; + + // 4. Accumulate the total USD amount + totalUsd += valueInUsd; + } + + return (amounts, usdValues, totalUsd); + } + + /// @notice Returns the total USD value of the user's unclaimed, uncomplete, stake amounts, based on current token prices from the oracle. + /// @return totalUsd The total unclaimed stake value, in USD (1e18 precision). + function getUserTotalUnclaimedUsdValue(address user) external view returns (uint256 totalUsd) { + uint256 length = vestings[user].length; + for (uint256 i = 0; i < length; i++) { + Vesting memory v = vestings[user][i]; + if (!v.complete) { + uint256 tokenPrice = iPriceOracle(priceOracles[v.token]).getLatestPrice(v.token); + + // The unclaimed portion of the stake + uint256 unclaimedAmount = v.amount - v.claimedAmount; + + // Convert unclaimed tokens to USD value + uint256 stakeUsd = (tokenPrice * unclaimedAmount) / 1e18; + + totalUsd += stakeUsd; + } + } + return totalUsd; + } + + + /// @notice Function that lets you look up an address’s stake by stakeId. + /// @param user The address to evaluate. + /// @param _stakeId The stakeId of the ORIGINAL stake that is waiting to be unlocked + function getWithdrawStake(address user, uint256 _stakeId) external view returns (WithdrawStake memory) { + WithdrawStake[] storage userStakes = withdrawStake[user]; + for (uint256 i = 0; i < userStakes.length; i++) { + if (userStakes[i].stakeId == _stakeId) { + return userStakes[i]; + } + } + revert("WithdrawStake with the specified stakeId not found for this user."); + } + + /// @notice Function that lets you look up an address’s stake by vestingId. + /// @param user The address to evaluate. + /// @param _vestingId The vestingId of the ORIGINAL vest that is waiting to be unlocked + function getVestingWithdrawStake(address user, uint256 _vestingId) external view returns (WithdrawStake memory) { + WithdrawStake[] storage userStakes = withdrawStake[user]; + uint256 boostedVestingId = _vestingId + 1e6; + for (uint256 i = 0; i < userStakes.length; i++) { + if (userStakes[i].stakeId == boostedVestingId) { + return userStakes[i]; + } + } + revert("WithdrawStake with the specified stakeId not found for this user."); + } + + /// @notice Function that returns an array of all the user's withdrawStakes. + /// @param user The address to evaluate. + /// @return An array of WithdrawStake for the given user. + function getAllWithdrawStakes(address user) external view returns (WithdrawStake[] memory) { + return withdrawStake[user]; + } + + /// @notice Function to put a stake for sale. + /// Sets the original stake amount to 0 to prevent any alterations while for sale. + /// @param _stakeId The stake to sell. + /// @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"); + // 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"); + + // Create a SellStake entry directly in the mapping. + sellStakes[msg.sender][_stakeId] = SellStake({ + price: price, + bonusAmount: (price * sellKickBack) / 100, + amount: stake.amount, + lastClaimed: stake.lastClaimed, + dailyRewardRate: stake.dailyRewardRate, + origUnlockTime: stake.unlockTime + }); + + // Lock the original stake by setting its amount to 0. + stake.amount = 0; + + // Add the key to the iteration array. + sellStakeKeys.push(SellStakeKey({ seller: msg.sender, stakeId: _stakeId })); + sellStakeKeyIndex[msg.sender][_stakeId] = sellStakeKeys.length - 1; + + emit StakeUpForSale(msg.sender, price, _stakeId); + } + + /// @notice Function to cancel a sell stake. + /// Restores the stake amount to the original stake and removes the sell stake. + /// @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"); + + // Access the original stake. + Stake storage stake = stakes[msg.sender][_stakeId]; + require(stake.amount == 0, "Stake not in sell state"); + + // Restore the original stake's amount. + stake.amount = sellStakeEntry.amount; + + delete sellStakes[msg.sender][_stakeId]; + + // Remove the key from the iteration array using swap-and-pop. + uint256 index = sellStakeKeyIndex[msg.sender][_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[msg.sender][_stakeId]; + + emit StakeSaleCancelled(msg.sender, _stakeId); + } + + /// @notice Function to update the price of a stake that is for sale. + /// @param _stakeId The stake ID to update. + /// @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"); + + sellStakeEntry.bonusAmount = (newPrice * sellKickBack) / 100; + sellStakeEntry.price = newPrice; + + emit StakeUpForSale(msg.sender, newPrice, _stakeId); + } + + /// @notice Buys a sell stake. + /// Transfers the sale price from the buyer (using safeTransferFrom), + /// pays the seller (applying the sellTax), + /// creates a new stake for the buyer (amount = original amount + bonus), + /// marks the original stake as complete, + /// and sets the new stake's unlock time. + /// @param seller The address of the seller. + /// @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"); + + // Transfer the sale price from the buyer to this contract. + IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), sellStakeEntry.price); + + // Calculate the seller's payment using the sell tax. + uint256 sellerPayment = (sellStakeEntry.price * (100 - sellTax)) / 100; + IERC20(pool.tokenAddress).safeTransfer(seller, sellerPayment); + + // Mark the original stake as complete. + Stake storage originalStake = stakes[seller][_stakeId]; + originalStake.complete = true; + + // Create the new stake for the buyer using the inline push pattern. + stakes[msg.sender].push(Stake({ + amount: sellStakeEntry.amount + sellStakeEntry.bonusAmount, + lastClaimed: sellStakeEntry.lastClaimed, + dailyRewardRate: sellStakeEntry.dailyRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); + + // Remove the sell stake listing. + delete sellStakes[seller][_stakeId]; + + // Remove the key from the iteration array. + 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 StakeSold(seller, msg.sender, sellStakeEntry.price, _stakeId); + } + + /// @notice Returns all active sell stakes with their keys and pending rewards. + /// @return sellers Array of seller addresses for each stake + /// @return stakeIds Array of stake IDs corresponding to each seller + /// @return sellStakeData Array of SellStake structs containing the sell stake data + /// @return pendingRewards Array of pending rewards for each stake + function getAllSellStakesWithKeys() external view returns ( + address[] memory sellers, + uint256[] memory stakeIds, + SellStake[] memory sellStakeData, + uint256[] memory pendingRewards + ) { + uint256 length = sellStakeKeys.length; + + sellers = new address[](length); + stakeIds = new uint256[](length); + sellStakeData = new SellStake[](length); + pendingRewards = new uint256[](length); + + for (uint256 i = 0; i < length; i++) { + SellStakeKey memory key = sellStakeKeys[i]; + sellers[i] = key.seller; + stakeIds[i] = key.stakeId; + + // Copy the SellStake struct from storage to memory + SellStake storage sourceStake = sellStakes[key.seller][key.stakeId]; + sellStakeData[i] = SellStake({ + price: sourceStake.price, + bonusAmount: sourceStake.bonusAmount, + amount: sourceStake.amount, + lastClaimed: sourceStake.lastClaimed, + dailyRewardRate: sourceStake.dailyRewardRate, + origUnlockTime: sourceStake.origUnlockTime + }); + + // Calculate pending rewards based on the provided logic + // Stop accumulating rewards past the unlockTime + uint256 endTime = block.timestamp < sourceStake.origUnlockTime ? block.timestamp : sourceStake.origUnlockTime; + uint256 elapsedTime = endTime > sourceStake.lastClaimed ? endTime - sourceStake.lastClaimed : 0; + pendingRewards[i] = (sourceStake.amount * sourceStake.dailyRewardRate * elapsedTime) / 1 days / 10000; + } + + return (sellers, stakeIds, sellStakeData, pendingRewards); + } + +} \ No newline at end of file diff --git a/contracts/bsc_paca.sol b/contracts/bsc_paca.sol new file mode 100644 index 0000000..8ea06fa --- /dev/null +++ b/contracts/bsc_paca.sol @@ -0,0 +1,1235 @@ +// SPDX-License-Identifier: MIT +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; + +pragma solidity ^0.8.20; + +interface iPriceOracle { + // returns price in USD + function getLatestPrice(address token) external view returns (uint256); +} + +// File: paca.sol + +contract PacaFinanceWithBoostAndScheduleUSDT is Initializable, ReentrancyGuardUpgradeable { + using SafeERC20 for IERC20; + + + // Struct Definitions + struct Pool { + uint256 lockupPeriod; + uint256 dailyRewardRate; + uint256 totalStaked; + uint256 totalRewards; + address tokenAddress; + } + + struct Stake { + uint256 amount; + uint256 lastClaimed; + uint256 dailyRewardRate; + uint256 unlockTime; + bool complete; + } + + struct StakeInput { + address user; + uint256 amount; + uint256 lastClaimed; + uint256 unlockTime; + uint256 dailyRewardRate; + } + + struct Vesting { + uint256 amount; + uint256 bonus; + uint256 lockedUntil; + uint256 claimedAmount; + uint256 claimedBonus; + uint256 lastClaimed; + uint256 createdAt; + address token; + bool complete; + uint256 usdAmount; + } + + struct UnlockStep { + uint256 timeOffset; + uint256 percentage; + } + + struct BoostRange { + uint256 minTokens; + uint256 maxTokens; + uint256 boostPercentage; + } + + struct WithdrawStake { + uint256 stakeId; + uint256 amount; + uint256 unlockTime; + } + + struct SellStake { + uint256 price; + uint256 bonusAmount; + uint256 amount; + uint256 lastClaimed; + uint256 dailyRewardRate; + uint256 origUnlockTime; + } + + struct SellStakeKey { + address seller; + uint256 stakeId; + } + + BoostRange[] public boosttiers; + + // Contract Variables + Pool public pool; + address public owner; + mapping(address => bool) public owners; + mapping(address => Stake[]) public stakes; + mapping(address => Vesting[]) public vestings; + mapping(address => UnlockStep[]) public unlockSchedules; + mapping(address => address) public priceOracles; + mapping(address => uint256) public dollarsVested; // per user address + uint256 public lockupDuration; + uint256 public minStakeLock; + uint256 private constant BONUS_PERCENTAGE = 10; + + mapping(address => bool) public authorizedBots; + mapping(address => uint256) public vestedTotal; // per vesting token + uint256 public unlockDelay; + uint256 public withdrawLiabilities; + mapping(address => WithdrawStake[]) public withdrawStake; + uint256 public restakeBonus; + mapping(address => uint256) public addressFixedRate; + mapping(address => mapping(uint256 => SellStake)) public sellStakes; + uint256 public sellTax; + uint256 public sellKickBack; + SellStakeKey[] public sellStakeKeys; + mapping(address => mapping(uint256 => uint256)) private sellStakeKeyIndex; + uint256 public sellMin; + + // Events + event Staked(address indexed user, uint256 amount); + event RewardClaimed(address indexed user, uint256 reward); + event VestingCreated(address indexed user, uint256 amount, uint256 bonus); + event VestingClaimed(address indexed user, uint256 amount, uint256 bonus); + event BonusClaimed(address indexed user, uint256 bonus); + event PoolUpdated(uint256 lockupPeriod, uint256 dailyRewardRate); + event UnlockScheduleSet(address indexed token); + event FundsWithdrawn(address indexed owner, address indexed token, uint256 amount); + event RewardsDeposited(uint256 amount); + event CompoundRewards(address indexed user, uint256 amount); + event MinStakeLockUpdated(uint256 amount); + event StakeWithdrawn(address indexed user, uint256 amount, uint256 stakeId); + event StakeUpForSale(address indexed user, uint256 saleAmount, uint256 stakeId); + event StakeSaleCancelled(address indexed user, uint256 stakeId); + event StakeSold(address indexed seller, address indexed buyer,uint256 saleAmount, uint256 stakeId); + + // Modifiers + modifier onlyOwner() { + require(owners[msg.sender], "Not authorized"); + _; + } + modifier onlyBot() { + require(authorizedBots[msg.sender], "Caller is not an authorized bot"); + _; + } + + function initialize() public initializer { + __ReentrancyGuard_init(); // Initialize ReentrancyGuardUpgradeable + owner = 0x41970Ce76b656030A79E7C1FA76FC4EB93980255; + owners[0x41970Ce76b656030A79E7C1FA76FC4EB93980255] = true; + + // lockupDuration = 250 days; + // minStakeLock = 16 ether; + + // pool.tokenAddress = 0x55d398326f99059fF775485246999027B3197955; + // pool.lockupPeriod = 250 * 1 days; + // pool.dailyRewardRate = 33; + + // // Price oracle for a specific tokens + // // priceOracles[0x940181a94A35A4569E4529A3CDfB74e38FD98631] = 0x0Dde1b42F7B3891C9731280A74081501729A73c5; + // authorizedBots[0xbf12D3b827a230F7390EbCc9b83b289FdC98ba81] = true; + // authorizedBots[0x7c40f272570fdf9549d6f67493aC250a1DB52F27] = true; + + // unlockDelay = 60 * 60 * 36; + // restakeBonus = 3; + } + + // Ownership Management + function addOwner(address _newOwner) external onlyOwner { + require(!owners[_newOwner], "Already an owner"); + owners[_newOwner] = true; + } + + function removeOwner(address _owner) external onlyOwner { + require(owners[_owner], "Not an owner"); + require(_owner != msg.sender, "Cannot remove yourself"); + owners[_owner] = false; + } + + /// @notice Function to add a bot to the list (only callable by the contract owner) + function addBot(address bot) external onlyOwner { + require(bot != address(0), "Invalid address"); + authorizedBots[bot] = true; + } + + // /// @notice Function to remove a bot from the list (only callable by the contract owner) + // function removeBot(address bot) external onlyOwner { + // require(bot != address(0), "Invalid address"); + // authorizedBots[bot] = false; + // } + + // Admin Functions + function updatePool(uint256 _lockupPeriod, uint256 _dailyRewardRate) external onlyOwner { + pool.lockupPeriod = _lockupPeriod * 1 days; + pool.dailyRewardRate = _dailyRewardRate; + emit PoolUpdated(_lockupPeriod, _dailyRewardRate); + } + + function depositRewards(uint256 _amount) external onlyOwner { + IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); + pool.totalRewards = pool.totalRewards + _amount; + emit RewardsDeposited(_amount); + } + + function updateStakeMin(uint256 _amount) external onlyOwner { + minStakeLock = _amount; + emit MinStakeLockUpdated(_amount); + } + + function updateUnlockDelay(uint256 _delay) external onlyOwner { + unlockDelay = _delay; + } + + function updateRestakeBonus(uint256 _newBonus) external onlyOwner { + restakeBonus = _newBonus; + } + + /// @notice New Stake Sell Tax + /// @param _newTax The rate expressed in 2 digits, ex: 20 + function updateSellTax(uint256 _newTax) external onlyOwner { + sellTax = _newTax; + } + + /// @notice New Stake Sell Minimum + /// @param _newMin The rate expressed in 2 digits, ex: 30 + function updateSellMin(uint256 _newMin) external onlyOwner { + sellMin = _newMin; + } + + /// @notice New Stake Sell kickback to the buyer + /// @param _newKickback The rate expressed in 2 digits, ex: 5 + function updatesellKickBack(uint256 _newKickback) external onlyOwner { + sellKickBack = _newKickback; + } + + /// @notice Function to add an address to have a fixed daily reward (only callable by the contract owner) + /// @param _addr The address to give a fixed rate + /// @param _rate The fixed rate expressed in 2 digits, ex: 40 + function addFixedRate(address _addr, uint _rate) external onlyOwner { + require(_addr != address(0), "Invalid address"); + addressFixedRate[_addr] = _rate; + } + + /// @notice Function to remove an address' fixed daily reward (only callable by the contract owner) + /// @param _addr The address to 0 out + function removeFixedRate(address _addr) external onlyOwner { + require(_addr != address(0), "Invalid address"); + addressFixedRate[_addr] = 0; + } + + // /// @notice Add or edit a tier range *commented out for size constraints* + // function addOrEditTier(uint256 minTokens, uint256 maxTokens, uint256 boostPercentage) public onlyOwner { + // require(minTokens < maxTokens, "Invalid range: minTokens must be < maxTokens"); + // require(!rangesOverlap(minTokens, maxTokens), "Range overlaps with existing tiers"); + + // // Check if editing an existing range + // for (uint256 i = 0; i < boosttiers.length; ++i) { + // if (boosttiers[i].minTokens == minTokens && boosttiers[i].maxTokens == maxTokens) { + // // Edit the existing range + // boosttiers[i].boostPercentage = boostPercentage; + // return; + // } + // } + + // // Add new range + // boosttiers.push(BoostRange(minTokens, maxTokens, boostPercentage)); + + // // Sort the ranges after adding + // sortRanges(); + // } + + // // Check for range overlap + // function rangesOverlap(uint256 minTokens, uint256 maxTokens) internal view returns (bool) { + // for (uint256 i = 0; i < boosttiers.length; ++i) { + // if (minTokens <= boosttiers[i].maxTokens && maxTokens >= boosttiers[i].minTokens) { + // return true; + // } + // } + // return false; + // } + + // /// @notice Sort ranges by minTokens + // function sortRanges() internal { + // for (uint256 i = 0; i < boosttiers.length; ++i) { + // for (uint256 j = i + 1; j < boosttiers.length; j++) { + // if (boosttiers[i].minTokens > boosttiers[j].minTokens) { + // // Swap ranges + // BoostRange memory temp = boosttiers[i]; + // boosttiers[i] = boosttiers[j]; + // boosttiers[j] = temp; + // } + // } + // } + // } + + // /// @notice Remove a range by index + // function removeTier(uint256 index) external onlyOwner { + // require(index < boosttiers.length, "Index out of bounds"); + // for (uint256 i = index; i < boosttiers.length - 1; ++i) { + // boosttiers[i] = boosttiers[i + 1]; + // } + // boosttiers.pop(); + // } + + function withdrawFromStakingPool(uint256 _amount) external onlyOwner { + IERC20(pool.tokenAddress).safeTransfer(msg.sender, _amount); + emit FundsWithdrawn(msg.sender, pool.tokenAddress, _amount); + } + + function withdrawFromVestingPool(address _token, uint256 _amount) external onlyOwner { + IERC20(_token).safeTransfer(msg.sender, _amount); + 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"); + + // 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 + + // 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 + // })); + // } + + // emit UnlockScheduleSet(_token); + // } + + /// @notice Get the boost percentage for a given token amount + function getBoost(uint256 depositedTokens) public view returns (uint256) { + for (uint256 i = 0; i < boosttiers.length; ++i) { + if (depositedTokens >= boosttiers[i].minTokens && depositedTokens <= boosttiers[i].maxTokens) { + return boosttiers[i].boostPercentage; + } + } + return 0; // Default boost if no range matches + } + + /// @notice This function will end and clear a user's stakes. + /// @dev Only to be used by bots in emergencies + /// @param user The user whose stakes will be ended and 0'd + function clearStakes(address user) external onlyBot { + uint256 clearedStakes = 0; + + for (uint256 i = 0; i < stakes[user].length; ++i) { + Stake storage stake = stakes[user][i]; + clearedStakes = clearedStakes + stake.amount; + stake.amount = 0; + stake.complete = true; + } + + pool.totalStaked = pool.totalStaked - clearedStakes; + } + + /// @notice This function will end and clear a user's withdraw stakes. + /// @dev Only to be used by bots in emergencies + /// @param user The user whose withdraw stakes will be 0'd + function clearWithdrawStakes(address user) external onlyBot { + uint256 clearedStakes = 0; + + for (uint256 i = 0; i < withdrawStake[user].length; ++i) { + WithdrawStake storage stake = withdrawStake[user][i]; + clearedStakes = clearedStakes + stake.amount; + stake.amount = 0; + } + + 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 + + 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; } + } + } + + 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"); + + stakes[_user].push(Stake({ + amount: _amount, + lastClaimed: block.timestamp, + dailyRewardRate: pool.dailyRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); + + pool.totalStaked = pool.totalStaked + _amount; + emit Staked(_user, _amount); + } + + + function createStakes(StakeInput[] calldata stakesInput) external onlyBot payable { + uint256 totalLength = stakesInput.length; + + for (uint256 i; i < totalLength;) { + StakeInput calldata stakeInput = stakesInput[i]; + + // Update pool total + pool.totalStaked = pool.totalStaked + stakeInput.amount; + + // Create the stake for the user + stakes[stakeInput.user].push(Stake({ + amount: stakeInput.amount, + lastClaimed: stakeInput.lastClaimed, + dailyRewardRate: stakeInput.dailyRewardRate, + unlockTime: stakeInput.unlockTime, + complete: false + })); + + unchecked { + ++i; + } + } + } + + /// @notice Calculates pending rewards for a specific user's stake + /// @dev Rewards only accumulate until the stake's unlockTime is reached + /// @param _user Address of the stake owner + /// @param _stakeIndex Index of the stake in the user's stakes array + /// @return Accumulated rewards since last claim, stopping at unlockTime if applicable + function getPoolRewards(address _user, uint _stakeIndex) public view returns (uint256) { + Stake storage stake = stakes[_user][_stakeIndex]; + + uint256 endTime = block.timestamp < stake.unlockTime ? block.timestamp : stake.unlockTime; + uint256 elapsedTime = endTime > stake.lastClaimed ? endTime - stake.lastClaimed : 0; + uint256 rewards = (stake.amount * stake.dailyRewardRate * elapsedTime) / 1 days / 10000; + + return rewards; + } + + function getUserRewardRate(address _user, bool isRestake) public view returns (uint256) { + uint256 finalRewardRate = pool.dailyRewardRate + getBoost(dollarsVested[_user]); + if (isRestake) { + finalRewardRate += restakeBonus; + } + return finalRewardRate; + } + + // 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; + // } + // } + + // 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); + + // 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"); + + // // Load the stake + // Stake storage stake = stakes[msg.sender][_stakeIndex]; + // uint256 _amount = stake.amount; + + // uint rewards = getPoolRewards(msg.sender, _stakeIndex); + + // _amount = _amount + rewards; + + // // 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"); + + // // Update state before external calls + // stake.amount = 0; + // stake.complete = true; + // withdrawLiabilities += _amount; + + // 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 + // })); + + // // 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"); + + // 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"); + + // uint256 _amount = stake.amount; + // uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); + // require(poolBalance >= _amount, "Insufficient rewards in the pool"); + + // // 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; + // } + // } + + // // Revert if no matching stake with non-zero amount was found + // revert("Invalid stake index or already withdrawn"); + // } + + + // 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; + // } + // } + + // 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); + // } + + // 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); + // } + + // 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 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; + + + // 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); + // } + + function getUnlockedVesting(address _user, uint256 _vestingIndex) public view returns (uint256) { + Vesting storage vesting = vestings[_user][_vestingIndex]; + uint256 timeElapsed = block.timestamp - vesting.createdAt; + address token = vesting.token; + + uint256 unlockedAmount = 0; + + for (uint256 i = 0; i < unlockSchedules[token].length; ++i) { + UnlockStep storage step = unlockSchedules[token][i]; + uint256 timeTier = step.timeOffset; + uint256 percentage = step.percentage; + + if (timeElapsed >= timeTier) { + unlockedAmount = unlockedAmount + ((vesting.amount * percentage) / 10000); + } + } + + return unlockedAmount; + } + + function getVestingSchedule(address _user, uint256 _vestingIndex) public view returns (uint256[] memory, uint256[] memory) { + Vesting storage vesting = vestings[_user][_vestingIndex]; + address token = vesting.token; + + uint256 scheduleLength = unlockSchedules[token].length; + uint256[] memory unlockTimestamps = new uint256[](scheduleLength); + uint256[] memory unlockPercentages = new uint256[](scheduleLength); + + for (uint256 i = 0; i < scheduleLength; ++i) { + UnlockStep storage step = unlockSchedules[token][i]; + + // Calculate the absolute unlock timestamp + unlockTimestamps[i] = vesting.createdAt + step.timeOffset; + unlockPercentages[i] = step.percentage; // Percentage is stored as scaled by 10000 (e.g., 2500 = 25%) + } + + return (unlockTimestamps, unlockPercentages); + } + + function getUnlockedVestingBonus(address _user, uint256 _vestingIndex) public view returns (uint256) { + Vesting storage vesting = vestings[_user][_vestingIndex]; + uint256 timeElapsed = block.timestamp - vesting.createdAt; + address token = vesting.token; + + uint256 unlockedAmount = 0; + + for (uint256 i = 0; i < unlockSchedules[token].length; ++i) { + UnlockStep storage step = unlockSchedules[token][i]; + uint256 timeTier = step.timeOffset; + uint256 percentage = step.percentage; + uint256 maxBonusAmount = (vesting.usdAmount * BONUS_PERCENTAGE) / 100; + + if (timeElapsed >= timeTier) { + unlockedAmount = unlockedAmount + ((maxBonusAmount * percentage) / 10000); + } + } + + return unlockedAmount; + } + + + // 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"); + + // 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); + // } + + // 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]; + + // 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; + + // // Mark vesting as complete if fully claimed + // if (vesting.claimedAmount >= vesting.amount) { + // vesting.complete = true; + // } + + // vestingsProcessed++; + // } + // } + // } + + // 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; + // } + // } + + // // 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); + // } + + + // 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"); + + // vesting.claimedBonus = vesting.claimedBonus + bonusToClaim; + // withdrawLiabilities += 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 + // })); + + // emit BonusClaimed(msg.sender, bonusToClaim); + // } + + function setPriceOracle(address _token, address _oracle) external onlyOwner { + priceOracles[_token] = _oracle; + } + + function viewRewards(address _user) external view returns (uint256) { + uint256 totalReward = 0; + + for (uint256 i = 0; i < stakes[_user].length; ++i) { + uint rewards = getPoolRewards(_user, i); + totalReward = totalReward + rewards; + } + + return totalReward; + } + + /// @notice View function to get all stakes for a specific address + function getStakes(address user) external view returns (Stake[] memory) { + return stakes[user]; + } + + /// @notice View function to get all vestings for a specific address + function getVestings(address user) external view returns (Vesting[] memory) { + return vestings[user]; + } + + /// @notice View to monitor contract pool deficits + function getPoolStatus() external view returns (uint256) { + uint256 poolBalance = IERC20(pool.tokenAddress).balanceOf(address(this)); + + // If the balance is greater than or equal to liabilities, return 0 + if (poolBalance >= withdrawLiabilities) { + return 0; + } + + // Otherwise, return the deficit (amount needed to cover liabilities) + return withdrawLiabilities - poolBalance; + } + + /** + * @notice Returns the vested amounts and USD values for an array of tokens. + * @param _tokens The array of token addresses to evaluate. + * @return amounts The array of vested amounts for each token. + * @return usdValues The array of USD values for each token’s vested amount. + * @return totalUsd The total USD value of all vested tokens in the array. + */ + function getVestedTotals(address[] calldata _tokens) + external + view + returns ( + uint256[] memory amounts, + uint256[] memory usdValues, + uint256 totalUsd + ) + { + uint256 length = _tokens.length; + amounts = new uint256[](length); + usdValues = new uint256[](length); + + for (uint256 i = 0; i < length; i++) { + address token = _tokens[i]; + + // 1. Get the total amount vested for this token. + uint256 tokenAmount = vestedTotal[token]; + amounts[i] = tokenAmount; + + // 2. Query the oracle for this token’s USD price. + // Assumes the oracle returns a price scaled by 1e18. + uint256 price = iPriceOracle(priceOracles[token]).getLatestPrice(token); + + // 3. Calculate the vested USD value: (price * amount) / 1e18 + uint256 valueInUsd = (price * tokenAmount) / 1e18; + usdValues[i] = valueInUsd; + + // 4. Accumulate the total USD amount + totalUsd += valueInUsd; + } + + return (amounts, usdValues, totalUsd); + } + + /// @notice Returns the total USD value of the user's unclaimed, uncomplete, stake amounts, based on current token prices from the oracle. + /// @return totalUsd The total unclaimed stake value, in USD (1e18 precision). + function getUserTotalUnclaimedUsdValue(address user) external view returns (uint256 totalUsd) { + uint256 length = vestings[user].length; + for (uint256 i = 0; i < length; i++) { + Vesting memory v = vestings[user][i]; + if (!v.complete) { + uint256 tokenPrice = iPriceOracle(priceOracles[v.token]).getLatestPrice(v.token); + + // The unclaimed portion of the stake + uint256 unclaimedAmount = v.amount - v.claimedAmount; + + // Convert unclaimed tokens to USD value + uint256 stakeUsd = (tokenPrice * unclaimedAmount) / 1e18; + + totalUsd += stakeUsd; + } + } + return totalUsd; + } + + + /// @notice Function that lets you look up an address’s stake by stakeId. + /// @param user The address to evaluate. + /// @param _stakeId The stakeId of the ORIGINAL stake that is waiting to be unlocked + function getWithdrawStake(address user, uint256 _stakeId) external view returns (WithdrawStake memory) { + WithdrawStake[] storage userStakes = withdrawStake[user]; + for (uint256 i = 0; i < userStakes.length; i++) { + if (userStakes[i].stakeId == _stakeId) { + return userStakes[i]; + } + } + revert("WithdrawStake with the specified stakeId not found for this user."); + } + + /// @notice Function that lets you look up an address’s stake by vestingId. + /// @param user The address to evaluate. + /// @param _vestingId The vestingId of the ORIGINAL vest that is waiting to be unlocked + function getVestingWithdrawStake(address user, uint256 _vestingId) external view returns (WithdrawStake memory) { + WithdrawStake[] storage userStakes = withdrawStake[user]; + uint256 boostedVestingId = _vestingId + 1e6; + for (uint256 i = 0; i < userStakes.length; i++) { + if (userStakes[i].stakeId == boostedVestingId) { + return userStakes[i]; + } + } + revert("WithdrawStake with the specified stakeId not found for this user."); + } + + /// @notice Function that returns an array of all the user's withdrawStakes. + /// @param user The address to evaluate. + /// @return An array of WithdrawStake for the given user. + function getAllWithdrawStakes(address user) external view returns (WithdrawStake[] memory) { + return withdrawStake[user]; + } + + /// @notice Function to put a stake for sale. + /// Sets the original stake amount to 0 to prevent any alterations while for sale. + /// @param _stakeId The stake to sell. + /// @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"); + // 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"); + + // Create a SellStake entry directly in the mapping. + sellStakes[msg.sender][_stakeId] = SellStake({ + price: price, + bonusAmount: (price * sellKickBack) / 100, + amount: stake.amount, + lastClaimed: stake.lastClaimed, + dailyRewardRate: stake.dailyRewardRate, + origUnlockTime: stake.unlockTime + }); + + // Lock the original stake by setting its amount to 0. + stake.amount = 0; + + // Add the key to the iteration array. + sellStakeKeys.push(SellStakeKey({ seller: msg.sender, stakeId: _stakeId })); + sellStakeKeyIndex[msg.sender][_stakeId] = sellStakeKeys.length - 1; + + emit StakeUpForSale(msg.sender, price, _stakeId); + } + + /// @notice Function to cancel a sell stake. + /// Restores the stake amount to the original stake and removes the sell stake. + /// @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"); + + // Access the original stake. + Stake storage stake = stakes[msg.sender][_stakeId]; + require(stake.amount == 0, "Stake not in sell state"); + + // Restore the original stake's amount. + stake.amount = sellStakeEntry.amount; + + delete sellStakes[msg.sender][_stakeId]; + + // Remove the key from the iteration array using swap-and-pop. + uint256 index = sellStakeKeyIndex[msg.sender][_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[msg.sender][_stakeId]; + + emit StakeSaleCancelled(msg.sender, _stakeId); + } + + /// @notice Function to update the price of a stake that is for sale. + /// @param _stakeId The stake ID to update. + /// @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"); + + sellStakeEntry.bonusAmount = (newPrice * sellKickBack) / 100; + sellStakeEntry.price = newPrice; + + emit StakeUpForSale(msg.sender, newPrice, _stakeId); + } + + /// @notice Buys a sell stake. + /// Transfers the sale price from the buyer (using safeTransferFrom), + /// pays the seller (applying the sellTax), + /// creates a new stake for the buyer (amount = original amount + bonus), + /// marks the original stake as complete, + /// and sets the new stake's unlock time. + /// @param seller The address of the seller. + /// @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"); + + // Transfer the sale price from the buyer to this contract. + IERC20(pool.tokenAddress).safeTransferFrom(msg.sender, address(this), sellStakeEntry.price); + + // Calculate the seller's payment using the sell tax. + uint256 sellerPayment = (sellStakeEntry.price * (100 - sellTax)) / 100; + IERC20(pool.tokenAddress).safeTransfer(seller, sellerPayment); + + // Mark the original stake as complete. + Stake storage originalStake = stakes[seller][_stakeId]; + originalStake.complete = true; + + // Create the new stake for the buyer using the inline push pattern. + stakes[msg.sender].push(Stake({ + amount: sellStakeEntry.amount + sellStakeEntry.bonusAmount, + lastClaimed: sellStakeEntry.lastClaimed, + dailyRewardRate: sellStakeEntry.dailyRewardRate, + unlockTime: block.timestamp + pool.lockupPeriod, + complete: false + })); + + // Remove the sell stake listing. + delete sellStakes[seller][_stakeId]; + + // Remove the key from the iteration array. + 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 StakeSold(seller, msg.sender, sellStakeEntry.price, _stakeId); + } + + /// @notice Returns all active sell stakes with their keys and pending rewards. + /// @return sellers Array of seller addresses for each stake + /// @return stakeIds Array of stake IDs corresponding to each seller + /// @return sellStakeData Array of SellStake structs containing the sell stake data + /// @return pendingRewards Array of pending rewards for each stake + function getAllSellStakesWithKeys() external view returns ( + address[] memory sellers, + uint256[] memory stakeIds, + SellStake[] memory sellStakeData, + uint256[] memory pendingRewards + ) { + uint256 length = sellStakeKeys.length; + + sellers = new address[](length); + stakeIds = new uint256[](length); + sellStakeData = new SellStake[](length); + pendingRewards = new uint256[](length); + + for (uint256 i = 0; i < length; i++) { + SellStakeKey memory key = sellStakeKeys[i]; + sellers[i] = key.seller; + stakeIds[i] = key.stakeId; + + // Copy the SellStake struct from storage to memory + SellStake storage sourceStake = sellStakes[key.seller][key.stakeId]; + sellStakeData[i] = SellStake({ + price: sourceStake.price, + bonusAmount: sourceStake.bonusAmount, + amount: sourceStake.amount, + lastClaimed: sourceStake.lastClaimed, + dailyRewardRate: sourceStake.dailyRewardRate, + origUnlockTime: sourceStake.origUnlockTime + }); + + // Calculate pending rewards based on the provided logic + // Stop accumulating rewards past the unlockTime + uint256 endTime = block.timestamp < sourceStake.origUnlockTime ? block.timestamp : sourceStake.origUnlockTime; + uint256 elapsedTime = endTime > sourceStake.lastClaimed ? endTime - sourceStake.lastClaimed : 0; + pendingRewards[i] = (sourceStake.amount * sourceStake.dailyRewardRate * elapsedTime) / 1 days / 10000; + } + + return (sellers, stakeIds, sellStakeData, pendingRewards); + } + +} \ No newline at end of file diff --git a/contracts/s_pricefeed.sol b/contracts/s_pricefeed.sol new file mode 100644 index 0000000..ae3ba62 --- /dev/null +++ b/contracts/s_pricefeed.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.8.20; + +interface AggregatorV3Interface { + function latestRoundData() external view returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract SPriceOracle { + AggregatorV3Interface public priceFeed; + + constructor() { + priceFeed = AggregatorV3Interface(0x726D2E87d73567ecA1b75C063Bd09c1493655918); + } + /// @notice Returns the USD price of S in 1e18 based on API3 + function getLatestPrice(address token) external view returns (uint256) { + (, int256 price,,,) = priceFeed.latestRoundData(); + require(price > 0, "Invalid price from Chainlink"); + return uint256(price); + } +} \ No newline at end of file diff --git a/contracts/swapx_pricefeed.sol b/contracts/swapx_pricefeed.sol new file mode 100644 index 0000000..fd485fa --- /dev/null +++ b/contracts/swapx_pricefeed.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@uniswap/v3-core/contracts/libraries/FullMath.sol"; + +/// @notice Minimal interface for a Uniswap V3 pool (to access slot0) +interface IUniswapV3Pool { + function safelyGetStateOfAMM() + external + view + returns ( + uint160 sqrtPrice, // The current sqrt(price) as a Q64.96 value + int24 tick, + uint16 lastFee, + uint8 pluginConfig, + uint128 activeLiquidity, + uint24 nextTick, + uint24 previousTick + ); +} + +/// @title USDC Quote for SWAPx +/// @notice Returns the USDC price (scaled to 18 decimals) of 1 token, +/// assuming a pool where token0 is USDC and token1 is the token of interest. +/// The pool is hardcoded as well as the token addresses. +contract UsdcQuoteV3 { + // Hardcoded addresses + address public constant POOL_ADDRESS = 0x467865E7Ce29E7ED8f362D51Fd7141117B234b44; + // token0 is USDC + address public constant USDC = 0x29219dd400f2Bf60E5a23d13Be72B486D4038894; + // token1 is the token to be priced (assumed to have 18 decimals) + address public constant TOKEN = 0xA04BC7140c26fc9BB1F36B1A604C7A5a88fb0E70; + + // Constant representing 2^192 used for fixed‑point math + uint256 public constant Q192 = 2**192; + + function getSqrtPrice() public view returns (uint256 price) { + IUniswapV3Pool pool = IUniswapV3Pool(POOL_ADDRESS); + (uint160 sqrtPrice,,,,,,) = pool.safelyGetStateOfAMM(); + + price = sqrtPrice; + } + /** + * @notice Returns the USDC price (scaled to 18 decimals) for exactly 1 TOKEN. + * + * @return amount18 The USDC amount (scaled to 18 decimals) per 1 TOKEN. + */ + function getLatestPrice(address token) external view returns (uint256 amount18) { + uint256 sqrtPrice = getSqrtPrice(); + + uint256 numerator1 = uint256(sqrtPrice) * uint256(sqrtPrice); + uint256 directPrice = FullMath.mulDiv(numerator1, 10**6, 1 << 192); + require(directPrice > 0, "Direct price is zero"); + amount18 = 1e36 / directPrice; + + } + +} diff --git a/hardhat.config.js b/hardhat.config.js new file mode 100644 index 0000000..bcb7332 --- /dev/null +++ b/hardhat.config.js @@ -0,0 +1,188 @@ +require("@openzeppelin/hardhat-upgrades"); +require("@nomicfoundation/hardhat-ignition-ethers"); +// require("@nomiclabs/hardhat-ethers"); +// require("@nomiclabs/hardhat-etherscan"); +// require("hardhat-contract-sizer"); +// require("dotenv").config(); +// require("hardhat-gas-reporter"); +require("@nomicfoundation/hardhat-verify"); + +const env = process.env; +// This is a sample Hardhat task. To learn how to create your own go to +// https://hardhat.org/guides/create-task.html + +module.exports = { + solidity: { + compilers: [ + { + version: "0.8.9", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + { + version: "0.8.20", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + { + version: "0.8.0", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + { + version: "0.6.12", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + { + version: "0.5.16", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + { + version: "0.4.25", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + ], + }, + mocha: { + timeout: 10000000, + }, + networks: { + hardhat: { + forking: { + // MAINNET FORK + url: `https://bsc-mainnet.nodereal.io/v1/f82aa3b8072a46ccadf3024a96f0cff4`, + // blockNumber: 30488174, + chainId: 56, + + // TESTNET FORK + // url: `https://bsc-testnet.nodereal.io/v1/f82aa3b8072a46ccadf3024a96f0cff4`, + // blockNumber: 31828689, + // chainId: 97, + }, + }, + local: { + url: "http://127.0.0.1:8545", + forking: { + url: `https://bsc-mainnet.nodereal.io/v1/f82aa3b8072a46ccadf3024a96f0cff4`, + chainId: 56, + blockNumber: 30010000, + }, + }, + sascha: { + // url: `https://f743-2600-4040-4448-b000-378c-f5c-adea-e76f.ngrok-free.app`, + url: `https://www.driplover69.info`, + // chainId: 1337, + chainId: 1337, + // minGasPrice: 3e9, + }, + bb: { + url: `https://rpc-beta.buildbear.io/submit/possible-jubilee-3eaaadf4`, + chainId: 22201, + timeout: 200000, + // accounts: [env.pk], + // gasPrice: "auto", + // gas: 2e9, + }, + bbsc: { + url: `https://rpc.buildbear.io/sascha`, + timeout: 200000, + // accounts: [env.pk], + // gasPrice: "auto", + // gas: 2e9, + }, + tenderly: { + url: `https://virtual.binance.rpc.tenderly.co/bd4ca4c1-0512-47bf-9674-6f509e9ad7fc`, + chainId: 56, + timeout: 200000, + // accounts: [env.pk], + // gasPrice: "auto", + // gas: 2e9, + }, + mainnet: { + url: `https://bsc-dataseed1.binance.org`, + chainId: 56, + }, + base: { + url: `https://base-mainnet.public.blastapi.io`, + chainId: 8453, + }, + sonic: { + url: `https://rpc.soniclabs.com`, + chainId: 146, + }, + }, + etherscan: { + enable: true, + apiKey: { + bbsc: "verifyContract", + base: "GN555QYEWPDFZ47H1TR5ASK693D38A69GY", + mainnet: "1I15826QJ4HHY2UTGK3EZEA4TNBT68FB83", + sonic: "N6DMIQQNJ7634I1ETH527Z1WZQM2Q6GEW8" + }, + customChains: [ + { + network: "bbsc", + chainId: 1337, + urls: { + apiURL: "https://rpc.buildbear.io/verify/sourcify/server/sascha", + browserURL: "https://explorer.buildbear.io/zesty-drax-7d87eef9", + }, + }, + { + network: "base", + chainId: 8453, // Mainnet chain ID for Base + urls: { + apiURL: "https://api.basescan.org/api", // BaseScan API URL + browserURL: "https://basescan.org", // BaseScan browser URL + }, + }, + { + network: "mainnet", + chainId: 56, // Mainnet chain ID for Base + urls: { + apiURL: "https://api.bscscan.com/api", + browserURL: "https://bscscan.com", + }, + }, + { + network: "sonic", + chainId: 146, // Mainnet chain ID for sonic + urls: { + apiURL: "https://api.sonicscan.org/api", + browserURL: "https://sonicscan.org", + }, + }, + ], + }, + sourcify: { + enabled: false, + apiUrl: "https://rpc.buildbear.io/verify/sourcify/server/sascha" + } +}; diff --git a/ignition/deployments/chain-22152/journal.jsonl b/ignition/deployments/chain-22152/journal.jsonl new file mode 100644 index 0000000..afbcc38 --- /dev/null +++ b/ignition/deployments/chain-22152/journal.jsonl @@ -0,0 +1,7 @@ + +{"chainId":22152,"type":"DEPLOYMENT_INITIALIZE"} +{"artifactId":"Paca#PacaFinanceWithBoostAndSchedule","constructorArgs":["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],"contractName":"PacaFinanceWithBoostAndSchedule","dependencies":[],"from":"0xde4457b268971ca7846a4fb604599a591a49f4c8","futureId":"Paca#PacaFinanceWithBoostAndSchedule","futureType":"NAMED_ARTIFACT_CONTRACT_DEPLOYMENT","libraries":{},"strategy":"basic","strategyConfig":{},"type":"DEPLOYMENT_EXECUTION_STATE_INITIALIZE","value":{"_kind":"bigint","value":"0"}} +{"futureId":"Paca#PacaFinanceWithBoostAndSchedule","networkInteraction":{"data":"0x60806040526301499700600e5567d02ab486cedc0000600f553480156200002557600080fd5b50604051620030b3380380620030b3833981016040819052620000489162000228565b60016000818155600780546001600160a01b031990811633908117909255908252600860209081526040808420805460ff191686179055600680546001600160a01b03979097169684169690961790955563014997006002908155602160039081558651606080820189528682526063828601908152828a018881528954808b018b558a8a5293519385027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf68181019590955591517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf78084019190915590517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf8928301558a518084018c526064815260c7818901908152818d018c81528c54808e018e558d8d5292519288028088019390935590518284015551908301558a519283018b5260c883526000198388019081529a83019586528954808b018b559989529151989093029182019790975596519587019590955551949093019390935573940181a94a35a4569e4529a3cdfb74e38fd986319052600c90527fb0d7879752dd69b18392316706d5581b7f77657c28b74d89fe870a1d343b50d98054909116730dde1b42f7b3891c9731280a74081501729a73c51790556200025a565b6000602082840312156200023b57600080fd5b81516001600160a01b03811681146200025357600080fd5b9392505050565b612e49806200026a6000396000f3fe608060405234801561001057600080fd5b50600436106102275760003560e01c80636ae21510116101305780639562ee15116100b8578063cfb866da1161007c578063cfb866da14610596578063d9193025146105c4578063d9613410146105d7578063da40ef80146105ea578063e4d06f90146105fd57600080fd5b80639562ee15146104e857806396ed7f89146104fb578063ac97b4171461050e578063b92a349f14610521578063bd84477d1461053457600080fd5b80637497211b116100ff5780637497211b1461048957806380ca0ecf1461049c578063853e0df2146104af5780638bdf67f2146104c25780638da5cb5b146104d557600080fd5b80636ae21510146104485780636c3b0c53146104505780637065cb4814610463578063710ea0ce1461047657600080fd5b8063372500ab116101b357806344c7d6ef1161018257806344c7d6ef1461039d57806351f6cf2f146103b0578063584b62a1146103d8578063592d1dd11461041557806367a74ddc1461043557600080fd5b8063372500ab1461036657806338a85ce21461036e5780633f35e7221461037757806343c7c0111461038a57600080fd5b806316f0115b116101fa57806316f0115b146102d9578063173825d91461032d5780631ada70a8146103425780631bf6ddae1461034b57806335ed3fd61461035e57600080fd5b8063013745181461022c578063022914a7146102725780630a84096a146102a557806313c8e2de146102c6575b600080fd5b61025561023a366004612ac6565b600c602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b610295610280366004612ac6565b60086020526000908152604090205460ff1681565b6040519015158152602001610269565b6102b86102b3366004612ae1565b610610565b604051908152602001610269565b6102b86102d4366004612b0b565b61072d565b6002546003546004546005546006546102fc94939291906001600160a01b031685565b6040805195865260208601949094529284019190915260608301526001600160a01b0316608082015260a001610269565b61034061033b366004612ac6565b6107e0565b005b6102b8600e5481565b610340610359366004612b0b565b6108e1565b6102b8600a81565b610340610a51565b6102b8600f5481565b610340610385366004612ae1565b610b92565b610340610398366004612ac6565b610c19565b6103406103ab366004612b0b565b610e69565b6103c36103be366004612ae1565b6110bc565b60408051928352602083019190915201610269565b6103eb6103e6366004612ae1565b6110f8565b6040805195865260208601949094529284019190915260608301521515608082015260a001610269565b6102b8610423366004612ac6565b600d6020526000908152604090205481565b610340610443366004612b24565b611149565b6103406111a6565b61034061045e366004612ae1565b611381565b610340610471366004612ac6565b6116a1565b6102b8610484366004612ae1565b611750565b610340610497366004612b0b565b6117e7565b6102b86104aa366004612ae1565b611925565b6103406104bd366004612b0b565b611a33565b6103406104d0366004612b0b565b611ac0565b600754610255906001600160a01b031681565b6103406104f6366004612b0b565b611b58565b610340610509366004612b57565b611bbc565b61034061051c366004612b0b565b611c3d565b61034061052f366004612b79565b611de7565b610547610542366004612ae1565b612030565b60408051998a5260208a0198909852968801959095526060870193909352608086019190915260a085015260c08401526001600160a01b031660e0830152151561010082015261012001610269565b6105a96105a4366004612b0b565b6120a7565b60408051938452602084019290925290820152606001610269565b6103406105d2366004612b0b565b6120da565b6103406105e5366004612bac565b612209565b6102b86105f8366004612ac6565b612428565b61034061060b366004612ae1565b612482565b6001600160a01b0382166000908152600a6020526040812080548291908490811061063d5761063d612bd8565b90600052602060002090600802019050600081600601544261065f9190612c04565b60078301549091506001600160a01b03166000805b6001600160a01b0383166000908152600b6020526040902054811015610720576001600160a01b0383166000908152600b602052604081208054839081106106be576106be612bd8565b60009182526020909120600290910201805460018201549192509081871061070a576127108189600101546106f39190612c17565b6106fd9190612c44565b6107079086612c58565b94505b505050808061071890612c6b565b915050610674565b5093505050505b92915050565b6000805b6001548110156107d7576001818154811061074e5761074e612bd8565b906000526020600020906003020160000154831015801561079357506001818154811061077d5761077d612bd8565b9060005260206000209060030201600101548311155b156107c557600181815481106107ab576107ab612bd8565b906000526020600020906003020160020154915050919050565b806107cf81612c6b565b915050610731565b50600092915050565b3360009081526008602052604090205460ff166108185760405162461bcd60e51b815260040161080f90612c84565b60405180910390fd5b6001600160a01b03811660009081526008602052604090205460ff1661086f5760405162461bcd60e51b815260206004820152600c60248201526b2737ba1030b71037bbb732b960a11b604482015260640161080f565b336001600160a01b038216036108c05760405162461bcd60e51b815260206004820152601660248201527521b0b73737ba103932b6b7bb32903cb7bab939b2b63360511b604482015260640161080f565b6001600160a01b03166000908152600860205260409020805460ff19169055565b600f546108f38264e8d4a51000612c17565b116109105760405162461bcd60e51b815260040161080f90612cac565b600654610928906001600160a01b03163330846125f9565b600354336000908152600d60205260409020546109449061072d565b61094e9082612c58565b905060096000336001600160a01b03166001600160a01b031681526020019081526020016000206040518060a00160405280848152602001428152602001838152602001600260000154426109a39190612c58565b8152600060209182018190528354600180820186559482528282208451600590920201908155918301519382019390935560408201516002820155606082015160038201556080909101516004918201805460ff19169115159190911790558054849290610a12908490612c58565b909155505060405182815233907f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d906020015b60405180910390a25050565b610a59612666565b6000805b33600090815260096020526040902054811015610adc57336000908152600960205260408120805483908110610a9557610a95612bd8565b906000526020600020906005020190506000610ab13384611750565b9050610abd8185612c58565b4260019093019290925550915080610ad481612c6b565b915050610a5d565b5060008111610afd5760405162461bcd60e51b815260040161080f90612cef565b600554811115610b1f5760405162461bcd60e51b815260040161080f90612d1c565b8060026003016000828254610b349190612c04565b9091555050600654610b50906001600160a01b03163383612690565b60405181815233907f106f923f993c2149d49b4255ff723acafa1f2d94393f561d3eda32ae348f72419060200160405180910390a250610b906001600055565b565b3360009081526008602052604090205460ff16610bc15760405162461bcd60e51b815260040161080f90612c84565b610bd56001600160a01b0383163383612690565b6040518181526001600160a01b0383169033907fa92ff919b850e4909ab2261d907ef955f11bc1716733a6cbece38d163a69af8a9060200160405180910390a35050565b610c21612666565b60008060005b336000908152600a6020526040902054811015610d6157336000908152600a60205260408120805483908110610c5f57610c5f612bd8565b6000918252602090912060089091020160078101549091506001600160a01b038681169116148015610c9d57506007810154600160a01b900460ff16155b15610d4e576000610cae3384611925565b90508160030154811015610cd45760405162461bcd60e51b815260040161080f90612d51565b6000826003015482610ce69190612c04565b90508015610d4b5780836003016000828254610d029190612c58565b90915550610d1290508187612c58565b4260058501558354600385015491975011610d3d5760078301805460ff60a01b1916600160a01b1790555b84610d4781612c6b565b9550505b50505b5080610d5981612c6b565b915050610c27565b5060008211610d825760405162461bcd60e51b815260040161080f90612cef565b6040516370a0823160e01b81523060048201526000906001600160a01b038516906370a0823190602401602060405180830381865afa158015610dc9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ded9190612d7f565b905082811015610e0f5760405162461bcd60e51b815260040161080f90612d1c565b610e236001600160a01b0385163385612690565b60405183815233907f106f923f993c2149d49b4255ff723acafa1f2d94393f561d3eda32ae348f7241906020015b60405180910390a2505050610e666001600055565b50565b610e71612666565b336000908152600960205260409020548110610ec55760405162461bcd60e51b8152602060048201526013602482015272092dcecc2d8d2c840e6e8c2d6ca40d2dcc8caf606b1b604482015260640161080f565b336000908152600960205260408120805483908110610ee657610ee6612bd8565b60009182526020909120600590910201805490915080610f185760405162461bcd60e51b815260040161080f90612cef565b8160030154421015610f645760405162461bcd60e51b815260206004820152601560248201527414dd185ad9481a5cc81cdd1a5b1b081b1bd8dad959605a1b604482015260640161080f565b6006546040516370a0823160e01b81523060048201526000916001600160a01b0316906370a0823190602401602060405180830381865afa158015610fad573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fd19190612d7f565b905081811015610ff35760405162461bcd60e51b815260040161080f90612d1c565b60008084556004808501805460ff191660011790558054849290611018908490612c04565b9091555050600654611034906001600160a01b03163384612690565b33600090815260096020526040902080548590811061105557611055612bd8565b60009182526020822060059091020181815560018101829055600281018290556003810191909155600401805460ff1916905560405133907f106f923f993c2149d49b4255ff723acafa1f2d94393f561d3eda32ae348f724190610e519085815260200190565b600b60205281600052604060002081815481106110d857600080fd5b600091825260209091206002909102018054600190910154909250905082565b6009602052816000526040600020818154811061111457600080fd5b600091825260209091206005909102018054600182015460028301546003840154600490940154929550909350919060ff1685565b3360009081526008602052604090205460ff166111785760405162461bcd60e51b815260040161080f90612c84565b6001600160a01b039182166000908152600c6020526040902080546001600160a01b03191691909216179055565b6000805b33600090815260096020526040902054811015611229573360009081526009602052604081208054839081106111e2576111e2612bd8565b9060005260206000209060050201905060006111fe3384611750565b905061120a8185612c58565b426001909301929092555091508061122181612c6b565b9150506111aa565b50600f5481116112745760405162461bcd60e51b8152602060048201526016602482015275139bdd08195b9bdd59da081d1bc818dbdb5c1bdd5b9960521b604482015260640161080f565b336000818152600960209081526040808320815160a0810183528681524281850152948452600d90925291829020549092918201906112b29061072d565b6003546112bf9190612c58565b8152602001600260000154426112d59190612c58565b8152600060209182018190528354600180820186559482528282208451600590920201908155918301519382019390935560408201516002820155606082015160038201556080909101516004918201805460ff19169115159190911790558054839290611344908490612c58565b909155505060405181815233907f3afe5b60dee16617a8adbbd7d6c28f17a27efabb834872efa8e7ad5dafddf6f19060200160405180910390a250565b600081116113d15760405162461bcd60e51b815260206004820181905260248201527f416d6f756e74206d7573742062652067726561746572207468616e207a65726f604482015260640161080f565b6001600160a01b038083166000908152600c602052604090205416806114455760405162461bcd60e51b815260206004820152602360248201527f5072696365206f7261636c65206e6f742073657420666f72207468697320746f60448201526235b2b760e91b606482015260840161080f565b61145a6001600160a01b0384163330856125f9565b60006064611469600a85612c17565b6114739190612c44565b6001600160a01b038581166000818152600c60205260408082205490516302c68be360e31b8152600481019390935293945092670de0b6b3a76400009287929116906316345f1890602401602060405180830381865afa1580156114db573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114ff9190612d7f565b6115099190612c17565b6115139190612c44565b9050600f5481116115365760405162461bcd60e51b815260040161080f90612cac565b336000908152600d602052604081208054839290611555908490612c58565b9091555050336000908152600a6020908152604091829020825161012081018452878152918201859052600e5490928201906115919042612c58565b81526000602080830182905260408084018390524260608086018290526080808701929092526001600160a01b03808e1660a08089019190915260c0978801879052895460018181018c559a885296869020895160089098020196875594880151988601989098558683015160028601558601516003850155850151600484015590840151600583015591830151600682015560e083015160079091018054610100909401511515600160a01b026001600160a81b03199094169190941617919091179091555133907f9ade76f4385de306666dfb21a52b27d52db0fde8ad0f515fa261f532cac60d21906116929087908690918252602082015260400190565b60405180910390a25050505050565b3360009081526008602052604090205460ff166116d05760405162461bcd60e51b815260040161080f90612c84565b6001600160a01b03811660009081526008602052604090205460ff161561172c5760405162461bcd60e51b815260206004820152601060248201526f20b63932b0b23c9030b71037bbb732b960811b604482015260640161080f565b6001600160a01b03166000908152600860205260409020805460ff19166001179055565b6001600160a01b038216600090815260096020526040812080548291908490811061177d5761177d612bd8565b90600052602060002090600502019050600081600101544261179f9190612c04565b905060006127106201518083856002015486600001546117bf9190612c17565b6117c99190612c17565b6117d39190612c44565b6117dd9190612c44565b9695505050505050565b3360009081526008602052604090205460ff166118165760405162461bcd60e51b815260040161080f90612c84565b600154811061185d5760405162461bcd60e51b8152602060048201526013602482015272496e646578206f7574206f6620626f756e647360681b604482015260640161080f565b805b6001805461186d9190612c04565b8110156118eb5760016118808282612c58565b8154811061189057611890612bd8565b9060005260206000209060030201600182815481106118b1576118b1612bd8565b60009182526020909120825460039092020190815560018083015490820155600291820154910155806118e381612c6b565b91505061185f565b5060018054806118fd576118fd612d98565b6000828152602081206003600019909301928302018181556001810182905560020155905550565b6001600160a01b0382166000908152600a6020526040812080548291908490811061195257611952612bd8565b9060005260206000209060080201905060008160060154426119749190612c04565b60078301549091506001600160a01b03166000805b6001600160a01b0383166000908152600b6020526040902054811015610720576001600160a01b0383166000908152600b602052604081208054839081106119d3576119d3612bd8565b600091825260209091206002909102018054600182015491925090818710611a1d57875461271090611a06908390612c17565b611a109190612c44565b611a1a9086612c58565b94505b5050508080611a2b90612c6b565b915050611989565b3360009081526008602052604090205460ff16611a625760405162461bcd60e51b815260040161080f90612c84565b600654611a79906001600160a01b03163383612690565b6006546040518281526001600160a01b039091169033907fa92ff919b850e4909ab2261d907ef955f11bc1716733a6cbece38d163a69af8a9060200160405180910390a350565b3360009081526008602052604090205460ff16611aef5760405162461bcd60e51b815260040161080f90612c84565b600654611b07906001600160a01b03163330846125f9565b8060026003016000828254611b1c9190612c58565b90915550506040518181527f4e9221f2cca6ca0397acc6004ea0b716798254f5abcf53924fab34f0373e5d4e906020015b60405180910390a150565b3360009081526008602052604090205460ff16611b875760405162461bcd60e51b815260040161080f90612c84565b600f8190556040518181527fe5dadb089824c750e4e7a2e98bfbe130f1a09834769e81339639fbe18606859b90602001611b4d565b3360009081526008602052604090205460ff16611beb5760405162461bcd60e51b815260040161080f90612c84565b611bf88262015180612c17565b600255600381905560408051838152602081018390527f7fa9647ec1cc14e3822b46d05a2b9d4e019bde8875c0088c46b6503d71bf1722910160405180910390a15050565b611c45612666565b336000908152600a60205260408120805483908110611c6657611c66612bd8565b600091825260209091206008909102016007810154909150600160a01b900460ff1615611cc95760405162461bcd60e51b81526020600482015260116024820152705374616b6520697320436f6d706c65746560781b604482015260640161080f565b6000611cd53384611925565b90508160030154811015611cfb5760405162461bcd60e51b815260040161080f90612d51565b6000826003015482611d0d9190612c04565b905060008111611d5b5760405162461bcd60e51b81526020600482015260196024820152784e6f2076657374656420616d6f756e7420746f20636c61696d60381b604482015260640161080f565b80836003016000828254611d6f9190612c58565b90915550508254600384015410611d965760078301805460ff60a01b1916600160a01b1790555b6007830154611daf906001600160a01b03163383612690565b604080518281526000602082015233917f4a94c2c356e29a6583071e731bdacf2ca56565ba5efebcff6936eb7923b517219101610e51565b3360009081526008602052604090205460ff16611e165760405162461bcd60e51b815260040161080f90612c84565b60008211611e725760405162461bcd60e51b815260206004820152602360248201527f4c6f636b2074696d65206d7573742062652067726561746572207468616e207a60448201526265726f60e81b606482015260840161080f565b60008111611ed85760405162461bcd60e51b815260206004820152602d60248201527f50657263656e74616765207065722073746570206d757374206265206772656160448201526c746572207468616e207a65726f60981b606482015260840161080f565b612710611ee58282612dae565b15611f3d5760405162461bcd60e51b815260206004820152602260248201527f50657263656e74616765206d757374206469766964652031303025206576656e6044820152616c7960f01b606482015260840161080f565b6000611f498383612c44565b90506000611f578286612c44565b6001600160a01b0387166000908152600b60205260408120919250611f7c9190612a6f565b60015b828111611ff3576001600160a01b0387166000908152600b60205260409081902081518083019092529080611fb48486612c17565b81526020908101889052825460018181018555600094855293829020835160029092020190815591015191015580611feb81612c6b565b915050611f7f565b506040516001600160a01b038716907fde4b6ccc38b84f88129403b65a309f9b1c41d4c316bc2118d7614e449b9d4c4590600090a2505050505050565b600a602052816000526040600020818154811061204c57600080fd5b600091825260209091206008909102018054600182015460028301546003840154600485015460058601546006870154600790970154959850939650919490939192916001600160a01b03811690600160a01b900460ff1689565b600181815481106120b757600080fd5b600091825260209091206003909102018054600182015460029092015490925083565b6120e2612666565b336000908152600a6020526040812080548390811061210357612103612bd8565b90600052602060002090600802019050600061211f3384610610565b905081600401548110156121455760405162461bcd60e51b815260040161080f90612d51565b60008260040154826121579190612c04565b9050600081116121a55760405162461bcd60e51b81526020600482015260196024820152784e6f2076657374656420616d6f756e7420746f20636c61696d60381b604482015260640161080f565b808360040160008282546121b99190612c58565b909155505060078301546121d7906001600160a01b03163383612690565b60405181815233907f4e69fdc49495bcab2b4375781457ba16653a90eb4ffb6588351bdc39071433e290602001610e51565b81831061226d5760405162461bcd60e51b815260206004820152602c60248201527f496e76616c69642072616e67653a206d696e546f6b656e73206d75737420626560448201526b203c206d6178546f6b656e7360a01b606482015260840161080f565b61227783836126c1565b156122cf5760405162461bcd60e51b815260206004820152602260248201527f52616e6765206f7665726c6170732077697468206578697374696e6720746965604482015261727360f01b606482015260840161080f565b60005b6001548110156123785783600182815481106122f0576122f0612bd8565b9060005260206000209060030201600001541480156123325750826001828154811061231e5761231e612bd8565b906000526020600020906003020160010154145b1561236657816001828154811061234b5761234b612bd8565b90600052602060002090600302016002018190555050505050565b8061237081612c6b565b9150506122d2565b50604080516060810182528481526020810184815291810183815260018054808201825560009190915291517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf660039093029283015591517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf782015590517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf890910155612423612752565b505050565b600080805b6001600160a01b03841660009081526009602052604090205481101561247b5760006124598583611750565b90506124658184612c58565b925050808061247390612c6b565b91505061242d565b5092915050565b3360009081526008602052604090205460ff166124b15760405162461bcd60e51b815260040161080f90612c84565b600081116124f25760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b604482015260640161080f565b60096000836001600160a01b03166001600160a01b031681526020019081526020016000206040518060a0016040528083815260200142815260200160026001015481526020016002600001544261254a9190612c58565b8152600060209182018190528354600180820186559482528282208451600590920201908155918301519382019390935560408201516002820155606082015160038201556080909101516004918201805460ff191691151591909117905580548392906125b9908490612c58565b90915550506040518181526001600160a01b038316907f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d90602001610a45565b6040516001600160a01b0384811660248301528381166044830152606482018390526126609186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506128e4565b50505050565b60026000540361268957604051633ee5aeb560e01b815260040160405180910390fd5b6002600055565b6040516001600160a01b0383811660248301526044820183905261242391859182169063a9059cbb9060640161262e565b6000805b60015481101561274857600181815481106126e2576126e2612bd8565b906000526020600020906003020160010154841115801561272757506001818154811061271157612711612bd8565b9060005260206000209060030201600001548310155b15612736576001915050610727565b8061274081612c6b565b9150506126c5565b5060009392505050565b60005b600154811015610e6657600061276c826001612c58565b90505b6001548110156128d1576001818154811061278c5761278c612bd8565b906000526020600020906003020160000154600183815481106127b1576127b1612bd8565b90600052602060002090600302016000015411156128bf576000600183815481106127de576127de612bd8565b9060005260206000209060030201604051806060016040529081600082015481526020016001820154815260200160028201548152505090506001828154811061282a5761282a612bd8565b90600052602060002090600302016001848154811061284b5761284b612bd8565b9060005260206000209060030201600082015481600001556001820154816001015560028201548160020155905050806001838154811061288e5761288e612bd8565b9060005260206000209060030201600082015181600001556020820151816001015560408201518160020155905050505b806128c981612c6b565b91505061276f565b50806128dc81612c6b565b915050612755565b60006128f96001600160a01b03841683612947565b9050805160001415801561291e57508080602001905181019061291c9190612dc2565b155b1561242357604051635274afe760e01b81526001600160a01b038416600482015260240161080f565b60606129558383600061295c565b9392505050565b6060814710156129815760405163cd78605960e01b815230600482015260240161080f565b600080856001600160a01b0316848660405161299d9190612de4565b60006040518083038185875af1925050503d80600081146129da576040519150601f19603f3d011682016040523d82523d6000602084013e6129df565b606091505b50915091506117dd8683836060826129ff576129fa82612a46565b612955565b8151158015612a1657506001600160a01b0384163b155b15612a3f57604051639996b31560e01b81526001600160a01b038516600482015260240161080f565b5080612955565b805115612a565780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5080546000825560020290600052602060002090810190610e6691905b80821115612aa65760008082556001820155600201612a8c565b5090565b80356001600160a01b0381168114612ac157600080fd5b919050565b600060208284031215612ad857600080fd5b61295582612aaa565b60008060408385031215612af457600080fd5b612afd83612aaa565b946020939093013593505050565b600060208284031215612b1d57600080fd5b5035919050565b60008060408385031215612b3757600080fd5b612b4083612aaa565b9150612b4e60208401612aaa565b90509250929050565b60008060408385031215612b6a57600080fd5b50508035926020909101359150565b600080600060608486031215612b8e57600080fd5b612b9784612aaa565b95602085013595506040909401359392505050565b600080600060608486031215612bc157600080fd5b505081359360208301359350604090920135919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111561072757610727612bee565b808202811582820484141761072757610727612bee565b634e487b7160e01b600052601260045260246000fd5b600082612c5357612c53612c2e565b500490565b8082018082111561072757610727612bee565b600060018201612c7d57612c7d612bee565b5060010190565b6020808252600e908201526d139bdd08185d5d1a1bdc9a5e995960921b604082015260600190565b60208082526023908201527f416d6f756e74206d7573742062652067726561746572206d696e5374616b654c6040820152626f636b60e81b606082015260800190565b6020808252601390820152724e6f207265776172647320746f20636c61696d60681b604082015260600190565b6020808252818101527f496e73756666696369656e74207265776172647320696e2074686520706f6f6c604082015260600190565b602080825260149082015273125b9d985b1a590818db185a5b48185b5bdd5b9d60621b604082015260600190565b600060208284031215612d9157600080fd5b5051919050565b634e487b7160e01b600052603160045260246000fd5b600082612dbd57612dbd612c2e565b500690565b600060208284031215612dd457600080fd5b8151801515811461295557600080fd5b6000825160005b81811015612e055760208186018101518583015201612deb565b50600092019182525091905056fea2646970667358221220eb82f325bebfa4f584da439090990bd82bdb1ad14edeacf4637487a97b980fc764736f6c63430008140033000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913","id":1,"type":"ONCHAIN_INTERACTION","value":{"_kind":"bigint","value":"0"}},"type":"NETWORK_INTERACTION_REQUEST"} +{"futureId":"Paca#PacaFinanceWithBoostAndSchedule","networkInteractionId":1,"nonce":0,"transaction":{"fees":{"maxFeePerGas":{"_kind":"bigint","value":"1135062584"},"maxPriorityFeePerGas":{"_kind":"bigint","value":"1000000000"}},"hash":"0x40d2f7ae35c04821ba5feefb42d0931db4abf7bbb024d7cadbaa44685e677f8a"},"type":"TRANSACTION_SEND"} +{"futureId":"Paca#PacaFinanceWithBoostAndSchedule","hash":"0x40d2f7ae35c04821ba5feefb42d0931db4abf7bbb024d7cadbaa44685e677f8a","networkInteractionId":1,"receipt":{"blockHash":"0xeeb086e0dfe3751516c41fd07f7b7c891a585a68c1e4833e2993cd9bfbfa7701","blockNumber":23445255,"contractAddress":"0xc64C70b657d987CBDf5bea0d8B0608A0FfAFa78a","logs":[],"status":"SUCCESS"},"type":"TRANSACTION_CONFIRM"} +{"futureId":"Paca#PacaFinanceWithBoostAndSchedule","result":{"address":"0xc64C70b657d987CBDf5bea0d8B0608A0FfAFa78a","type":"SUCCESS"},"type":"DEPLOYMENT_EXECUTION_STATE_COMPLETE"} \ No newline at end of file diff --git a/ignition/deployments/chain-31337/journal.jsonl b/ignition/deployments/chain-31337/journal.jsonl new file mode 100644 index 0000000..a28236d --- /dev/null +++ b/ignition/deployments/chain-31337/journal.jsonl @@ -0,0 +1,7 @@ + +{"chainId":31337,"type":"DEPLOYMENT_INITIALIZE"} +{"artifactId":"Paca#PacaFinanceWithBoostAndSchedule","constructorArgs":["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],"contractName":"PacaFinanceWithBoostAndSchedule","dependencies":[],"from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","futureId":"Paca#PacaFinanceWithBoostAndSchedule","futureType":"NAMED_ARTIFACT_CONTRACT_DEPLOYMENT","libraries":{},"strategy":"basic","strategyConfig":{},"type":"DEPLOYMENT_EXECUTION_STATE_INITIALIZE","value":{"_kind":"bigint","value":"0"}} +{"futureId":"Paca#PacaFinanceWithBoostAndSchedule","networkInteraction":{"data":"0x60806040526301499700600e5567d02ab486cedc0000600f553480156200002557600080fd5b50604051620030b3380380620030b3833981016040819052620000489162000228565b60016000818155600780546001600160a01b031990811633908117909255908252600860209081526040808420805460ff191686179055600680546001600160a01b03979097169684169690961790955563014997006002908155602160039081558651606080820189528682526063828601908152828a018881528954808b018b558a8a5293519385027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf68181019590955591517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf78084019190915590517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf8928301558a518084018c526064815260c7818901908152818d018c81528c54808e018e558d8d5292519288028088019390935590518284015551908301558a519283018b5260c883526000198388019081529a83019586528954808b018b559989529151989093029182019790975596519587019590955551949093019390935573940181a94a35a4569e4529a3cdfb74e38fd986319052600c90527fb0d7879752dd69b18392316706d5581b7f77657c28b74d89fe870a1d343b50d98054909116730dde1b42f7b3891c9731280a74081501729a73c51790556200025a565b6000602082840312156200023b57600080fd5b81516001600160a01b03811681146200025357600080fd5b9392505050565b612e49806200026a6000396000f3fe608060405234801561001057600080fd5b50600436106102275760003560e01c80636ae21510116101305780639562ee15116100b8578063cfb866da1161007c578063cfb866da14610596578063d9193025146105c4578063d9613410146105d7578063da40ef80146105ea578063e4d06f90146105fd57600080fd5b80639562ee15146104e857806396ed7f89146104fb578063ac97b4171461050e578063b92a349f14610521578063bd84477d1461053457600080fd5b80637497211b116100ff5780637497211b1461048957806380ca0ecf1461049c578063853e0df2146104af5780638bdf67f2146104c25780638da5cb5b146104d557600080fd5b80636ae21510146104485780636c3b0c53146104505780637065cb4814610463578063710ea0ce1461047657600080fd5b8063372500ab116101b357806344c7d6ef1161018257806344c7d6ef1461039d57806351f6cf2f146103b0578063584b62a1146103d8578063592d1dd11461041557806367a74ddc1461043557600080fd5b8063372500ab1461036657806338a85ce21461036e5780633f35e7221461037757806343c7c0111461038a57600080fd5b806316f0115b116101fa57806316f0115b146102d9578063173825d91461032d5780631ada70a8146103425780631bf6ddae1461034b57806335ed3fd61461035e57600080fd5b8063013745181461022c578063022914a7146102725780630a84096a146102a557806313c8e2de146102c6575b600080fd5b61025561023a366004612ac6565b600c602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b610295610280366004612ac6565b60086020526000908152604090205460ff1681565b6040519015158152602001610269565b6102b86102b3366004612ae1565b610610565b604051908152602001610269565b6102b86102d4366004612b0b565b61072d565b6002546003546004546005546006546102fc94939291906001600160a01b031685565b6040805195865260208601949094529284019190915260608301526001600160a01b0316608082015260a001610269565b61034061033b366004612ac6565b6107e0565b005b6102b8600e5481565b610340610359366004612b0b565b6108e1565b6102b8600a81565b610340610a51565b6102b8600f5481565b610340610385366004612ae1565b610b92565b610340610398366004612ac6565b610c19565b6103406103ab366004612b0b565b610e69565b6103c36103be366004612ae1565b6110bc565b60408051928352602083019190915201610269565b6103eb6103e6366004612ae1565b6110f8565b6040805195865260208601949094529284019190915260608301521515608082015260a001610269565b6102b8610423366004612ac6565b600d6020526000908152604090205481565b610340610443366004612b24565b611149565b6103406111a6565b61034061045e366004612ae1565b611381565b610340610471366004612ac6565b6116a1565b6102b8610484366004612ae1565b611750565b610340610497366004612b0b565b6117e7565b6102b86104aa366004612ae1565b611925565b6103406104bd366004612b0b565b611a33565b6103406104d0366004612b0b565b611ac0565b600754610255906001600160a01b031681565b6103406104f6366004612b0b565b611b58565b610340610509366004612b57565b611bbc565b61034061051c366004612b0b565b611c3d565b61034061052f366004612b79565b611de7565b610547610542366004612ae1565b612030565b60408051998a5260208a0198909852968801959095526060870193909352608086019190915260a085015260c08401526001600160a01b031660e0830152151561010082015261012001610269565b6105a96105a4366004612b0b565b6120a7565b60408051938452602084019290925290820152606001610269565b6103406105d2366004612b0b565b6120da565b6103406105e5366004612bac565b612209565b6102b86105f8366004612ac6565b612428565b61034061060b366004612ae1565b612482565b6001600160a01b0382166000908152600a6020526040812080548291908490811061063d5761063d612bd8565b90600052602060002090600802019050600081600601544261065f9190612c04565b60078301549091506001600160a01b03166000805b6001600160a01b0383166000908152600b6020526040902054811015610720576001600160a01b0383166000908152600b602052604081208054839081106106be576106be612bd8565b60009182526020909120600290910201805460018201549192509081871061070a576127108189600101546106f39190612c17565b6106fd9190612c44565b6107079086612c58565b94505b505050808061071890612c6b565b915050610674565b5093505050505b92915050565b6000805b6001548110156107d7576001818154811061074e5761074e612bd8565b906000526020600020906003020160000154831015801561079357506001818154811061077d5761077d612bd8565b9060005260206000209060030201600101548311155b156107c557600181815481106107ab576107ab612bd8565b906000526020600020906003020160020154915050919050565b806107cf81612c6b565b915050610731565b50600092915050565b3360009081526008602052604090205460ff166108185760405162461bcd60e51b815260040161080f90612c84565b60405180910390fd5b6001600160a01b03811660009081526008602052604090205460ff1661086f5760405162461bcd60e51b815260206004820152600c60248201526b2737ba1030b71037bbb732b960a11b604482015260640161080f565b336001600160a01b038216036108c05760405162461bcd60e51b815260206004820152601660248201527521b0b73737ba103932b6b7bb32903cb7bab939b2b63360511b604482015260640161080f565b6001600160a01b03166000908152600860205260409020805460ff19169055565b600f546108f38264e8d4a51000612c17565b116109105760405162461bcd60e51b815260040161080f90612cac565b600654610928906001600160a01b03163330846125f9565b600354336000908152600d60205260409020546109449061072d565b61094e9082612c58565b905060096000336001600160a01b03166001600160a01b031681526020019081526020016000206040518060a00160405280848152602001428152602001838152602001600260000154426109a39190612c58565b8152600060209182018190528354600180820186559482528282208451600590920201908155918301519382019390935560408201516002820155606082015160038201556080909101516004918201805460ff19169115159190911790558054849290610a12908490612c58565b909155505060405182815233907f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d906020015b60405180910390a25050565b610a59612666565b6000805b33600090815260096020526040902054811015610adc57336000908152600960205260408120805483908110610a9557610a95612bd8565b906000526020600020906005020190506000610ab13384611750565b9050610abd8185612c58565b4260019093019290925550915080610ad481612c6b565b915050610a5d565b5060008111610afd5760405162461bcd60e51b815260040161080f90612cef565b600554811115610b1f5760405162461bcd60e51b815260040161080f90612d1c565b8060026003016000828254610b349190612c04565b9091555050600654610b50906001600160a01b03163383612690565b60405181815233907f106f923f993c2149d49b4255ff723acafa1f2d94393f561d3eda32ae348f72419060200160405180910390a250610b906001600055565b565b3360009081526008602052604090205460ff16610bc15760405162461bcd60e51b815260040161080f90612c84565b610bd56001600160a01b0383163383612690565b6040518181526001600160a01b0383169033907fa92ff919b850e4909ab2261d907ef955f11bc1716733a6cbece38d163a69af8a9060200160405180910390a35050565b610c21612666565b60008060005b336000908152600a6020526040902054811015610d6157336000908152600a60205260408120805483908110610c5f57610c5f612bd8565b6000918252602090912060089091020160078101549091506001600160a01b038681169116148015610c9d57506007810154600160a01b900460ff16155b15610d4e576000610cae3384611925565b90508160030154811015610cd45760405162461bcd60e51b815260040161080f90612d51565b6000826003015482610ce69190612c04565b90508015610d4b5780836003016000828254610d029190612c58565b90915550610d1290508187612c58565b4260058501558354600385015491975011610d3d5760078301805460ff60a01b1916600160a01b1790555b84610d4781612c6b565b9550505b50505b5080610d5981612c6b565b915050610c27565b5060008211610d825760405162461bcd60e51b815260040161080f90612cef565b6040516370a0823160e01b81523060048201526000906001600160a01b038516906370a0823190602401602060405180830381865afa158015610dc9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ded9190612d7f565b905082811015610e0f5760405162461bcd60e51b815260040161080f90612d1c565b610e236001600160a01b0385163385612690565b60405183815233907f106f923f993c2149d49b4255ff723acafa1f2d94393f561d3eda32ae348f7241906020015b60405180910390a2505050610e666001600055565b50565b610e71612666565b336000908152600960205260409020548110610ec55760405162461bcd60e51b8152602060048201526013602482015272092dcecc2d8d2c840e6e8c2d6ca40d2dcc8caf606b1b604482015260640161080f565b336000908152600960205260408120805483908110610ee657610ee6612bd8565b60009182526020909120600590910201805490915080610f185760405162461bcd60e51b815260040161080f90612cef565b8160030154421015610f645760405162461bcd60e51b815260206004820152601560248201527414dd185ad9481a5cc81cdd1a5b1b081b1bd8dad959605a1b604482015260640161080f565b6006546040516370a0823160e01b81523060048201526000916001600160a01b0316906370a0823190602401602060405180830381865afa158015610fad573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fd19190612d7f565b905081811015610ff35760405162461bcd60e51b815260040161080f90612d1c565b60008084556004808501805460ff191660011790558054849290611018908490612c04565b9091555050600654611034906001600160a01b03163384612690565b33600090815260096020526040902080548590811061105557611055612bd8565b60009182526020822060059091020181815560018101829055600281018290556003810191909155600401805460ff1916905560405133907f106f923f993c2149d49b4255ff723acafa1f2d94393f561d3eda32ae348f724190610e519085815260200190565b600b60205281600052604060002081815481106110d857600080fd5b600091825260209091206002909102018054600190910154909250905082565b6009602052816000526040600020818154811061111457600080fd5b600091825260209091206005909102018054600182015460028301546003840154600490940154929550909350919060ff1685565b3360009081526008602052604090205460ff166111785760405162461bcd60e51b815260040161080f90612c84565b6001600160a01b039182166000908152600c6020526040902080546001600160a01b03191691909216179055565b6000805b33600090815260096020526040902054811015611229573360009081526009602052604081208054839081106111e2576111e2612bd8565b9060005260206000209060050201905060006111fe3384611750565b905061120a8185612c58565b426001909301929092555091508061122181612c6b565b9150506111aa565b50600f5481116112745760405162461bcd60e51b8152602060048201526016602482015275139bdd08195b9bdd59da081d1bc818dbdb5c1bdd5b9960521b604482015260640161080f565b336000818152600960209081526040808320815160a0810183528681524281850152948452600d90925291829020549092918201906112b29061072d565b6003546112bf9190612c58565b8152602001600260000154426112d59190612c58565b8152600060209182018190528354600180820186559482528282208451600590920201908155918301519382019390935560408201516002820155606082015160038201556080909101516004918201805460ff19169115159190911790558054839290611344908490612c58565b909155505060405181815233907f3afe5b60dee16617a8adbbd7d6c28f17a27efabb834872efa8e7ad5dafddf6f19060200160405180910390a250565b600081116113d15760405162461bcd60e51b815260206004820181905260248201527f416d6f756e74206d7573742062652067726561746572207468616e207a65726f604482015260640161080f565b6001600160a01b038083166000908152600c602052604090205416806114455760405162461bcd60e51b815260206004820152602360248201527f5072696365206f7261636c65206e6f742073657420666f72207468697320746f60448201526235b2b760e91b606482015260840161080f565b61145a6001600160a01b0384163330856125f9565b60006064611469600a85612c17565b6114739190612c44565b6001600160a01b038581166000818152600c60205260408082205490516302c68be360e31b8152600481019390935293945092670de0b6b3a76400009287929116906316345f1890602401602060405180830381865afa1580156114db573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114ff9190612d7f565b6115099190612c17565b6115139190612c44565b9050600f5481116115365760405162461bcd60e51b815260040161080f90612cac565b336000908152600d602052604081208054839290611555908490612c58565b9091555050336000908152600a6020908152604091829020825161012081018452878152918201859052600e5490928201906115919042612c58565b81526000602080830182905260408084018390524260608086018290526080808701929092526001600160a01b03808e1660a08089019190915260c0978801879052895460018181018c559a885296869020895160089098020196875594880151988601989098558683015160028601558601516003850155850151600484015590840151600583015591830151600682015560e083015160079091018054610100909401511515600160a01b026001600160a81b03199094169190941617919091179091555133907f9ade76f4385de306666dfb21a52b27d52db0fde8ad0f515fa261f532cac60d21906116929087908690918252602082015260400190565b60405180910390a25050505050565b3360009081526008602052604090205460ff166116d05760405162461bcd60e51b815260040161080f90612c84565b6001600160a01b03811660009081526008602052604090205460ff161561172c5760405162461bcd60e51b815260206004820152601060248201526f20b63932b0b23c9030b71037bbb732b960811b604482015260640161080f565b6001600160a01b03166000908152600860205260409020805460ff19166001179055565b6001600160a01b038216600090815260096020526040812080548291908490811061177d5761177d612bd8565b90600052602060002090600502019050600081600101544261179f9190612c04565b905060006127106201518083856002015486600001546117bf9190612c17565b6117c99190612c17565b6117d39190612c44565b6117dd9190612c44565b9695505050505050565b3360009081526008602052604090205460ff166118165760405162461bcd60e51b815260040161080f90612c84565b600154811061185d5760405162461bcd60e51b8152602060048201526013602482015272496e646578206f7574206f6620626f756e647360681b604482015260640161080f565b805b6001805461186d9190612c04565b8110156118eb5760016118808282612c58565b8154811061189057611890612bd8565b9060005260206000209060030201600182815481106118b1576118b1612bd8565b60009182526020909120825460039092020190815560018083015490820155600291820154910155806118e381612c6b565b91505061185f565b5060018054806118fd576118fd612d98565b6000828152602081206003600019909301928302018181556001810182905560020155905550565b6001600160a01b0382166000908152600a6020526040812080548291908490811061195257611952612bd8565b9060005260206000209060080201905060008160060154426119749190612c04565b60078301549091506001600160a01b03166000805b6001600160a01b0383166000908152600b6020526040902054811015610720576001600160a01b0383166000908152600b602052604081208054839081106119d3576119d3612bd8565b600091825260209091206002909102018054600182015491925090818710611a1d57875461271090611a06908390612c17565b611a109190612c44565b611a1a9086612c58565b94505b5050508080611a2b90612c6b565b915050611989565b3360009081526008602052604090205460ff16611a625760405162461bcd60e51b815260040161080f90612c84565b600654611a79906001600160a01b03163383612690565b6006546040518281526001600160a01b039091169033907fa92ff919b850e4909ab2261d907ef955f11bc1716733a6cbece38d163a69af8a9060200160405180910390a350565b3360009081526008602052604090205460ff16611aef5760405162461bcd60e51b815260040161080f90612c84565b600654611b07906001600160a01b03163330846125f9565b8060026003016000828254611b1c9190612c58565b90915550506040518181527f4e9221f2cca6ca0397acc6004ea0b716798254f5abcf53924fab34f0373e5d4e906020015b60405180910390a150565b3360009081526008602052604090205460ff16611b875760405162461bcd60e51b815260040161080f90612c84565b600f8190556040518181527fe5dadb089824c750e4e7a2e98bfbe130f1a09834769e81339639fbe18606859b90602001611b4d565b3360009081526008602052604090205460ff16611beb5760405162461bcd60e51b815260040161080f90612c84565b611bf88262015180612c17565b600255600381905560408051838152602081018390527f7fa9647ec1cc14e3822b46d05a2b9d4e019bde8875c0088c46b6503d71bf1722910160405180910390a15050565b611c45612666565b336000908152600a60205260408120805483908110611c6657611c66612bd8565b600091825260209091206008909102016007810154909150600160a01b900460ff1615611cc95760405162461bcd60e51b81526020600482015260116024820152705374616b6520697320436f6d706c65746560781b604482015260640161080f565b6000611cd53384611925565b90508160030154811015611cfb5760405162461bcd60e51b815260040161080f90612d51565b6000826003015482611d0d9190612c04565b905060008111611d5b5760405162461bcd60e51b81526020600482015260196024820152784e6f2076657374656420616d6f756e7420746f20636c61696d60381b604482015260640161080f565b80836003016000828254611d6f9190612c58565b90915550508254600384015410611d965760078301805460ff60a01b1916600160a01b1790555b6007830154611daf906001600160a01b03163383612690565b604080518281526000602082015233917f4a94c2c356e29a6583071e731bdacf2ca56565ba5efebcff6936eb7923b517219101610e51565b3360009081526008602052604090205460ff16611e165760405162461bcd60e51b815260040161080f90612c84565b60008211611e725760405162461bcd60e51b815260206004820152602360248201527f4c6f636b2074696d65206d7573742062652067726561746572207468616e207a60448201526265726f60e81b606482015260840161080f565b60008111611ed85760405162461bcd60e51b815260206004820152602d60248201527f50657263656e74616765207065722073746570206d757374206265206772656160448201526c746572207468616e207a65726f60981b606482015260840161080f565b612710611ee58282612dae565b15611f3d5760405162461bcd60e51b815260206004820152602260248201527f50657263656e74616765206d757374206469766964652031303025206576656e6044820152616c7960f01b606482015260840161080f565b6000611f498383612c44565b90506000611f578286612c44565b6001600160a01b0387166000908152600b60205260408120919250611f7c9190612a6f565b60015b828111611ff3576001600160a01b0387166000908152600b60205260409081902081518083019092529080611fb48486612c17565b81526020908101889052825460018181018555600094855293829020835160029092020190815591015191015580611feb81612c6b565b915050611f7f565b506040516001600160a01b038716907fde4b6ccc38b84f88129403b65a309f9b1c41d4c316bc2118d7614e449b9d4c4590600090a2505050505050565b600a602052816000526040600020818154811061204c57600080fd5b600091825260209091206008909102018054600182015460028301546003840154600485015460058601546006870154600790970154959850939650919490939192916001600160a01b03811690600160a01b900460ff1689565b600181815481106120b757600080fd5b600091825260209091206003909102018054600182015460029092015490925083565b6120e2612666565b336000908152600a6020526040812080548390811061210357612103612bd8565b90600052602060002090600802019050600061211f3384610610565b905081600401548110156121455760405162461bcd60e51b815260040161080f90612d51565b60008260040154826121579190612c04565b9050600081116121a55760405162461bcd60e51b81526020600482015260196024820152784e6f2076657374656420616d6f756e7420746f20636c61696d60381b604482015260640161080f565b808360040160008282546121b99190612c58565b909155505060078301546121d7906001600160a01b03163383612690565b60405181815233907f4e69fdc49495bcab2b4375781457ba16653a90eb4ffb6588351bdc39071433e290602001610e51565b81831061226d5760405162461bcd60e51b815260206004820152602c60248201527f496e76616c69642072616e67653a206d696e546f6b656e73206d75737420626560448201526b203c206d6178546f6b656e7360a01b606482015260840161080f565b61227783836126c1565b156122cf5760405162461bcd60e51b815260206004820152602260248201527f52616e6765206f7665726c6170732077697468206578697374696e6720746965604482015261727360f01b606482015260840161080f565b60005b6001548110156123785783600182815481106122f0576122f0612bd8565b9060005260206000209060030201600001541480156123325750826001828154811061231e5761231e612bd8565b906000526020600020906003020160010154145b1561236657816001828154811061234b5761234b612bd8565b90600052602060002090600302016002018190555050505050565b8061237081612c6b565b9150506122d2565b50604080516060810182528481526020810184815291810183815260018054808201825560009190915291517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf660039093029283015591517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf782015590517fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf890910155612423612752565b505050565b600080805b6001600160a01b03841660009081526009602052604090205481101561247b5760006124598583611750565b90506124658184612c58565b925050808061247390612c6b565b91505061242d565b5092915050565b3360009081526008602052604090205460ff166124b15760405162461bcd60e51b815260040161080f90612c84565b600081116124f25760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b604482015260640161080f565b60096000836001600160a01b03166001600160a01b031681526020019081526020016000206040518060a0016040528083815260200142815260200160026001015481526020016002600001544261254a9190612c58565b8152600060209182018190528354600180820186559482528282208451600590920201908155918301519382019390935560408201516002820155606082015160038201556080909101516004918201805460ff191691151591909117905580548392906125b9908490612c58565b90915550506040518181526001600160a01b038316907f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d90602001610a45565b6040516001600160a01b0384811660248301528381166044830152606482018390526126609186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506128e4565b50505050565b60026000540361268957604051633ee5aeb560e01b815260040160405180910390fd5b6002600055565b6040516001600160a01b0383811660248301526044820183905261242391859182169063a9059cbb9060640161262e565b6000805b60015481101561274857600181815481106126e2576126e2612bd8565b906000526020600020906003020160010154841115801561272757506001818154811061271157612711612bd8565b9060005260206000209060030201600001548310155b15612736576001915050610727565b8061274081612c6b565b9150506126c5565b5060009392505050565b60005b600154811015610e6657600061276c826001612c58565b90505b6001548110156128d1576001818154811061278c5761278c612bd8565b906000526020600020906003020160000154600183815481106127b1576127b1612bd8565b90600052602060002090600302016000015411156128bf576000600183815481106127de576127de612bd8565b9060005260206000209060030201604051806060016040529081600082015481526020016001820154815260200160028201548152505090506001828154811061282a5761282a612bd8565b90600052602060002090600302016001848154811061284b5761284b612bd8565b9060005260206000209060030201600082015481600001556001820154816001015560028201548160020155905050806001838154811061288e5761288e612bd8565b9060005260206000209060030201600082015181600001556020820151816001015560408201518160020155905050505b806128c981612c6b565b91505061276f565b50806128dc81612c6b565b915050612755565b60006128f96001600160a01b03841683612947565b9050805160001415801561291e57508080602001905181019061291c9190612dc2565b155b1561242357604051635274afe760e01b81526001600160a01b038416600482015260240161080f565b60606129558383600061295c565b9392505050565b6060814710156129815760405163cd78605960e01b815230600482015260240161080f565b600080856001600160a01b0316848660405161299d9190612de4565b60006040518083038185875af1925050503d80600081146129da576040519150601f19603f3d011682016040523d82523d6000602084013e6129df565b606091505b50915091506117dd8683836060826129ff576129fa82612a46565b612955565b8151158015612a1657506001600160a01b0384163b155b15612a3f57604051639996b31560e01b81526001600160a01b038516600482015260240161080f565b5080612955565b805115612a565780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5080546000825560020290600052602060002090810190610e6691905b80821115612aa65760008082556001820155600201612a8c565b5090565b80356001600160a01b0381168114612ac157600080fd5b919050565b600060208284031215612ad857600080fd5b61295582612aaa565b60008060408385031215612af457600080fd5b612afd83612aaa565b946020939093013593505050565b600060208284031215612b1d57600080fd5b5035919050565b60008060408385031215612b3757600080fd5b612b4083612aaa565b9150612b4e60208401612aaa565b90509250929050565b60008060408385031215612b6a57600080fd5b50508035926020909101359150565b600080600060608486031215612b8e57600080fd5b612b9784612aaa565b95602085013595506040909401359392505050565b600080600060608486031215612bc157600080fd5b505081359360208301359350604090920135919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111561072757610727612bee565b808202811582820484141761072757610727612bee565b634e487b7160e01b600052601260045260246000fd5b600082612c5357612c53612c2e565b500490565b8082018082111561072757610727612bee565b600060018201612c7d57612c7d612bee565b5060010190565b6020808252600e908201526d139bdd08185d5d1a1bdc9a5e995960921b604082015260600190565b60208082526023908201527f416d6f756e74206d7573742062652067726561746572206d696e5374616b654c6040820152626f636b60e81b606082015260800190565b6020808252601390820152724e6f207265776172647320746f20636c61696d60681b604082015260600190565b6020808252818101527f496e73756666696369656e74207265776172647320696e2074686520706f6f6c604082015260600190565b602080825260149082015273125b9d985b1a590818db185a5b48185b5bdd5b9d60621b604082015260600190565b600060208284031215612d9157600080fd5b5051919050565b634e487b7160e01b600052603160045260246000fd5b600082612dbd57612dbd612c2e565b500690565b600060208284031215612dd457600080fd5b8151801515811461295557600080fd5b6000825160005b81811015612e055760208186018101518583015201612deb565b50600092019182525091905056fea2646970667358221220eb82f325bebfa4f584da439090990bd82bdb1ad14edeacf4637487a97b980fc764736f6c63430008140033000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913","id":1,"type":"ONCHAIN_INTERACTION","value":{"_kind":"bigint","value":"0"}},"type":"NETWORK_INTERACTION_REQUEST"} +{"futureId":"Paca#PacaFinanceWithBoostAndSchedule","networkInteractionId":1,"nonce":350,"transaction":{"fees":{"maxFeePerGas":{"_kind":"bigint","value":"1006514028"},"maxPriorityFeePerGas":{"_kind":"bigint","value":"1000000000"}},"hash":"0x4461cc91e353fb18140141da6c41a3e6c90d1ecc876610d72601e151746d44ce"},"type":"TRANSACTION_SEND"} +{"futureId":"Paca#PacaFinanceWithBoostAndSchedule","hash":"0x4461cc91e353fb18140141da6c41a3e6c90d1ecc876610d72601e151746d44ce","networkInteractionId":1,"receipt":{"blockHash":"0xc2a782d93e7b54de91a7821cb1b4a0885a216d028e3761230b94439f82a24a79","blockNumber":23452069,"contractAddress":"0x103A3b128991781EE2c8db0454cA99d67b257923","logs":[],"status":"SUCCESS"},"type":"TRANSACTION_CONFIRM"} +{"futureId":"Paca#PacaFinanceWithBoostAndSchedule","result":{"address":"0x103A3b128991781EE2c8db0454cA99d67b257923","type":"SUCCESS"},"type":"DEPLOYMENT_EXECUTION_STATE_COMPLETE"} \ No newline at end of file diff --git a/ignition/modules/paca.js b/ignition/modules/paca.js new file mode 100644 index 0000000..0676063 --- /dev/null +++ b/ignition/modules/paca.js @@ -0,0 +1,9 @@ +const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); + +module.exports = buildModule("Paca", (m) => { + const paca = m.contract("PacaFinanceWithBoostAndSchedule", ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"]); + + // m.call(paca, "launch", []); + + return { paca }; +}); \ No newline at end of file diff --git a/scripts/advanceTime.js b/scripts/advanceTime.js new file mode 100644 index 0000000..ab77183 --- /dev/null +++ b/scripts/advanceTime.js @@ -0,0 +1,28 @@ +const { ethers, JsonRpcProvider } = require('ethers'); + +// Define the Tenderly provider with your Tenderly fork RPC URL +// const provider = new ethers.providers.JsonRpcProvider("https://rpc.tenderly.co/fork/c3c1b1c6-3139-4682-80ef-992caf6c59d8"); +const provider = new JsonRpcProvider('https://virtual.binance.rpc.tenderly.co/bd4ca4c1-0512-47bf-9674-6f509e9ad7fc'); + +// Define the number of seconds to advance the time +const timeInSeconds = 24 * 60 * 60 * 120; // 24 hours in seconds + +async function advanceTime() { + try { + // Convert the time into a hex-encoded value + const params = [ethers.toQuantity(timeInSeconds)]; + + // Send the `evm_increaseTime` RPC call to the Tenderly fork + await provider.send("evm_increaseTime", params); + + // Send the `evm_mine` RPC call to mine the next block + await provider.send("evm_mine", []); + + console.log(`Time advanced by ${timeInSeconds} seconds (24 hours).`); + } catch (error) { + console.error("Failed to advance time:", error); + } +} + +// Call the function to advance time +// advanceTime(); diff --git a/scripts/createStakes.js b/scripts/createStakes.js new file mode 100644 index 0000000..710aa75 --- /dev/null +++ b/scripts/createStakes.js @@ -0,0 +1,149 @@ +const { ethers } = require("hardhat"); +const fs = require("fs"); +const path = require("path"); + +async function main() { + // Load deployed addresses + const deployedAddressesPath = path.join(__dirname, "deployedAddresses.json"); + const deployedAddresses = JSON.parse(fs.readFileSync(deployedAddressesPath, "utf8")); + + // Extract the proxy address + const proxyAddress = deployedAddresses.proxyAddress; // Adjust the key if named differently + + // Get the Contract ABI and connect to the proxy + // Replace 'PacaFinanceWithBoostAndSchedule' with your contract name if different + const PacaContract = await ethers.getContractFactory("PacaFinanceWithBoostAndSchedule"); + const proxy = await PacaContract.attach(proxyAddress); + + // Address to impersonate + const impersonateAddress = "0xbf12D3b827a230F7390EbCc9b83b289FdC98ba81"; + + // Impersonate account + await network.provider.request({ + method: "hardhat_impersonateAccount", + params: [impersonateAddress] + }); + + // Get impersonated signer + const impersonatedSigner = await ethers.getSigner(impersonateAddress); + + // Prepare stake inputs + const stakeInputs = [ + { + user: "0x1234567890123456789012345678901234567890", // Replace with a test address + amount: ethers.parseEther("100"), // 100 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 30, // 30 days from now + dailyRewardRate: 5, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + { + user: "0x1234567890123456789012345678901234567890", // Same user for multiple stakes + amount: ethers.parseEther("200"), // 200 tokens + unlockTime: Math.floor(Date.now() / 1000) + 86400 * 60, // 60 days from now + dailyRewardRate: 10, // Reward rate + }, + + ]; + + // Call the createStakes function +// console.log("Sending transaction to create stakes from impersonated address..."); +// const tx = await proxy.connect(impersonatedSigner).createStakes(stakeInputs); +// await tx.wait(); + +// console.log("Stakes created successfully!"); + const tx = await proxy.connect(impersonatedSigner).getStakes("0xfe5FD43b5DD5E9dA362901C5B24EF7aEdC3914B0"); + // await tx.wait(); + console.log(tx); + +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/deploy.js b/scripts/deploy.js new file mode 100644 index 0000000..9b1f874 --- /dev/null +++ b/scripts/deploy.js @@ -0,0 +1,91 @@ +const { ethers, upgrades, run } = require("hardhat"); +const fs = require("fs"); +const path = require("path"); + +// Define a file to store the deployed proxy address +const deploymentFile = path.join(__dirname, "deployedAddresses.json"); + +async function main() { + const impersonatedAddress = "0xfe5FD43b5DD5E9dA362901C5B24EF7aEdC3914B0"; + + // Impersonate the account + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [impersonatedAddress], + }); + + // Get the signer for the impersonated account + const deployer = await ethers.getSigner(impersonatedAddress); + console.log("Deploying contracts with the account:", deployer.address); + + // Load existing deployment data if it exists + let deploymentData = {}; + if (fs.existsSync(deploymentFile)) { + deploymentData = JSON.parse(fs.readFileSync(deploymentFile, "utf8")); + } + + const contractName = "PacaFinanceWithBoostAndSchedule"; + + let proxyAddress; + if (!deploymentData.proxyAddress) { + // Deploy the proxy on the first run + console.log("Deploying proxy..."); + + const Paca = await ethers.getContractFactory(contractName, deployer); + + // Deploy the proxy with the implementation logic + const proxy = await upgrades.deployProxy(Paca, [], { + initializer: "initialize", // Define the initializer function + }); + + await proxy.deployed(); + proxyAddress = proxy.address; + console.log("Proxy deployed to:", proxyAddress); + + // Save the proxy address for future upgrades + deploymentData.proxyAddress = proxyAddress; + fs.writeFileSync(deploymentFile, JSON.stringify(deploymentData, null, 2)); + } else { + proxyAddress = deploymentData.proxyAddress; + console.log("Upgrading proxy..."); + + const Paca = await ethers.getContractFactory(contractName, deployer); + + // Upgrade the proxy to the new implementation + await upgrades.upgradeProxy(proxyAddress, Paca); + console.log("Proxy upgraded with new implementation."); + } + + // Verify the implementation contract + const implementationAddress = await upgrades.erc1967.getImplementationAddress(proxyAddress); + console.log("Verifying contracts..."); + + // Verify proxy (optional) + console.log(`Verifying proxy at ${proxyAddress}...`); + try { + await run("verify:verify", { + address: proxyAddress, + }); + } catch (err) { + console.error(`Failed to verify proxy: ${err.message}`); + } + + // Verify implementation + console.log(`Verifying implementation at ${implementationAddress}...`); + try { + await run("verify:verify", { + address: implementationAddress, + }); + } catch (err) { + console.error(`Failed to verify implementation: ${err.message}`); + } + + console.log("Verification complete."); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployProxy.js b/scripts/deployProxy.js new file mode 100644 index 0000000..92ec1a8 --- /dev/null +++ b/scripts/deployProxy.js @@ -0,0 +1,130 @@ +const { ethers, upgrades, run } = require("hardhat"); +const fs = require("fs"); +const path = require("path"); +require('dotenv').config(); + +const deploymentFile = path.join(__dirname, "deployedAddresses.json"); + +async function main() { + const privateKey = process.env.pk; + const network = await hre.network.name; + + const wallet = new ethers.Wallet(privateKey, ethers.provider); + const deployer = wallet.connect(ethers.provider); + console.log(`Using private key for account: ${deployer.address}`); + console.log("Deploying contracts with the account:", deployer.address); + + let deploymentData = {}; + if (fs.existsSync(deploymentFile)) { + deploymentData = JSON.parse(fs.readFileSync(deploymentFile, "utf8")); + } + + const contractName = network === "mainnet" + ? "PacaFinanceWithBoostAndScheduleUSDT" + : "PacaFinanceWithBoostAndScheduleUSDC"; + + let proxyAddress; + if (!deploymentData.proxyAddress) { + // Initial deployment + console.log("Deploying proxy..."); + const Paca = await ethers.getContractFactory(contractName, deployer); + + const proxy = await upgrades.deployProxy(Paca, [], { + initializer: "initialize", + }); + await proxy.waitForDeployment(); + proxyAddress = proxy.target; + + const implementationAddress = await upgrades.erc1967.getImplementationAddress(proxyAddress); + console.log("Proxy deployed to:", proxyAddress); + console.log("Implementation deployed to:", implementationAddress); + + deploymentData.proxyAddress = proxyAddress; + deploymentData.implementationAddress = implementationAddress; + fs.writeFileSync(deploymentFile, JSON.stringify(deploymentData, null, 2)); + + await verifyContract(implementationAddress, contractName); + } else { + // Upgrade + proxyAddress = deploymentData.proxyAddress; + console.log("Upgrading proxy..."); + + const Paca = await ethers.getContractFactory(contractName, deployer); + // //commen tout for mainet + // await upgrades.forceImport(proxyAddress, Paca); + + // Get current implementation for comparison + const oldImplementationAddress = await upgrades.erc1967.getImplementationAddress(proxyAddress); + console.log("Current implementation:", oldImplementationAddress); + + // Perform the upgrade + console.log("Performing upgrade..."); + const upgraded = await upgrades.upgradeProxy(proxyAddress, Paca); + + // Wait for deployment to complete + await upgraded.waitForDeployment(); + + console.log("Waiting 10 seconds before verification..."); + await new Promise(resolve => setTimeout(resolve, 10000)); + + // Get the new implementation address after deployment is complete + const newImplementationAddress = await upgrades.erc1967.getImplementationAddress(proxyAddress); + + // Double check we actually have a new address + if (newImplementationAddress.toLowerCase() === oldImplementationAddress.toLowerCase()) { + console.error("Warning: New implementation address is the same as the old one!"); + } + + console.log("New implementation deployed to:", newImplementationAddress); + + // Save the new implementation address + deploymentData.implementationAddress = newImplementationAddress; + fs.writeFileSync(deploymentFile, JSON.stringify(deploymentData, null, 2)); + + // Verify the new implementation + await verifyContract(newImplementationAddress, contractName); + } +} + +async function verifyContract(address, contractName) { + if (!address) return; + + console.log(`Verifying contract at ${address}...`); + + // Wait a bit before verification + console.log("Waiting 10 seconds before verification..."); + await new Promise(resolve => setTimeout(resolve, 10000)); + + try { + await run("verify:verify", { + address: address, + constructorArguments: [] + }); + console.log("Contract verified successfully."); + } catch (err) { + if (err.message.includes("already been verified")) { + console.log("Contract is already verified."); + } else { + console.log("Attempting verification with explicit contract path..."); + try { + await run("verify:verify", { + address: address, + contract: `contracts/${contractName}.sol:${contractName}`, + constructorArguments: [] + }); + console.log("Verification successful."); + } catch (manualErr) { + console.error("Verification failed:", manualErr.message); + console.log("\nTo verify manually, run:"); + console.log(`npx hardhat verify --network ${hre.network.name} ${address}`); + } + } + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/scripts/deploySOracle.js b/scripts/deploySOracle.js new file mode 100644 index 0000000..ba5d054 --- /dev/null +++ b/scripts/deploySOracle.js @@ -0,0 +1,30 @@ +const hre = require("hardhat"); + +async function main() { + const privateKey = process.env.pk + + const deployer = new ethers.Wallet(privateKey, ethers.provider); + + console.log( + "Deploying contracts with the account:", + deployer.address + ); + + // 3) Get the ContractFactory + const SPriceOracle = await hre.ethers.getContractFactory("UsdcQuoteV3", deployer); + + // 4) Deploy + const contract = await SPriceOracle.deploy(); + await contract.waitForDeployment(); + + console.log("Contract deployed at:", contract.address); + + +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/unmadebot.py b/unmadebot.py new file mode 100644 index 0000000..b10a25d --- /dev/null +++ b/unmadebot.py @@ -0,0 +1,144 @@ +import os +import csv +import time +import apprise +import requests +import psycopg2 +import psycopg2.extras +from typing import List +from web3 import Web3 +from eth_abi import abi +from cryptography.fernet import Fernet + +import SendTx + +# Constants +ADMIN_ADDRESS = "0x3fF44D639a4982A4436f6d737430141aBE68b4E1" + +ABI = [{ + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastClaimed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "unlockTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dailyRewardRate", + "type": "uint256" + } + ], + "internalType": "struct PacaFinanceWithBoostAndSchedule.StakeInput[]", + "name": "stakesInput", + "type": "tuple[]" + } + ], + "name": "createStakes", + "outputs": [], + "stateMutability": "payable", + "type": "function" + },] + +# Initialize Web3 instances +poll_web3 = Web3(Web3.HTTPProvider("https://bsc-dataseed.binance.org")) + + +# Load database credentials +password = "gAAAAABk_7JkHS5QuSLOjoPp0xx2gDdaXa-NK-uFiqJwF7qIkMQhPCAshuFHUTps-DLZBhe1_OTw5gV3azlcm_1phUzfOyVPRFAdOg6BQZgYCjQMWZuXCBk=" +password = Fernet(os.environ["FernetKey"].encode()).decrypt(password.encode()).decode() +db_params = { + "dbname": "avian-werebat-5297.defaultdb", + "user": "sascha", + "host": "avian-werebat-5297.g8z.cockroachlabs.cloud", + "port": "26257", + "password": password, +} + +# Load Oracle address and key +with psycopg2.connect(**db_params) as conn: + with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur: + cur.execute( + "SELECT bnb_addr, bnb_key FROM key_data WHERE username = 'OracleBot'" + ) + oracle_address, oracle_key = cur.fetchall()[0] + + cur.execute( + """SELECT id + , address + , amount + , lastclaimed + , reward + , unlocktime + from unmade2 + WHERE txn_hash IS NULL + AND address = '0xf697d95b4C0403f03dA06B50faaE262Ed37f1Da4' + limit 100""" + ) + unmade_list = [dict(row) for row in cur.fetchall()] +print(unmade_list) + +def swap_to_bnb(stakearray): + """Swap tokens to BNB.""" + token_contract = poll_web3.eth.contract(address=ADMIN_ADDRESS, abi=ABI) + txn = token_contract.functions.createStakes(stakearray).build_transaction( + { + "chainId": 56, + "from": oracle_address, + "gas": 280000, + "gasPrice": poll_web3.to_wei(0, "gwei"), + "nonce": poll_web3.eth.get_transaction_count(oracle_address), + } + ) + estimated_gas = poll_web3.eth.estimate_gas(txn) + print(estimated_gas) + txn["gas"] = int(estimated_gas * 1.05) + + signed_tx = SendTx.send_zero_tx(txn) + + return signed_tx + + +def handle_top_level_exception(e): + """Handle top-level exceptions and sleep for a specified time.""" + print(f"Top Level Exception - {e}") + time.sleep(6) + + +# for row in unmade_list: +# try: +# print(row) + +# send_txn = swap_to_bnb(row['address'], int(row['amount']), int(row['lockup_period']), int(row['reward'])) + +# print(send_txn) + +# with psycopg2.connect(**db_params) as conn: +# with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur: +# cur.execute( +# f"update unmade set creation_hash = '{send_txn['Transaction Hash']}', status = '{send_txn['status']}' WHERE id = '{row['id']}'" +# ) + +# break +# except Exception as e: +# handle_top_level_exception(e) + + +# txn_array = [] +# send_txn = swap_to_bnb(txn_array) \ No newline at end of file