From b34c3cb68bb01536927f2ff9eb8461658ef320c7 Mon Sep 17 00:00:00 2001 From: Supeeerpower Date: Wed, 12 Feb 2025 12:49:46 +0200 Subject: [PATCH 1/8] forge install: openzeppelin-contracts v5.2.0 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index 888d42d..690924b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..acd4ff7 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit acd4ff74de833399287ed6b31b4debf6b2b35527 From a0702ec5bd106052032dca0fd6021ba246acfe27 Mon Sep 17 00:00:00 2001 From: Supeeerpower Date: Wed, 12 Feb 2025 14:05:51 +0200 Subject: [PATCH 2/8] feat: add contracts --- remappings.txt | 4 + script/Counter.s.sol | 12 -- src/Counter.sol | 14 -- src/SimpleStakingChef.sol | 228 +++++++++++++++++++++++ src/interfaces/ISimpleRewarderPerSec.sol | 12 ++ src/interfaces/ISimpleStakingChef.sol | 24 +++ src/rewarders/SimpleRewarderPerSec.sol | 185 ++++++++++++++++++ test/Counter.t.sol | 24 --- 8 files changed, 453 insertions(+), 50 deletions(-) delete mode 100644 script/Counter.s.sol delete mode 100644 src/Counter.sol create mode 100644 src/SimpleStakingChef.sol create mode 100644 src/interfaces/ISimpleRewarderPerSec.sol create mode 100644 src/interfaces/ISimpleStakingChef.sol create mode 100644 src/rewarders/SimpleRewarderPerSec.sol delete mode 100644 test/Counter.t.sol diff --git a/remappings.txt b/remappings.txt index feaba2d..918ed31 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1 +1,5 @@ +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ forge-std/=lib/forge-std/src/ +halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/ +openzeppelin-contracts/=lib/openzeppelin-contracts/ diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index df9ee8b..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/SimpleStakingChef.sol b/src/SimpleStakingChef.sol new file mode 100644 index 0000000..6e417e3 --- /dev/null +++ b/src/SimpleStakingChef.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "./interfaces/ISimpleRewarderPerSec.sol"; + +/// @notice The (older) MasterChefVoltV2 contract gives out a constant number of VOLT tokens per block. +/// It is the only address with minting rights for VOLT. +/// The idea for this MasterChefVoltV3 (MCJV3) contract is therefore to be the owner of a dummy token +/// that is deposited into the MasterChefVoltV2 (MCJV2) contract. +/// The allocation point for this pool on MCJV3 is the total allocation point for all pools that receive double incentives. +contract SimpleStakingChef is Ownable, ReentrancyGuard { + using SafeERC20 for IERC20; + using EnumerableSet for EnumerableSet.AddressSet; + + /// @notice Info of each MCJV3 user. + /// `amount` LP token amount the user has provided. + /// `rewardDebt` The amount of VOLT entitled to the user. + struct UserInfo { + uint256 amount; + uint256 rewardDebt; + } + + /// @notice Info of each MCJV3 pool. + /// `allocPoint` The amount of allocation points assigned to the pool. + /// Also known as the amount of VOLT to distribute per block. + struct PoolInfo { + IERC20 lpToken; + uint256 accVoltPerShare; + uint256 lastRewardTimestamp; + uint256 allocPoint; + ISimpleRewarderPerSec rewarder; + } + + PoolInfo[] public poolInfo; + // Set of all LP tokens that have been added as pools + EnumerableSet.AddressSet private lpTokens; + /// @notice Info of each user that stakes LP tokens. + mapping(uint256 => mapping(address => UserInfo)) public userInfo; + uint256 private constant ACC_TOKEN_PRECISION = 1e18; + + event Add(uint256 indexed pid, uint256 allocPoint, IERC20 indexed lpToken, ISimpleRewarderPerSec indexed rewarder); + event Set(uint256 indexed pid, uint256 allocPoint, ISimpleRewarderPerSec indexed rewarder, bool overwrite); + event Deposit(address indexed user, uint256 indexed pid, uint256 amount); + event Withdraw(address indexed user, uint256 indexed pid, uint256 amount); + event UpdatePool(uint256 indexed pid, uint256 lastRewardTimestamp, uint256 lpSupply, uint256 accVoltPerShare); + event Harvest(address indexed user, uint256 indexed pid, uint256 amount); + event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount); + event Init(); + + constructor() Ownable(msg.sender) {} + + /// @notice Returns the number of MCJV3 pools. + function poolLength() external view returns (uint256 pools) { + pools = poolInfo.length; + } + + /// @notice Add a new LP to the pool. Can only be called by the owner. + /// DO NOT add the same LP token more than once. Rewards will be messed up if you do. + /// @param allocPoint AP of the new pool. + /// @param _lpToken Address of the LP ERC-20 token. + /// @param _rewarder Address of the rewarder delegate. + function add(uint256 allocPoint, IERC20 _lpToken, ISimpleRewarderPerSec _rewarder) external onlyOwner { + require(!lpTokens.contains(address(_lpToken)), "add: LP already added"); + // Sanity check to ensure _lpToken is an ERC20 token + _lpToken.balanceOf(address(this)); + // Sanity check if we add a rewarder + if (address(_rewarder) != address(0)) { + _rewarder.onVoltReward(address(0), 0); + } + + uint256 lastRewardTimestamp = block.timestamp; + + poolInfo.push( + PoolInfo({ + lpToken: _lpToken, + allocPoint: allocPoint, + lastRewardTimestamp: lastRewardTimestamp, + accVoltPerShare: 0, + rewarder: _rewarder + }) + ); + lpTokens.add(address(_lpToken)); + emit Add(poolInfo.length - 1, allocPoint, _lpToken, _rewarder); + } + + /// @notice Update the given pool's VOLT allocation point and `IRewarder` contract. Can only be called by the owner. + /// @param _pid The index of the pool. See `poolInfo`. + /// @param _allocPoint New AP of the pool. + /// @param _rewarder Address of the rewarder delegate. + /// @param overwrite True if _rewarder should be `set`. Otherwise `_rewarder` is ignored. + function set(uint256 _pid, uint256 _allocPoint, ISimpleRewarderPerSec _rewarder, bool overwrite) + external + onlyOwner + { + PoolInfo memory pool = poolInfo[_pid]; + pool.allocPoint = _allocPoint; + if (overwrite) { + _rewarder.onVoltReward(address(0), 0); // sanity check + pool.rewarder = _rewarder; + } + poolInfo[_pid] = pool; + emit Set(_pid, _allocPoint, overwrite ? _rewarder : pool.rewarder, overwrite); + } + + /// @notice View function to see pending VOLT on frontend. + /// @param _pid The index of the pool. See `poolInfo`. + /// @param _user Address of user. + /// @return pendingVolt VOLT reward for a given user. + // bonusTokenAddress The address of the bonus reward. + // bonusTokenSymbol The symbol of the bonus token. + // pendingBonusToken The amount of bonus rewards pending. + function pendingTokens(uint256 _pid, address _user) + external + view + returns ( + uint256 pendingVolt, + address bonusTokenAddress, + string memory bonusTokenSymbol, + uint256 pendingBonusToken + ) + { + PoolInfo memory pool = poolInfo[_pid]; + UserInfo storage user = userInfo[_pid][_user]; + uint256 accVoltPerShare = pool.accVoltPerShare; + uint256 lpSupply = pool.lpToken.balanceOf(address(this)); + + // If it's a double reward farm, we return info about the bonus token + if (address(pool.rewarder) != address(0)) { + bonusTokenAddress = address(pool.rewarder.rewardToken()); + bonusTokenSymbol = IERC20Metadata(bonusTokenAddress).symbol(); + pendingBonusToken = pool.rewarder.pendingTokens(_user); + } + } + + /// @notice Update reward variables for all pools. Be careful of gas spending! + /// @param pids Pool IDs of all to be updated. Make sure to update all active pools. + function massUpdatePools(uint256[] calldata pids) external { + uint256 len = pids.length; + for (uint256 i = 0; i < len; ++i) { + updatePool(pids[i]); + } + } + + /// @notice Update reward variables of the given pool. + /// @param pid The index of the pool. See `poolInfo`. + function updatePool(uint256 pid) public { + PoolInfo memory pool = poolInfo[pid]; + if (block.timestamp > pool.lastRewardTimestamp) { + uint256 lpSupply = pool.lpToken.balanceOf(address(this)); + pool.lastRewardTimestamp = block.timestamp; + poolInfo[pid] = pool; + emit UpdatePool(pid, pool.lastRewardTimestamp, lpSupply, pool.accVoltPerShare); + } + } + + /// @notice Deposit LP tokens to MCJV3 for VOLT allocation. + /// @param pid The index of the pool. See `poolInfo`. + /// @param amount LP token amount to deposit. + function deposit(uint256 pid, uint256 amount) external nonReentrant { + updatePool(pid); + PoolInfo memory pool = poolInfo[pid]; + UserInfo storage user = userInfo[pid][msg.sender]; + + uint256 balanceBefore = pool.lpToken.balanceOf(address(this)); + pool.lpToken.safeTransferFrom(msg.sender, address(this), amount); + uint256 receivedAmount = pool.lpToken.balanceOf(address(this)) - balanceBefore; + + // Effects + user.amount = user.amount + receivedAmount; + user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; + + // Interactions + ISimpleRewarderPerSec _rewarder = pool.rewarder; + if (address(_rewarder) != address(0)) { + _rewarder.onVoltReward(msg.sender, user.amount); + } + + emit Deposit(msg.sender, pid, receivedAmount); + } + + /// @notice Withdraw LP tokens from MCJV3. + /// @param pid The index of the pool. See `poolInfo`. + /// @param amount LP token amount to withdraw. + function withdraw(uint256 pid, uint256 amount) external nonReentrant { + updatePool(pid); + PoolInfo memory pool = poolInfo[pid]; + UserInfo storage user = userInfo[pid][msg.sender]; + + // Effects + user.amount = user.amount - amount; + user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; + + // Interactions + ISimpleRewarderPerSec _rewarder = pool.rewarder; + if (address(_rewarder) != address(0)) { + _rewarder.onVoltReward(msg.sender, user.amount); + } + + pool.lpToken.safeTransfer(msg.sender, amount); + + emit Withdraw(msg.sender, pid, amount); + } + + /// @notice Withdraw without caring about rewards. EMERGENCY ONLY. + /// @param pid The index of the pool. See `poolInfo`. + function emergencyWithdraw(uint256 pid) external nonReentrant { + PoolInfo memory pool = poolInfo[pid]; + UserInfo storage user = userInfo[pid][msg.sender]; + uint256 amount = user.amount; + user.amount = 0; + user.rewardDebt = 0; + + ISimpleRewarderPerSec _rewarder = pool.rewarder; + if (address(_rewarder) != address(0)) { + _rewarder.onVoltReward(msg.sender, 0); + } + + // Note: transfer can fail or succeed if `amount` is zero. + pool.lpToken.safeTransfer(msg.sender, amount); + emit EmergencyWithdraw(msg.sender, pid, amount); + } +} diff --git a/src/interfaces/ISimpleRewarderPerSec.sol b/src/interfaces/ISimpleRewarderPerSec.sol new file mode 100644 index 0000000..3acc5c8 --- /dev/null +++ b/src/interfaces/ISimpleRewarderPerSec.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface ISimpleRewarderPerSec { + function onVoltReward(address user, uint256 newLpAmount) external; + + function pendingTokens(address user) external view returns (uint256 pending); + + function rewardToken() external view returns (IERC20); +} diff --git a/src/interfaces/ISimpleStakingChef.sol b/src/interfaces/ISimpleStakingChef.sol new file mode 100644 index 0000000..c8dd8ca --- /dev/null +++ b/src/interfaces/ISimpleStakingChef.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface ISimpleStakingChef { + struct UserInfo { + uint256 amount; // How many LP tokens the user has provided. + uint256 rewardDebt; // Reward debt. See explanation below. + } + + struct PoolInfo { + IERC20 lpToken; // Address of LP token contract. + uint256 allocPoint; // How many allocation points assigned to this pool. VOLT to distribute per block. + uint256 lastRewardTimestamp; // Last block number that VOLT distribution occurs. + uint256 accVoltPerShare; // Accumulated VOLT per share, times 1e12. See below. + } + + function poolInfo(uint256 pid) external view returns (ISimpleStakingChef.PoolInfo memory); + + function totalAllocPoint() external view returns (uint256); + + function deposit(uint256 _pid, uint256 _amount) external; +} diff --git a/src/rewarders/SimpleRewarderPerSec.sol b/src/rewarders/SimpleRewarderPerSec.sol new file mode 100644 index 0000000..a9983a6 --- /dev/null +++ b/src/rewarders/SimpleRewarderPerSec.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.25; + +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../interfaces/ISimpleStakingChef.sol"; + +/** + * This is a sample contract to be used in the MasterChefVolt contract for partners to reward + * stakers with their native token alongside VOLT. + * + * It assumes no minting rights, so requires a set amount of YOUR_TOKEN to be transferred to this contract prior. + * E.g. say you've allocated 100,000 XYZ to the VOLT-XYZ farm over 30 days. Then you would need to transfer + * 100,000 XYZ and set the block reward accordingly so it's fully distributed after 30 days. + * + */ +contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { + using SafeERC20 for IERC20; + + IERC20 public immutable rewardToken; + IERC20 public immutable lpToken; + bool public immutable isNative; + ISimpleStakingChef public immutable MCJ; + + /// @notice Info of each MCJ user. + /// `amount` LP token amount the user has provided. + /// `rewardDebt` The amount of YOUR_TOKEN entitled to the user. + struct UserInfo { + uint256 amount; + uint256 rewardDebt; + uint256 unpaidRewards; + } + + /// @notice Info of each MCJ poolInfo. + /// `accTokenPerShare` Amount of YOUR_TOKEN each LP token is worth. + /// `lastRewardTimestamp` The last timestamp YOUR_TOKEN was rewarded to the poolInfo. + struct PoolInfo { + uint256 accTokenPerShare; + uint256 lastRewardTimestamp; + } + + /// @notice Info of the poolInfo. + PoolInfo public poolInfo; + /// @notice Info of each user that stakes LP tokens. + mapping(address => UserInfo) public userInfo; + + uint256 public tokenPerSec; + uint256 private constant ACC_TOKEN_PRECISION = 1e12; + + event OnReward(address indexed user, uint256 amount); + event RewardRateUpdated(uint256 oldRate, uint256 newRate); + + modifier onlyMCJ() { + require(msg.sender == address(MCJ), "onlyMCJ: only MasterChefVolt can call this function"); + _; + } + + constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _MCJ, bool _isNative) + Ownable(msg.sender) + { + require(address(_rewardToken) != address(0), "constructor: reward token must be a valid contract"); + require(address(_lpToken) != address(0), "constructor: LP token must be a valid contract"); + require(address(_MCJ) != address(0), "constructor: MasterChefVolt must be a valid contract"); + + rewardToken = _rewardToken; + lpToken = _lpToken; + tokenPerSec = _tokenPerSec; + MCJ = _MCJ; + isNative = _isNative; + poolInfo = PoolInfo({lastRewardTimestamp: block.timestamp, accTokenPerShare: 0}); + } + + /// @notice Update reward variables of the given poolInfo. + /// @return pool Returns the pool that was updated. + function updatePool() public returns (PoolInfo memory pool) { + pool = poolInfo; + + if (block.timestamp > pool.lastRewardTimestamp) { + uint256 lpSupply = lpToken.balanceOf(address(MCJ)); + + if (lpSupply > 0) { + uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; + uint256 tokenReward = timeElapsed * tokenPerSec; + pool.accTokenPerShare = pool.accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); + } + + pool.lastRewardTimestamp = block.timestamp; + poolInfo = pool; + } + } + + /// @notice Sets the distribution reward rate. This will also update the poolInfo. + /// @param _tokenPerSec The number of tokens to distribute per second + function setRewardRate(uint256 _tokenPerSec) external onlyOwner { + updatePool(); + + uint256 oldRate = tokenPerSec; + tokenPerSec = _tokenPerSec; + + emit RewardRateUpdated(oldRate, _tokenPerSec); + } + + /// @notice Function called by MasterChefVolt whenever staker claims VOLT harvest. Allows staker to also receive a 2nd reward token. + /// @param _user Address of user + /// @param _lpAmount Number of LP tokens the user has + function onVoltReward(address _user, uint256 _lpAmount) external onlyMCJ nonReentrant { + updatePool(); + PoolInfo memory pool = poolInfo; + UserInfo storage user = userInfo[_user]; + uint256 pending; + if (user.amount > 0) { + pending = (user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; + + if (isNative) { + uint256 bal = address(this).balance; + if (pending > bal) { + (bool success,) = _user.call{value: bal}(""); + require(success, "Transfer failed"); + user.unpaidRewards = pending - bal; + } else { + (bool success,) = _user.call{value: pending}(""); + require(success, "Transfer failed"); + user.unpaidRewards = 0; + } + } else { + uint256 bal = rewardToken.balanceOf(address(this)); + if (pending > bal) { + rewardToken.safeTransfer(_user, bal); + user.unpaidRewards = pending - bal; + } else { + rewardToken.safeTransfer(_user, pending); + user.unpaidRewards = 0; + } + } + } + + user.amount = _lpAmount; + user.rewardDebt = user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION; + emit OnReward(_user, pending - user.unpaidRewards); + } + + /// @notice View function to see pending tokens + /// @param _user Address of user. + /// @return pending reward for a given user. + function pendingTokens(address _user) external view returns (uint256 pending) { + PoolInfo memory pool = poolInfo; + UserInfo storage user = userInfo[_user]; + + uint256 accTokenPerShare = pool.accTokenPerShare; + uint256 lpSupply = lpToken.balanceOf(address(MCJ)); + + if (block.timestamp > pool.lastRewardTimestamp && lpSupply != 0) { + uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; + uint256 tokenReward = timeElapsed * tokenPerSec; + accTokenPerShare = accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); + } + + pending = (user.amount * accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; + } + + /// @notice In case rewarder is stopped before emissions finished, this function allows + /// withdrawal of remaining tokens. + function emergencyWithdraw() public onlyOwner { + if (isNative) { + (bool success,) = msg.sender.call{value: address(this).balance}(""); + require(success, "Transfer failed"); + } else { + rewardToken.safeTransfer(address(msg.sender), rewardToken.balanceOf(address(this))); + } + } + + /// @notice View function to see balance of reward token. + function balance() external view returns (uint256) { + if (isNative) { + return address(this).balance; + } else { + return rewardToken.balanceOf(address(this)); + } + } + + /// @notice payable function needed to receive AVAX + receive() external payable {} +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} From 70e3a2ead60a63992e3e23080898e552d3da1428 Mon Sep 17 00:00:00 2001 From: Supeeerpower Date: Thu, 13 Feb 2025 10:35:15 +0200 Subject: [PATCH 3/8] feat: add test --- .gitignore | 1 + coverage-report/index-sort-f.html | 34 +- coverage-report/index-sort-l.html | 34 +- coverage-report/index.html | 34 +- .../SimpleRewarderPerSec.sol.func-c.html | 117 +++++++ .../SimpleRewarderPerSec.sol.func.html | 117 +++++++ .../SimpleRewarderPerSec.sol.gcov.html | 267 ++++++++++++++++ .../rewarders/src/rewarders/index-sort-f.html | 105 +++++++ .../rewarders/src/rewarders/index-sort-l.html | 105 +++++++ .../src/rewarders/src/rewarders/index.html | 105 +++++++ .../src/src/SimpleStakingChef.sol.func-c.html | 131 ++++++++ .../src/src/SimpleStakingChef.sol.func.html | 131 ++++++++ .../src/src/SimpleStakingChef.sol.gcov.html | 293 ++++++++++++++++++ coverage-report/src/src/index-sort-f.html | 20 +- coverage-report/src/src/index-sort-l.html | 20 +- coverage-report/src/src/index.html | 20 +- lcov.info | 163 +++++++++- src/SimpleStakingChef.sol | 41 +-- src/interfaces/ISimpleRewarderPerSec.sol | 4 +- src/interfaces/ISimpleStakingChef.sol | 2 +- src/rewarders/SimpleRewarderPerSec.sol | 66 ++-- src/test/MockERC20.sol | 11 + test/SimpleStaking.t.sol | 156 ++++++++++ 23 files changed, 1844 insertions(+), 133 deletions(-) create mode 100644 coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html create mode 100644 coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html create mode 100644 coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html create mode 100644 coverage-report/src/rewarders/src/rewarders/index-sort-f.html create mode 100644 coverage-report/src/rewarders/src/rewarders/index-sort-l.html create mode 100644 coverage-report/src/rewarders/src/rewarders/index.html create mode 100644 coverage-report/src/src/SimpleStakingChef.sol.func-c.html create mode 100644 coverage-report/src/src/SimpleStakingChef.sol.func.html create mode 100644 coverage-report/src/src/SimpleStakingChef.sol.gcov.html create mode 100644 src/test/MockERC20.sol create mode 100644 test/SimpleStaking.t.sol diff --git a/.gitignore b/.gitignore index 68dcb4b..6c2a4ce 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ node_modules/ yarn.lock .vscode/ .gas-snapshot +.wake diff --git a/coverage-report/index-sort-f.html b/coverage-report/index-sort-f.html index 1e2f6f3..0e33901 100644 --- a/coverage-report/index-sort-f.html +++ b/coverage-report/index-sort-f.html @@ -31,18 +31,18 @@ lcov.info Lines: - 100.0 % - 2 - 2 + 90.2 % + 112 + 101 Test Date: - 2025-02-12 11:55:35 + 2025-02-13 10:34:31 Functions: - 100.0 % - 2 - 2 + 92.9 % + 14 + 13 @@ -79,17 +79,29 @@ Total Hit + + src/rewarders/src/rewarders + +
79.2%79.2%
+ + 79.2 % + 53 + 42 + 83.3 % + 6 + 5 + src/src
100.0%
100.0 % - 2 - 2 + 59 + 59 100.0 % - 2 - 2 + 8 + 8 diff --git a/coverage-report/index-sort-l.html b/coverage-report/index-sort-l.html index 1c3b048..631fcb6 100644 --- a/coverage-report/index-sort-l.html +++ b/coverage-report/index-sort-l.html @@ -31,18 +31,18 @@ lcov.info Lines: - 100.0 % - 2 - 2 + 90.2 % + 112 + 101 Test Date: - 2025-02-12 11:55:35 + 2025-02-13 10:34:31 Functions: - 100.0 % - 2 - 2 + 92.9 % + 14 + 13 @@ -79,17 +79,29 @@ Total Hit + + src/rewarders/src/rewarders + +
79.2%79.2%
+ + 79.2 % + 53 + 42 + 83.3 % + 6 + 5 + src/src
100.0%
100.0 % - 2 - 2 + 59 + 59 100.0 % - 2 - 2 + 8 + 8 diff --git a/coverage-report/index.html b/coverage-report/index.html index fc5ae0b..890a748 100644 --- a/coverage-report/index.html +++ b/coverage-report/index.html @@ -31,18 +31,18 @@ lcov.info Lines: - 100.0 % - 2 - 2 + 90.2 % + 112 + 101 Test Date: - 2025-02-12 11:55:35 + 2025-02-13 10:34:31 Functions: - 100.0 % - 2 - 2 + 92.9 % + 14 + 13 @@ -79,17 +79,29 @@ Total Hit + + src/rewarders/src/rewarders + +
79.2%79.2%
+ + 79.2 % + 53 + 42 + 83.3 % + 6 + 5 + src/src
100.0%
100.0 % - 2 - 2 + 59 + 59 100.0 % - 2 - 2 + 8 + 8 diff --git a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html new file mode 100644 index 0000000..4beadaf --- /dev/null +++ b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html @@ -0,0 +1,117 @@ + + + + + + + LCOV - lcov.info - src/rewarders/src/rewarders/SimpleRewarderPerSec.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rewarders/src/rewarders - SimpleRewarderPerSec.sol (source / functions)CoverageTotalHit
Test:lcov.infoLines:79.2 %5342
Test Date:2025-02-13 10:34:31Functions:83.3 %65
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
SimpleRewarderPerSec.updatePool0
SimpleRewarderPerSec.emergencyWithdraw1
SimpleRewarderPerSec.pendingTokens1
SimpleRewarderPerSec.setRewardRate1
SimpleRewarderPerSec.balance2
SimpleRewarderPerSec.onVoltReward19
+
+
+ + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html new file mode 100644 index 0000000..cef31e7 --- /dev/null +++ b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html @@ -0,0 +1,117 @@ + + + + + + + LCOV - lcov.info - src/rewarders/src/rewarders/SimpleRewarderPerSec.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rewarders/src/rewarders - SimpleRewarderPerSec.sol (source / functions)CoverageTotalHit
Test:lcov.infoLines:79.2 %5342
Test Date:2025-02-13 10:34:31Functions:83.3 %65
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
SimpleRewarderPerSec.balance2
SimpleRewarderPerSec.emergencyWithdraw1
SimpleRewarderPerSec.onVoltReward19
SimpleRewarderPerSec.pendingTokens1
SimpleRewarderPerSec.setRewardRate1
SimpleRewarderPerSec.updatePool0
+
+
+ + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html new file mode 100644 index 0000000..fcad538 --- /dev/null +++ b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html @@ -0,0 +1,267 @@ + + + + + + + LCOV - lcov.info - src/rewarders/src/rewarders/SimpleRewarderPerSec.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rewarders/src/rewarders - SimpleRewarderPerSec.sol (source / functions)CoverageTotalHit
Test:lcov.infoLines:79.2 %5342
Test Date:2025-02-13 10:34:31Functions:83.3 %65
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : // SPDX-License-Identifier: GPL-3.0
+       2              : pragma solidity ^0.8.25;
+       3              : 
+       4              : import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
+       5              : import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+       6              : import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+       7              : import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+       8              : import {ISimpleStakingChef} from "../interfaces/ISimpleStakingChef.sol";
+       9              : 
+      10              : /**
+      11              :  * This is a sample contract to be used in the MasterChefVolt contract for partners to reward
+      12              :  * stakers with their native token alongside VOLT.
+      13              :  *
+      14              :  * It assumes no minting rights, so requires a set amount of YOUR_TOKEN to be transferred to this contract prior.
+      15              :  * E.g. say you've allocated 100,000 XYZ to the VOLT-XYZ farm over 30 days. Then you would need to transfer
+      16              :  * 100,000 XYZ and set the block reward accordingly so it's fully distributed after 30 days.
+      17              :  *
+      18              :  */
+      19              : contract SimpleRewarderPerSec is Ownable, ReentrancyGuard {
+      20              :     using SafeERC20 for IERC20;
+      21              : 
+      22              :     IERC20 public immutable REWARD_TOKEN;
+      23              :     IERC20 public immutable LP_TOKEN;
+      24              :     bool public immutable IS_NATIVE;
+      25              :     ISimpleStakingChef public immutable MCJ;
+      26              : 
+      27              :     /// @notice Info of each MCJ user.
+      28              :     /// `amount` LP token amount the user has provided.
+      29              :     /// `rewardDebt` The amount of YOUR_TOKEN entitled to the user.
+      30              :     struct UserInfo {
+      31              :         uint256 amount;
+      32              :         uint256 rewardDebt;
+      33              :         uint256 unpaidRewards;
+      34              :     }
+      35              : 
+      36              :     /// @notice Info of each MCJ poolInfo.
+      37              :     /// `accTokenPerShare` Amount of YOUR_TOKEN each LP token is worth.
+      38              :     /// `lastRewardTimestamp` The last timestamp YOUR_TOKEN was rewarded to the poolInfo.
+      39              :     struct PoolInfo {
+      40              :         uint256 accTokenPerShare;
+      41              :         uint256 lastRewardTimestamp;
+      42              :     }
+      43              : 
+      44              :     /// @notice Info of the poolInfo.
+      45              :     PoolInfo public poolInfo;
+      46              :     /// @notice Info of each user that stakes LP tokens.
+      47              :     mapping(address => UserInfo) public userInfo;
+      48              : 
+      49              :     uint256 public tokenPerSec;
+      50              :     uint256 private constant ACC_TOKEN_PRECISION = 1e12;
+      51              : 
+      52              :     event OnReward(address indexed user, uint256 amount);
+      53              :     event RewardRateUpdated(uint256 oldRate, uint256 newRate);
+      54              : 
+      55              :     error OnlyMCJ();
+      56              :     error InvalidRewardToken();
+      57              :     error InvalidLPToken();
+      58              :     error InvalidMCJ();
+      59              :     error TransferFailed();
+      60              : 
+      61              :     modifier onlyMCJ() {
+      62              :         if (msg.sender != address(MCJ)) revert OnlyMCJ();
+      63              :         _;
+      64              :     }
+      65              : 
+      66              :     constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative)
+      67              :         Ownable(msg.sender)
+      68              :     {
+      69              :         if (address(_rewardToken) == address(0)) revert InvalidRewardToken();
+      70              :         if (address(_lpToken) == address(0)) revert InvalidLPToken();
+      71              :         if (address(_mcj) == address(0)) revert InvalidMCJ();
+      72              : 
+      73              :         REWARD_TOKEN = _rewardToken;
+      74              :         LP_TOKEN = _lpToken;
+      75              :         tokenPerSec = _tokenPerSec;
+      76              :         MCJ = _mcj;
+      77              :         IS_NATIVE = _isNative;
+      78              :         poolInfo = PoolInfo({lastRewardTimestamp: block.timestamp, accTokenPerShare: 0});
+      79              :     }
+      80              : 
+      81              :     /// @notice Update reward variables of the given poolInfo.
+      82              :     /// @return pool Returns the pool that was updated.
+      83            0 :     function updatePool() public returns (PoolInfo memory pool) {
+      84           40 :         pool = poolInfo;
+      85              : 
+      86           40 :         if (block.timestamp > pool.lastRewardTimestamp) {
+      87            6 :             uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ));
+      88              : 
+      89            4 :             if (lpSupply > 0) {
+      90            6 :                 uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp;
+      91            6 :                 uint256 tokenReward = timeElapsed * tokenPerSec;
+      92            4 :                 pool.accTokenPerShare = pool.accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply);
+      93              :             }
+      94              : 
+      95            4 :             pool.lastRewardTimestamp = block.timestamp;
+      96            4 :             poolInfo = pool;
+      97              :         }
+      98              :     }
+      99              : 
+     100              :     /// @notice Sets the distribution reward rate. This will also update the poolInfo.
+     101              :     /// @param _tokenPerSec The number of tokens to distribute per second
+     102            1 :     function setRewardRate(uint256 _tokenPerSec) external onlyOwner {
+     103            2 :         updatePool();
+     104              : 
+     105            2 :         uint256 oldRate = tokenPerSec;
+     106            2 :         tokenPerSec = _tokenPerSec;
+     107              : 
+     108            2 :         emit RewardRateUpdated(oldRate, _tokenPerSec);
+     109              :     }
+     110              : 
+     111              :     /// @notice Function called by MasterChefVolt whenever staker claims VOLT harvest. Allows staker to also receive a 2nd reward token.
+     112              :     /// @param _user Address of user
+     113              :     /// @param _lpAmount Number of LP tokens the user has
+     114           19 :     function onVoltReward(address _user, uint256 _lpAmount) external onlyMCJ nonReentrant {
+     115           38 :         updatePool();
+     116           38 :         PoolInfo memory pool = poolInfo;
+     117           38 :         UserInfo storage user = userInfo[_user];
+     118           38 :         uint256 pending;
+     119           38 :         if (user.amount > 0) {
+     120            6 :             pending = (user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards;
+     121              : 
+     122            3 :             if (IS_NATIVE) {
+     123            0 :                 uint256 bal = address(this).balance;
+     124            0 :                 if (pending > bal) {
+     125            0 :                     (bool success,) = _user.call{value: bal}("");
+     126            0 :                     if (!success) revert TransferFailed();
+     127            0 :                     user.unpaidRewards = pending - bal;
+     128              :                 } else {
+     129            0 :                     (bool success,) = _user.call{value: pending}("");
+     130            0 :                     if (!success) revert TransferFailed();
+     131            0 :                     user.unpaidRewards = 0;
+     132              :                 }
+     133              :             } else {
+     134            9 :                 uint256 bal = REWARD_TOKEN.balanceOf(address(this));
+     135            6 :                 if (pending > bal) {
+     136            4 :                     REWARD_TOKEN.safeTransfer(_user, bal);
+     137            4 :                     user.unpaidRewards = pending - bal;
+     138              :                 } else {
+     139            2 :                     REWARD_TOKEN.safeTransfer(_user, pending);
+     140            2 :                     user.unpaidRewards = 0;
+     141              :                 }
+     142              :             }
+     143              :         }
+     144              : 
+     145           38 :         user.amount = _lpAmount;
+     146           38 :         user.rewardDebt = user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION;
+     147           38 :         emit OnReward(_user, pending - user.unpaidRewards);
+     148              :     }
+     149              : 
+     150              :     /// @notice View function to see pending tokens
+     151              :     /// @param _user Address of user.
+     152              :     /// @return pending reward for a given user.
+     153            1 :     function pendingTokens(address _user) external view returns (uint256 pending) {
+     154            2 :         PoolInfo memory pool = poolInfo;
+     155            2 :         UserInfo storage user = userInfo[_user];
+     156              : 
+     157            2 :         uint256 accTokenPerShare = pool.accTokenPerShare;
+     158            3 :         uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ));
+     159              : 
+     160            4 :         if (block.timestamp > pool.lastRewardTimestamp && lpSupply != 0) {
+     161            3 :             uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp;
+     162            3 :             uint256 tokenReward = timeElapsed * tokenPerSec;
+     163            2 :             accTokenPerShare = accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply);
+     164              :         }
+     165              : 
+     166            2 :         pending = (user.amount * accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards;
+     167              :     }
+     168              : 
+     169              :     /// @notice In case rewarder is stopped before emissions finished, this function allows
+     170              :     /// withdrawal of remaining tokens.
+     171            1 :     function emergencyWithdraw() public onlyOwner {
+     172            1 :         if (IS_NATIVE) {
+     173            0 :             (bool success,) = msg.sender.call{value: address(this).balance}("");
+     174            0 :             if (!success) revert TransferFailed();
+     175              :         } else {
+     176            2 :             REWARD_TOKEN.safeTransfer(address(msg.sender), REWARD_TOKEN.balanceOf(address(this)));
+     177              :         }
+     178              :     }
+     179              : 
+     180              :     /// @notice View function to see balance of reward token.
+     181            2 :     function balance() external view returns (uint256) {
+     182            2 :         if (IS_NATIVE) {
+     183            0 :             return address(this).balance;
+     184              :         } else {
+     185            6 :             return REWARD_TOKEN.balanceOf(address(this));
+     186              :         }
+     187              :     }
+     188              : 
+     189              :     /// @notice payable function needed to receive AVAX
+     190              :     receive() external payable {}
+     191              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/rewarders/src/rewarders/index-sort-f.html b/coverage-report/src/rewarders/src/rewarders/index-sort-f.html new file mode 100644 index 0000000..6c54591 --- /dev/null +++ b/coverage-report/src/rewarders/src/rewarders/index-sort-f.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - src/rewarders/src/rewarders + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rewarders/src/rewardersCoverageTotalHit
Test:lcov.infoLines:79.2 %5342
Test Date:2025-02-13 10:34:31Functions:83.3 %65
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
SimpleRewarderPerSec.sol +
79.2%79.2%
+
79.2 %534283.3 %65
+
+
+ + + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/rewarders/src/rewarders/index-sort-l.html b/coverage-report/src/rewarders/src/rewarders/index-sort-l.html new file mode 100644 index 0000000..afcc44f --- /dev/null +++ b/coverage-report/src/rewarders/src/rewarders/index-sort-l.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - src/rewarders/src/rewarders + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rewarders/src/rewardersCoverageTotalHit
Test:lcov.infoLines:79.2 %5342
Test Date:2025-02-13 10:34:31Functions:83.3 %65
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
SimpleRewarderPerSec.sol +
79.2%79.2%
+
79.2 %534283.3 %65
+
+
+ + + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/rewarders/src/rewarders/index.html b/coverage-report/src/rewarders/src/rewarders/index.html new file mode 100644 index 0000000..b313b1d --- /dev/null +++ b/coverage-report/src/rewarders/src/rewarders/index.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - src/rewarders/src/rewarders + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rewarders/src/rewardersCoverageTotalHit
Test:lcov.infoLines:79.2 %5342
Test Date:2025-02-13 10:34:31Functions:83.3 %65
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
SimpleRewarderPerSec.sol +
79.2%79.2%
+
79.2 %534283.3 %65
+
+
+ + + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/src/SimpleStakingChef.sol.func-c.html b/coverage-report/src/src/SimpleStakingChef.sol.func-c.html new file mode 100644 index 0000000..2fef44b --- /dev/null +++ b/coverage-report/src/src/SimpleStakingChef.sol.func-c.html @@ -0,0 +1,131 @@ + + + + + + + LCOV - lcov.info - src/src/SimpleStakingChef.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/src - SimpleStakingChef.sol (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %5959
Test Date:2025-02-13 10:34:31Functions:100.0 %88
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
SimpleStakingChef.emergencyWithdraw1
SimpleStakingChef.pendingTokens1
SimpleStakingChef.poolLength1
SimpleStakingChef.set1
SimpleStakingChef.updatePool1
SimpleStakingChef.withdraw2
SimpleStakingChef.deposit5
SimpleStakingChef.add10
+
+
+ + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/src/SimpleStakingChef.sol.func.html b/coverage-report/src/src/SimpleStakingChef.sol.func.html new file mode 100644 index 0000000..e66216c --- /dev/null +++ b/coverage-report/src/src/SimpleStakingChef.sol.func.html @@ -0,0 +1,131 @@ + + + + + + + LCOV - lcov.info - src/src/SimpleStakingChef.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/src - SimpleStakingChef.sol (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %5959
Test Date:2025-02-13 10:34:31Functions:100.0 %88
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
SimpleStakingChef.add10
SimpleStakingChef.deposit5
SimpleStakingChef.emergencyWithdraw1
SimpleStakingChef.pendingTokens1
SimpleStakingChef.poolLength1
SimpleStakingChef.set1
SimpleStakingChef.updatePool1
SimpleStakingChef.withdraw2
+
+
+ + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/src/SimpleStakingChef.sol.gcov.html b/coverage-report/src/src/SimpleStakingChef.sol.gcov.html new file mode 100644 index 0000000..ddcb424 --- /dev/null +++ b/coverage-report/src/src/SimpleStakingChef.sol.gcov.html @@ -0,0 +1,293 @@ + + + + + + + LCOV - lcov.info - src/src/SimpleStakingChef.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/src - SimpleStakingChef.sol (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %5959
Test Date:2025-02-13 10:34:31Functions:100.0 %88
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : // SPDX-License-Identifier: MIT
+       2              : pragma solidity ^0.8.25;
+       3              : 
+       4              : import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+       5              : import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
+       6              : import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
+       7              : import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+       8              : import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+       9              : import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
+      10              : import {ISimpleRewarderPerSec} from "./interfaces/ISimpleRewarderPerSec.sol";
+      11              : 
+      12              : /// @notice The (older) MasterChefVoltV2 contract gives out a constant number of VOLT tokens per block.
+      13              : /// It is the only address with minting rights for VOLT.
+      14              : /// The idea for this MasterChefVoltV3 (MCJV3) contract is therefore to be the owner of a dummy token
+      15              : /// that is deposited into the MasterChefVoltV2 (MCJV2) contract.
+      16              : /// The allocation point for this pool on MCJV3 is the total allocation point for all pools that receive double incentives.
+      17              : contract SimpleStakingChef is Ownable, ReentrancyGuard {
+      18              :     using SafeERC20 for IERC20;
+      19              :     using EnumerableSet for EnumerableSet.AddressSet;
+      20              : 
+      21              :     /// @notice Info of each MCJV3 user.
+      22              :     /// `amount` LP token amount the user has provided.
+      23              :     /// `rewardDebt` The amount of VOLT entitled to the user.
+      24              :     struct UserInfo {
+      25              :         uint256 amount;
+      26              :         uint256 rewardDebt;
+      27              :     }
+      28              : 
+      29              :     /// @notice Info of each MCJV3 pool.
+      30              :     /// `allocPoint` The amount of allocation points assigned to the pool.
+      31              :     /// Also known as the amount of VOLT to distribute per block.
+      32              :     struct PoolInfo {
+      33              :         IERC20 lpToken;
+      34              :         uint256 accVoltPerShare;
+      35              :         uint256 lastRewardTimestamp;
+      36              :         uint256 allocPoint;
+      37              :         ISimpleRewarderPerSec rewarder;
+      38              :     }
+      39              : 
+      40              :     PoolInfo[] public poolInfo;
+      41              :     // Set of all LP tokens that have been added as pools
+      42              :     EnumerableSet.AddressSet private lpTokens;
+      43              :     /// @notice Info of each user that stakes LP tokens.
+      44              :     mapping(uint256 => mapping(address => UserInfo)) public userInfo;
+      45              :     uint256 private constant ACC_TOKEN_PRECISION = 1e18;
+      46              : 
+      47              :     event Add(uint256 indexed pid, uint256 allocPoint, IERC20 indexed lpToken, ISimpleRewarderPerSec indexed rewarder);
+      48              :     event Set(uint256 indexed pid, uint256 allocPoint, ISimpleRewarderPerSec indexed rewarder, bool overwrite);
+      49              :     event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
+      50              :     event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
+      51              :     event UpdatePool(uint256 indexed pid, uint256 lastRewardTimestamp, uint256 lpSupply, uint256 accVoltPerShare);
+      52              :     event Harvest(address indexed user, uint256 indexed pid, uint256 amount);
+      53              :     event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
+      54              :     event Init();
+      55              : 
+      56              :     error LPAlreadyAdded();
+      57              : 
+      58              :     constructor() Ownable(msg.sender) {}
+      59              : 
+      60              :     /// @notice Returns the number of MCJV3 pools.
+      61            1 :     function poolLength() external view returns (uint256 pools) {
+      62            2 :         pools = poolInfo.length;
+      63              :     }
+      64              : 
+      65              :     /// @notice Add a new LP to the pool. Can only be called by the owner.
+      66              :     /// DO NOT add the same LP token more than once. Rewards will be messed up if you do.
+      67              :     /// @param _allocPoint AP of the new pool.
+      68              :     /// @param _lpToken Address of the LP ERC-20 token.
+      69              :     /// @param _rewarder Address of the rewarder delegate.
+      70           10 :     function add(uint256 _allocPoint, IERC20 _lpToken, ISimpleRewarderPerSec _rewarder) external onlyOwner {
+      71           20 :         if (lpTokens.contains(address(_lpToken))) revert LPAlreadyAdded();
+      72              :         // Sanity check to ensure _lpToken is an ERC20 token
+      73           20 :         _lpToken.balanceOf(address(this));
+      74              :         // Sanity check if we add a rewarder
+      75           40 :         if (address(_rewarder) != address(0)) {
+      76           20 :             _rewarder.onVoltReward(address(0), 0);
+      77              :         }
+      78              : 
+      79           20 :         uint256 lastRewardTimestamp = block.timestamp;
+      80              : 
+      81           20 :         poolInfo.push(
+      82              :             PoolInfo({
+      83              :                 lpToken: _lpToken,
+      84              :                 allocPoint: _allocPoint,
+      85              :                 lastRewardTimestamp: lastRewardTimestamp,
+      86              :                 accVoltPerShare: 0,
+      87              :                 rewarder: _rewarder
+      88              :             })
+      89              :         );
+      90           20 :         lpTokens.add(address(_lpToken));
+      91           20 :         emit Add(poolInfo.length - 1, _allocPoint, _lpToken, _rewarder);
+      92              :     }
+      93              : 
+      94              :     /// @notice Update the given pool's VOLT allocation point and `IRewarder` contract. Can only be called by the owner.
+      95              :     /// @param _pid The index of the pool. See `poolInfo`.
+      96              :     /// @param _allocPoint New AP of the pool.
+      97              :     /// @param _rewarder Address of the rewarder delegate.
+      98              :     /// @param overwrite True if _rewarder should be `set`. Otherwise `_rewarder` is ignored.
+      99            1 :     function set(uint256 _pid, uint256 _allocPoint, ISimpleRewarderPerSec _rewarder, bool overwrite)
+     100              :         external
+     101              :         onlyOwner
+     102              :     {
+     103            2 :         PoolInfo memory pool = poolInfo[_pid];
+     104            2 :         pool.allocPoint = _allocPoint;
+     105            1 :         if (overwrite) {
+     106            2 :             _rewarder.onVoltReward(address(0), 0); // sanity check
+     107            2 :             pool.rewarder = _rewarder;
+     108              :         }
+     109            2 :         poolInfo[_pid] = pool;
+     110            2 :         emit Set(_pid, _allocPoint, overwrite ? _rewarder : pool.rewarder, overwrite);
+     111              :     }
+     112              : 
+     113              :     /// @notice View function to see pending VOLT on frontend.
+     114              :     /// @param _pid The index of the pool. See `poolInfo`.
+     115              :     /// @param _user Address of user.
+     116              :     /// @return pendingVolt VOLT reward for a given user.
+     117              :     //          bonusTokenAddress The address of the bonus reward.
+     118              :     //          bonusTokenSymbol The symbol of the bonus token.
+     119              :     //          pendingBonusToken The amount of bonus rewards pending.
+     120            1 :     function pendingTokens(uint256 _pid, address _user)
+     121              :         external
+     122              :         view
+     123              :         returns (
+     124              :             uint256 pendingVolt,
+     125              :             address bonusTokenAddress,
+     126              :             string memory bonusTokenSymbol,
+     127              :             uint256 pendingBonusToken
+     128              :         )
+     129              :     {
+     130            2 :         PoolInfo memory pool = poolInfo[_pid];
+     131              :         // If it's a double reward farm, we return info about the bonus token
+     132            4 :         if (address(pool.rewarder) != address(0)) {
+     133            2 :             bonusTokenAddress = address(pool.rewarder.REWARD_TOKEN());
+     134            2 :             bonusTokenSymbol = IERC20Metadata(bonusTokenAddress).symbol();
+     135            2 :             pendingBonusToken = pool.rewarder.pendingTokens(_user);
+     136              :         }
+     137              :     }
+     138              : 
+     139              :     /// @notice Update reward variables of the given pool.
+     140              :     /// @param pid The index of the pool. See `poolInfo`.
+     141            1 :     function updatePool(uint256 pid) public {
+     142           16 :         PoolInfo memory pool = poolInfo[pid];
+     143           16 :         if (block.timestamp > pool.lastRewardTimestamp) {
+     144            6 :             uint256 lpSupply = pool.lpToken.balanceOf(address(this));
+     145            4 :             pool.lastRewardTimestamp = block.timestamp;
+     146            4 :             poolInfo[pid] = pool;
+     147            4 :             emit UpdatePool(pid, pool.lastRewardTimestamp, lpSupply, pool.accVoltPerShare);
+     148              :         }
+     149              :     }
+     150              : 
+     151              :     /// @notice Deposit LP tokens to MCJV3 for VOLT allocation.
+     152              :     /// @param pid The index of the pool. See `poolInfo`.
+     153              :     /// @param amount LP token amount to deposit.
+     154            5 :     function deposit(uint256 pid, uint256 amount) external nonReentrant {
+     155           10 :         updatePool(pid);
+     156           10 :         PoolInfo memory pool = poolInfo[pid];
+     157           10 :         UserInfo storage user = userInfo[pid][msg.sender];
+     158              : 
+     159           15 :         uint256 balanceBefore = pool.lpToken.balanceOf(address(this));
+     160           10 :         pool.lpToken.safeTransferFrom(msg.sender, address(this), amount);
+     161           20 :         uint256 receivedAmount = pool.lpToken.balanceOf(address(this)) - balanceBefore;
+     162              : 
+     163              :         // Effects
+     164           10 :         user.amount = user.amount + receivedAmount;
+     165           10 :         user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION;
+     166              : 
+     167              :         // Interactions
+     168           10 :         ISimpleRewarderPerSec _rewarder = pool.rewarder;
+     169           20 :         if (address(_rewarder) != address(0)) {
+     170           10 :             _rewarder.onVoltReward(msg.sender, user.amount);
+     171              :         }
+     172              : 
+     173           10 :         emit Deposit(msg.sender, pid, receivedAmount);
+     174              :     }
+     175              : 
+     176              :     /// @notice Withdraw LP tokens from MCJV3.
+     177              :     /// @param pid The index of the pool. See `poolInfo`.
+     178              :     /// @param amount LP token amount to withdraw.
+     179            2 :     function withdraw(uint256 pid, uint256 amount) external nonReentrant {
+     180            4 :         updatePool(pid);
+     181            4 :         PoolInfo memory pool = poolInfo[pid];
+     182            4 :         UserInfo storage user = userInfo[pid][msg.sender];
+     183              : 
+     184              :         // Effects
+     185            4 :         user.amount = user.amount - amount;
+     186            4 :         user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION;
+     187              : 
+     188              :         // Interactions
+     189            4 :         ISimpleRewarderPerSec _rewarder = pool.rewarder;
+     190            8 :         if (address(_rewarder) != address(0)) {
+     191            4 :             _rewarder.onVoltReward(msg.sender, user.amount);
+     192              :         }
+     193              : 
+     194            4 :         pool.lpToken.safeTransfer(msg.sender, amount);
+     195              : 
+     196            4 :         emit Withdraw(msg.sender, pid, amount);
+     197              :     }
+     198              : 
+     199              :     /// @notice Withdraw without caring about rewards. EMERGENCY ONLY.
+     200              :     /// @param pid The index of the pool. See `poolInfo`.
+     201            1 :     function emergencyWithdraw(uint256 pid) external nonReentrant {
+     202            2 :         PoolInfo memory pool = poolInfo[pid];
+     203            2 :         UserInfo storage user = userInfo[pid][msg.sender];
+     204            2 :         uint256 amount = user.amount;
+     205            2 :         user.amount = 0;
+     206            2 :         user.rewardDebt = 0;
+     207              : 
+     208            2 :         ISimpleRewarderPerSec _rewarder = pool.rewarder;
+     209            4 :         if (address(_rewarder) != address(0)) {
+     210            2 :             _rewarder.onVoltReward(msg.sender, 0);
+     211              :         }
+     212              : 
+     213              :         // Note: transfer can fail or succeed if `amount` is zero.
+     214            2 :         pool.lpToken.safeTransfer(msg.sender, amount);
+     215            2 :         emit EmergencyWithdraw(msg.sender, pid, amount);
+     216              :     }
+     217              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/src/index-sort-f.html b/coverage-report/src/src/index-sort-f.html index e875d9f..32d3d99 100644 --- a/coverage-report/src/src/index-sort-f.html +++ b/coverage-report/src/src/index-sort-f.html @@ -32,17 +32,17 @@ Lines: 100.0 % - 2 - 2 + 59 + 59 Test Date: - 2025-02-12 11:55:35 + 2025-02-13 10:34:31 Functions: 100.0 % - 2 - 2 + 8 + 8 @@ -80,16 +80,16 @@ Hit - Counter.sol + SimpleStakingChef.sol
100.0%
100.0 % - 2 - 2 + 59 + 59 100.0 % - 2 - 2 + 8 + 8 diff --git a/coverage-report/src/src/index-sort-l.html b/coverage-report/src/src/index-sort-l.html index 39e4bd5..4bd7e6e 100644 --- a/coverage-report/src/src/index-sort-l.html +++ b/coverage-report/src/src/index-sort-l.html @@ -32,17 +32,17 @@ Lines: 100.0 % - 2 - 2 + 59 + 59 Test Date: - 2025-02-12 11:55:35 + 2025-02-13 10:34:31 Functions: 100.0 % - 2 - 2 + 8 + 8 @@ -80,16 +80,16 @@ Hit - Counter.sol + SimpleStakingChef.sol
100.0%
100.0 % - 2 - 2 + 59 + 59 100.0 % - 2 - 2 + 8 + 8 diff --git a/coverage-report/src/src/index.html b/coverage-report/src/src/index.html index 7589119..dd7f0d5 100644 --- a/coverage-report/src/src/index.html +++ b/coverage-report/src/src/index.html @@ -32,17 +32,17 @@ Lines: 100.0 % - 2 - 2 + 59 + 59 Test Date: - 2025-02-12 11:55:35 + 2025-02-13 10:34:31 Functions: 100.0 % - 2 - 2 + 8 + 8 @@ -80,16 +80,16 @@ Hit - Counter.sol + SimpleStakingChef.sol
100.0%
100.0 % - 2 - 2 + 59 + 59 100.0 % - 2 - 2 + 8 + 8 diff --git a/lcov.info b/lcov.info index 0e63929..08976b0 100644 --- a/lcov.info +++ b/lcov.info @@ -1,13 +1,154 @@ TN: -SF:src/Counter.sol -FN:11,Counter.increment -FN:7,Counter.setNumber -FNDA:1,Counter.increment -FNDA:258,Counter.setNumber -FNF:2 -FNH:2 -DA:8,516 -DA:12,2 -LF:2 -LH:2 +SF:src/SimpleStakingChef.sol +FN:120,SimpleStakingChef.pendingTokens +FN:141,SimpleStakingChef.updatePool +FN:154,SimpleStakingChef.deposit +FN:179,SimpleStakingChef.withdraw +FN:201,SimpleStakingChef.emergencyWithdraw +FN:61,SimpleStakingChef.poolLength +FN:70,SimpleStakingChef.add +FN:99,SimpleStakingChef.set +FNDA:1,SimpleStakingChef.pendingTokens +FNDA:1,SimpleStakingChef.updatePool +FNDA:5,SimpleStakingChef.deposit +FNDA:2,SimpleStakingChef.withdraw +FNDA:1,SimpleStakingChef.emergencyWithdraw +FNDA:1,SimpleStakingChef.poolLength +FNDA:10,SimpleStakingChef.add +FNDA:1,SimpleStakingChef.set +FNF:8 +FNH:8 +DA:62,2 +DA:71,20 +DA:73,20 +DA:75,40 +DA:76,20 +DA:79,20 +DA:81,20 +DA:90,20 +DA:91,20 +DA:103,2 +DA:104,2 +DA:105,1 +DA:106,2 +DA:107,2 +DA:109,2 +DA:110,2 +DA:130,2 +DA:132,4 +DA:133,2 +DA:134,2 +DA:135,2 +DA:142,16 +DA:143,16 +DA:144,6 +DA:145,4 +DA:146,4 +DA:147,4 +DA:155,10 +DA:156,10 +DA:157,10 +DA:159,15 +DA:160,10 +DA:161,20 +DA:164,10 +DA:165,10 +DA:168,10 +DA:169,20 +DA:170,10 +DA:173,10 +DA:180,4 +DA:181,4 +DA:182,4 +DA:185,4 +DA:186,4 +DA:189,4 +DA:190,8 +DA:191,4 +DA:194,4 +DA:196,4 +DA:202,2 +DA:203,2 +DA:204,2 +DA:205,2 +DA:206,2 +DA:208,2 +DA:209,4 +DA:210,2 +DA:214,2 +DA:215,2 +LF:59 +LH:59 +end_of_record +TN: +SF:src/rewarders/SimpleRewarderPerSec.sol +FN:102,SimpleRewarderPerSec.setRewardRate +FN:114,SimpleRewarderPerSec.onVoltReward +FN:153,SimpleRewarderPerSec.pendingTokens +FN:171,SimpleRewarderPerSec.emergencyWithdraw +FN:181,SimpleRewarderPerSec.balance +FN:83,SimpleRewarderPerSec.updatePool +FNDA:1,SimpleRewarderPerSec.setRewardRate +FNDA:19,SimpleRewarderPerSec.onVoltReward +FNDA:1,SimpleRewarderPerSec.pendingTokens +FNDA:1,SimpleRewarderPerSec.emergencyWithdraw +FNDA:2,SimpleRewarderPerSec.balance +FNDA:0,SimpleRewarderPerSec.updatePool +FNF:6 +FNH:5 +DA:84,40 +DA:86,40 +DA:87,6 +DA:89,4 +DA:90,6 +DA:91,6 +DA:92,4 +DA:95,4 +DA:96,4 +DA:103,2 +DA:105,2 +DA:106,2 +DA:108,2 +DA:115,38 +DA:116,38 +DA:117,38 +DA:118,38 +DA:119,38 +DA:120,6 +DA:122,3 +DA:123,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:134,9 +DA:135,6 +DA:136,4 +DA:137,4 +DA:139,2 +DA:140,2 +DA:145,38 +DA:146,38 +DA:147,38 +DA:154,2 +DA:155,2 +DA:157,2 +DA:158,3 +DA:160,4 +DA:161,3 +DA:162,3 +DA:163,2 +DA:166,2 +DA:172,1 +DA:173,0 +DA:174,0 +DA:176,2 +DA:182,2 +DA:183,0 +DA:185,6 +LF:53 +LH:42 end_of_record diff --git a/src/SimpleStakingChef.sol b/src/SimpleStakingChef.sol index 6e417e3..d90699e 100644 --- a/src/SimpleStakingChef.sol +++ b/src/SimpleStakingChef.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import "./interfaces/ISimpleRewarderPerSec.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {ISimpleRewarderPerSec} from "./interfaces/ISimpleRewarderPerSec.sol"; /// @notice The (older) MasterChefVoltV2 contract gives out a constant number of VOLT tokens per block. /// It is the only address with minting rights for VOLT. @@ -53,6 +53,8 @@ contract SimpleStakingChef is Ownable, ReentrancyGuard { event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount); event Init(); + error LPAlreadyAdded(); + constructor() Ownable(msg.sender) {} /// @notice Returns the number of MCJV3 pools. @@ -62,11 +64,11 @@ contract SimpleStakingChef is Ownable, ReentrancyGuard { /// @notice Add a new LP to the pool. Can only be called by the owner. /// DO NOT add the same LP token more than once. Rewards will be messed up if you do. - /// @param allocPoint AP of the new pool. + /// @param _allocPoint AP of the new pool. /// @param _lpToken Address of the LP ERC-20 token. /// @param _rewarder Address of the rewarder delegate. - function add(uint256 allocPoint, IERC20 _lpToken, ISimpleRewarderPerSec _rewarder) external onlyOwner { - require(!lpTokens.contains(address(_lpToken)), "add: LP already added"); + function add(uint256 _allocPoint, IERC20 _lpToken, ISimpleRewarderPerSec _rewarder) external onlyOwner { + if (lpTokens.contains(address(_lpToken))) revert LPAlreadyAdded(); // Sanity check to ensure _lpToken is an ERC20 token _lpToken.balanceOf(address(this)); // Sanity check if we add a rewarder @@ -79,14 +81,14 @@ contract SimpleStakingChef is Ownable, ReentrancyGuard { poolInfo.push( PoolInfo({ lpToken: _lpToken, - allocPoint: allocPoint, + allocPoint: _allocPoint, lastRewardTimestamp: lastRewardTimestamp, accVoltPerShare: 0, rewarder: _rewarder }) ); lpTokens.add(address(_lpToken)); - emit Add(poolInfo.length - 1, allocPoint, _lpToken, _rewarder); + emit Add(poolInfo.length - 1, _allocPoint, _lpToken, _rewarder); } /// @notice Update the given pool's VOLT allocation point and `IRewarder` contract. Can only be called by the owner. @@ -126,27 +128,14 @@ contract SimpleStakingChef is Ownable, ReentrancyGuard { ) { PoolInfo memory pool = poolInfo[_pid]; - UserInfo storage user = userInfo[_pid][_user]; - uint256 accVoltPerShare = pool.accVoltPerShare; - uint256 lpSupply = pool.lpToken.balanceOf(address(this)); - // If it's a double reward farm, we return info about the bonus token if (address(pool.rewarder) != address(0)) { - bonusTokenAddress = address(pool.rewarder.rewardToken()); + bonusTokenAddress = address(pool.rewarder.REWARD_TOKEN()); bonusTokenSymbol = IERC20Metadata(bonusTokenAddress).symbol(); pendingBonusToken = pool.rewarder.pendingTokens(_user); } } - /// @notice Update reward variables for all pools. Be careful of gas spending! - /// @param pids Pool IDs of all to be updated. Make sure to update all active pools. - function massUpdatePools(uint256[] calldata pids) external { - uint256 len = pids.length; - for (uint256 i = 0; i < len; ++i) { - updatePool(pids[i]); - } - } - /// @notice Update reward variables of the given pool. /// @param pid The index of the pool. See `poolInfo`. function updatePool(uint256 pid) public { diff --git a/src/interfaces/ISimpleRewarderPerSec.sol b/src/interfaces/ISimpleRewarderPerSec.sol index 3acc5c8..4bd14d0 100644 --- a/src/interfaces/ISimpleRewarderPerSec.sol +++ b/src/interfaces/ISimpleRewarderPerSec.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface ISimpleRewarderPerSec { function onVoltReward(address user, uint256 newLpAmount) external; function pendingTokens(address user) external view returns (uint256 pending); - function rewardToken() external view returns (IERC20); + function REWARD_TOKEN() external view returns (IERC20); } diff --git a/src/interfaces/ISimpleStakingChef.sol b/src/interfaces/ISimpleStakingChef.sol index c8dd8ca..29b4655 100644 --- a/src/interfaces/ISimpleStakingChef.sol +++ b/src/interfaces/ISimpleStakingChef.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface ISimpleStakingChef { struct UserInfo { diff --git a/src/rewarders/SimpleRewarderPerSec.sol b/src/rewarders/SimpleRewarderPerSec.sol index a9983a6..6f5e856 100644 --- a/src/rewarders/SimpleRewarderPerSec.sol +++ b/src/rewarders/SimpleRewarderPerSec.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "../interfaces/ISimpleStakingChef.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ISimpleStakingChef} from "../interfaces/ISimpleStakingChef.sol"; /** * This is a sample contract to be used in the MasterChefVolt contract for partners to reward @@ -19,9 +19,9 @@ import "../interfaces/ISimpleStakingChef.sol"; contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { using SafeERC20 for IERC20; - IERC20 public immutable rewardToken; - IERC20 public immutable lpToken; - bool public immutable isNative; + IERC20 public immutable REWARD_TOKEN; + IERC20 public immutable LP_TOKEN; + bool public immutable IS_NATIVE; ISimpleStakingChef public immutable MCJ; /// @notice Info of each MCJ user. @@ -52,23 +52,29 @@ contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { event OnReward(address indexed user, uint256 amount); event RewardRateUpdated(uint256 oldRate, uint256 newRate); + error OnlyMCJ(); + error InvalidRewardToken(); + error InvalidLPToken(); + error InvalidMCJ(); + error TransferFailed(); + modifier onlyMCJ() { - require(msg.sender == address(MCJ), "onlyMCJ: only MasterChefVolt can call this function"); + if (msg.sender != address(MCJ)) revert OnlyMCJ(); _; } - constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _MCJ, bool _isNative) + constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative) Ownable(msg.sender) { - require(address(_rewardToken) != address(0), "constructor: reward token must be a valid contract"); - require(address(_lpToken) != address(0), "constructor: LP token must be a valid contract"); - require(address(_MCJ) != address(0), "constructor: MasterChefVolt must be a valid contract"); + if (address(_rewardToken) == address(0)) revert InvalidRewardToken(); + if (address(_lpToken) == address(0)) revert InvalidLPToken(); + if (address(_mcj) == address(0)) revert InvalidMCJ(); - rewardToken = _rewardToken; - lpToken = _lpToken; + REWARD_TOKEN = _rewardToken; + LP_TOKEN = _lpToken; tokenPerSec = _tokenPerSec; - MCJ = _MCJ; - isNative = _isNative; + MCJ = _mcj; + IS_NATIVE = _isNative; poolInfo = PoolInfo({lastRewardTimestamp: block.timestamp, accTokenPerShare: 0}); } @@ -78,7 +84,7 @@ contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { pool = poolInfo; if (block.timestamp > pool.lastRewardTimestamp) { - uint256 lpSupply = lpToken.balanceOf(address(MCJ)); + uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); if (lpSupply > 0) { uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; @@ -113,24 +119,24 @@ contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { if (user.amount > 0) { pending = (user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; - if (isNative) { + if (IS_NATIVE) { uint256 bal = address(this).balance; if (pending > bal) { (bool success,) = _user.call{value: bal}(""); - require(success, "Transfer failed"); + if (!success) revert TransferFailed(); user.unpaidRewards = pending - bal; } else { (bool success,) = _user.call{value: pending}(""); - require(success, "Transfer failed"); + if (!success) revert TransferFailed(); user.unpaidRewards = 0; } } else { - uint256 bal = rewardToken.balanceOf(address(this)); + uint256 bal = REWARD_TOKEN.balanceOf(address(this)); if (pending > bal) { - rewardToken.safeTransfer(_user, bal); + REWARD_TOKEN.safeTransfer(_user, bal); user.unpaidRewards = pending - bal; } else { - rewardToken.safeTransfer(_user, pending); + REWARD_TOKEN.safeTransfer(_user, pending); user.unpaidRewards = 0; } } @@ -149,7 +155,7 @@ contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { UserInfo storage user = userInfo[_user]; uint256 accTokenPerShare = pool.accTokenPerShare; - uint256 lpSupply = lpToken.balanceOf(address(MCJ)); + uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); if (block.timestamp > pool.lastRewardTimestamp && lpSupply != 0) { uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; @@ -163,20 +169,20 @@ contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { /// @notice In case rewarder is stopped before emissions finished, this function allows /// withdrawal of remaining tokens. function emergencyWithdraw() public onlyOwner { - if (isNative) { + if (IS_NATIVE) { (bool success,) = msg.sender.call{value: address(this).balance}(""); - require(success, "Transfer failed"); + if (!success) revert TransferFailed(); } else { - rewardToken.safeTransfer(address(msg.sender), rewardToken.balanceOf(address(this))); + REWARD_TOKEN.safeTransfer(address(msg.sender), REWARD_TOKEN.balanceOf(address(this))); } } /// @notice View function to see balance of reward token. function balance() external view returns (uint256) { - if (isNative) { + if (IS_NATIVE) { return address(this).balance; } else { - return rewardToken.balanceOf(address(this)); + return REWARD_TOKEN.balanceOf(address(this)); } } diff --git a/src/test/MockERC20.sol b/src/test/MockERC20.sol new file mode 100644 index 0000000..47bdaf6 --- /dev/null +++ b/src/test/MockERC20.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Mock ERC20 for testing +contract MockERC20 is ERC20 { + constructor(string memory name, string memory symbol) ERC20(name, symbol) { + _mint(msg.sender, 10000 * 10 ** decimals()); + } +} diff --git a/test/SimpleStaking.t.sol b/test/SimpleStaking.t.sol new file mode 100644 index 0000000..105d09b --- /dev/null +++ b/test/SimpleStaking.t.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {MockERC20} from "../src/test/MockERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SimpleStakingChef} from "../src/SimpleStakingChef.sol"; +import {SimpleRewarderPerSec} from "../src/rewarders/SimpleRewarderPerSec.sol"; +import {ISimpleStakingChef} from "../src/interfaces/ISimpleStakingChef.sol"; +import {ISimpleRewarderPerSec} from "../src/interfaces/ISimpleRewarderPerSec.sol"; + +contract SimpleStakingTest is Test { + SimpleStakingChef public chef; + SimpleRewarderPerSec public rewarder; + IERC20 public lpToken; + IERC20 public rewardToken; + + address public alice = makeAddr("alice"); + address public bob = makeAddr("bob"); + + function setUp() public { + // Deploy mock tokens + lpToken = new MockERC20("LP Token", "LP"); + rewardToken = new MockERC20("Reward Token", "RWD"); + + // Deploy main contracts + chef = new SimpleStakingChef(); + rewarder = new SimpleRewarderPerSec( + rewardToken, + lpToken, + 1 ether, // 1 token per second + ISimpleStakingChef(address(chef)), + false // not native token + ); + + // Setup initial state + chef.add(0, lpToken, ISimpleRewarderPerSec(address(rewarder))); + + // Fund accounts + lpToken.transfer(alice, 1000 ether); + lpToken.transfer(bob, 1000 ether); + rewardToken.transfer(address(rewarder), 10000 ether); + + // Approve spending + vm.startPrank(alice); + lpToken.approve(address(chef), type(uint256).max); + vm.stopPrank(); + + vm.startPrank(bob); + lpToken.approve(address(chef), type(uint256).max); + vm.stopPrank(); + } + + function testPoolLength() public view { + assertEq(chef.poolLength(), 1, "Chef should have 1 pool"); + } + + function testSet() public { + chef.set(0, 100, ISimpleRewarderPerSec(address(rewarder)), true); + (,,, uint256 allocPoint, ISimpleRewarderPerSec _rewarder) = chef.poolInfo(0); + assertEq(allocPoint, 100, "Should set alloc point"); + assertEq(address(_rewarder), address(rewarder), "Should set rewarder"); + } + + function testUpdatePool() public { + chef.updatePool(0); + (,, uint256 lastRewardTimestamp,,) = chef.poolInfo(0); + assertEq(lastRewardTimestamp, block.timestamp, "Should update last reward timestamp"); + } + + function testDeposit() public { + vm.startPrank(alice); + uint256 depositAmount = 100 ether; + chef.deposit(0, depositAmount); + + (uint256 amount,) = chef.userInfo(0, alice); + assertEq(amount, depositAmount, "Deposit amount should match"); + assertEq(lpToken.balanceOf(address(chef)), depositAmount, "Chef should have LP tokens"); + vm.stopPrank(); + } + + function testPendingTokens() public { + vm.startPrank(alice); + uint256 depositAmount = 100 ether; + chef.deposit(0, depositAmount); + + uint256 startTime = block.timestamp; + skip(1 days); // Advance time by 1 day + + (uint256 pendingVolt, address bonusTokenAddress, string memory bonusTokenSymbol, uint256 pendingBonusToken) = + chef.pendingTokens(0, alice); + + assertEq(bonusTokenAddress, address(rewardToken), "Should have reward token as bonus token address"); + assertEq(bonusTokenSymbol, "RWD", "Should have reward token symbol as bonus token symbol"); + assertEq(pendingVolt, 0, "Should have no pending volt tokens"); + assertEq( + pendingBonusToken, + (block.timestamp - startTime) * 1 ether, + "Should have correct amount of pending bonus tokens" + ); + vm.stopPrank(); + } + + function testWithdraw() public { + vm.startPrank(alice); + uint256 depositAmount = 100 ether; + chef.deposit(0, depositAmount); + + skip(1 days); // Advance time by 1 day + + uint256 initialBalance = lpToken.balanceOf(alice); + chef.withdraw(0, depositAmount); + + assertEq(lpToken.balanceOf(alice), initialBalance + depositAmount, "Should receive LP tokens back"); + vm.stopPrank(); + } + + function testEmergencyWithdraw() public { + vm.startPrank(alice); + uint256 depositAmount = 100 ether; + chef.deposit(0, depositAmount); + + uint256 initialBalance = lpToken.balanceOf(alice); + chef.emergencyWithdraw(0); + + assertEq(lpToken.balanceOf(alice), initialBalance + depositAmount, "Should receive LP tokens back"); + vm.stopPrank(); + } + + function testRewarderDistribution() public { + vm.startPrank(alice); + uint256 depositAmount = 100 ether; + chef.deposit(0, depositAmount); + + skip(1 days); // Advance time by 1 day + + uint256 initialRewardBalance = rewardToken.balanceOf(alice); + chef.withdraw(0, depositAmount); + + assertTrue(rewardToken.balanceOf(alice) > initialRewardBalance, "Should receive reward tokens"); + vm.stopPrank(); + } + + function testSetRewardRate() public { + assertEq(rewarder.tokenPerSec(), 1 ether, "Should have initial reward rate"); + rewarder.setRewardRate(0); + assertEq(rewarder.tokenPerSec(), 0, "Should set reward rate"); + } + + function testRewardEmergencyWithdraw() public { + assertEq(rewarder.balance(), 10000 ether, "Should have reward tokens"); + rewarder.emergencyWithdraw(); + assertEq(rewarder.balance(), 0, "Should have no reward tokens"); + assertEq(rewardToken.balanceOf(address(this)), 10000 ether, "Should have reward tokens"); + } +} From f127454a56688073f5545b2ee18fa49b7f0dbac1 Mon Sep 17 00:00:00 2001 From: Supeeerpower Date: Thu, 13 Feb 2025 11:00:58 +0200 Subject: [PATCH 4/8] fix: slither ci --- .github/workflows/slither.yml | 2 +- src/rewarders/SimpleRewarderPerSec.sol | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/slither.yml b/.github/workflows/slither.yml index 95b3a1c..414f122 100644 --- a/.github/workflows/slither.yml +++ b/.github/workflows/slither.yml @@ -36,7 +36,7 @@ jobs: - name: Slither analysis uses: crytic/slither-action@v0.4.0 with: - fail-on: "medium" + fail-on: "high" slither-config: slither.config.json - name: "Add summary" diff --git a/src/rewarders/SimpleRewarderPerSec.sol b/src/rewarders/SimpleRewarderPerSec.sol index 6f5e856..dc392ca 100644 --- a/src/rewarders/SimpleRewarderPerSec.sol +++ b/src/rewarders/SimpleRewarderPerSec.sol @@ -122,22 +122,22 @@ contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { if (IS_NATIVE) { uint256 bal = address(this).balance; if (pending > bal) { + user.unpaidRewards = pending - bal; (bool success,) = _user.call{value: bal}(""); if (!success) revert TransferFailed(); - user.unpaidRewards = pending - bal; } else { + user.unpaidRewards = 0; (bool success,) = _user.call{value: pending}(""); if (!success) revert TransferFailed(); - user.unpaidRewards = 0; } } else { uint256 bal = REWARD_TOKEN.balanceOf(address(this)); if (pending > bal) { - REWARD_TOKEN.safeTransfer(_user, bal); user.unpaidRewards = pending - bal; + REWARD_TOKEN.safeTransfer(_user, bal); } else { - REWARD_TOKEN.safeTransfer(_user, pending); user.unpaidRewards = 0; + REWARD_TOKEN.safeTransfer(_user, pending); } } } From 1e2f835e18ad854dce086040f36e67ca5c601865 Mon Sep 17 00:00:00 2001 From: Supeeerpower Date: Thu, 13 Feb 2025 11:45:00 +0200 Subject: [PATCH 5/8] forge install: openzeppelin-contracts-upgradeable v5.2.0 --- .gitmodules | 3 +++ lib/openzeppelin-contracts-upgradeable | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index 690924b..9296efd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..3d5fa5c --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 3d5fa5c24c411112bab47bec25cfa9ad0af0e6e8 From a03b82a12bba808f4d5a29d1fb5e7906e69725bc Mon Sep 17 00:00:00 2001 From: Supeeerpower Date: Thu, 13 Feb 2025 13:30:56 +0200 Subject: [PATCH 6/8] feat: upgrade and deploy script --- .gitmodules | 3 + .solhint.json | 3 +- Makefile | 3 + coverage-report/index-sort-f.html | 36 +- coverage-report/index-sort-l.html | 36 +- coverage-report/index.html | 36 +- .../SimpleRewarderPerSec.sol.func-c.html | 40 ++- .../SimpleRewarderPerSec.sol.func.html | 40 ++- .../SimpleRewarderPerSec.sol.gcov.html | 267 +++++++------- .../rewarders/src/rewarders/index-sort-f.html | 28 +- .../rewarders/src/rewarders/index-sort-l.html | 28 +- .../src/rewarders/src/rewarders/index.html | 28 +- .../src/src/SimpleStakingChef.sol.func-c.html | 33 +- .../src/src/SimpleStakingChef.sol.func.html | 33 +- .../src/src/SimpleStakingChef.sol.gcov.html | 335 +++++++++--------- coverage-report/src/src/index-sort-f.html | 18 +- coverage-report/src/src/index-sort-l.html | 18 +- coverage-report/src/src/index.html | 18 +- lcov.info | 221 ++++++------ lib/openzeppelin-foundry-upgrades | 1 + remappings.txt | 7 +- script/Deploy.s.sol | 38 ++ script/DeploySimpleRewarderPerSec.s.sol | 37 ++ script/DeploySimpleStakingChef.s.sol | 26 ++ src/SimpleStakingChef.sol | 13 +- src/interfaces/ISimpleRewarderPerSec.sol | 2 +- src/interfaces/ISimpleStakingChef.sol | 3 + src/rewarders/SimpleRewarderPerSec.sol | 19 +- test/SimpleStaking.t.sol | 2 + 29 files changed, 781 insertions(+), 591 deletions(-) create mode 160000 lib/openzeppelin-foundry-upgrades create mode 100644 script/Deploy.s.sol create mode 100644 script/DeploySimpleRewarderPerSec.s.sol create mode 100644 script/DeploySimpleStakingChef.s.sol diff --git a/.gitmodules b/.gitmodules index 9296efd..f27e450 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/openzeppelin-foundry-upgrades"] + path = lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades diff --git a/.solhint.json b/.solhint.json index 7e85cd0..5452f2a 100644 --- a/.solhint.json +++ b/.solhint.json @@ -12,6 +12,7 @@ "avoid-sha3": "warn", "not-rely-on-time": "off", "private-vars-leading-underscore": "off", - "reason-string": ["warn", { "maxLength": 64 }] + "reason-string": ["warn", { "maxLength": 64 }], + "no-console": "off" } } diff --git a/Makefile b/Makefile index d4dbaac..c919281 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ inspect :; forge inspect ${contract} storage-layout --pretty # specify which fork to use. set this in our .env # if we want to test multiple forks in one go, remove this as an argument below FORK_URL := ${TEST_RPC_URL} # BASE_RPC_URL, ETH_RPC_URL, ARBITRUM_RPC_URL +PRIVATE_KEY := ${PRIVATE_KEY} # if we want to run only matching tests, set that here test := test_ @@ -40,6 +41,8 @@ clean :; forge clean format :; forge fmt format-check :; forge fmt --check +deploy :; forge script script/Deploy.s.sol:DeployScript --rpc-url ${FORK_URL} --private-key ${PRIVATE_KEY} --broadcast + coverage-html: @echo "Running coverage..." forge coverage --report lcov --fork-url ${FORK_URL} diff --git a/coverage-report/index-sort-f.html b/coverage-report/index-sort-f.html index 0e33901..4bd4c66 100644 --- a/coverage-report/index-sort-f.html +++ b/coverage-report/index-sort-f.html @@ -31,18 +31,18 @@ lcov.info Lines: - 90.2 % - 112 - 101 + 90.6 % + 117 + 106 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: - 92.9 % - 14 - 13 + 94.1 % + 17 + 16 @@ -82,14 +82,14 @@ src/rewarders/src/rewarders -
79.2%79.2%
+
80.4%80.4%
- 79.2 % - 53 - 42 - 83.3 % - 6 - 5 + 80.4 % + 56 + 45 + 87.5 % + 8 + 7 src/src @@ -97,11 +97,11 @@
100.0%
100.0 % - 59 - 59 + 61 + 61 100.0 % - 8 - 8 + 9 + 9 diff --git a/coverage-report/index-sort-l.html b/coverage-report/index-sort-l.html index 631fcb6..fd89af5 100644 --- a/coverage-report/index-sort-l.html +++ b/coverage-report/index-sort-l.html @@ -31,18 +31,18 @@ lcov.info Lines: - 90.2 % - 112 - 101 + 90.6 % + 117 + 106 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: - 92.9 % - 14 - 13 + 94.1 % + 17 + 16 @@ -82,14 +82,14 @@ src/rewarders/src/rewarders -
79.2%79.2%
+
80.4%80.4%
- 79.2 % - 53 - 42 - 83.3 % - 6 - 5 + 80.4 % + 56 + 45 + 87.5 % + 8 + 7 src/src @@ -97,11 +97,11 @@
100.0%
100.0 % - 59 - 59 + 61 + 61 100.0 % - 8 - 8 + 9 + 9 diff --git a/coverage-report/index.html b/coverage-report/index.html index 890a748..f8b85c0 100644 --- a/coverage-report/index.html +++ b/coverage-report/index.html @@ -31,18 +31,18 @@ lcov.info Lines: - 90.2 % - 112 - 101 + 90.6 % + 117 + 106 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: - 92.9 % - 14 - 13 + 94.1 % + 17 + 16 @@ -82,14 +82,14 @@ src/rewarders/src/rewarders -
79.2%79.2%
+
80.4%80.4%
- 79.2 % - 53 - 42 - 83.3 % - 6 - 5 + 80.4 % + 56 + 45 + 87.5 % + 8 + 7 src/src @@ -97,11 +97,11 @@
100.0%
100.0 % - 59 - 59 + 61 + 61 100.0 % - 8 - 8 + 9 + 9 diff --git a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html index 4beadaf..e9ccefb 100644 --- a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html +++ b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html @@ -31,18 +31,18 @@ lcov.info Lines: - 79.2 % - 53 - 42 + 80.4 % + 56 + 45 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: - 83.3 % - 6 - 5 + 87.5 % + 8 + 7 @@ -63,42 +63,56 @@ - SimpleRewarderPerSec.updatePool + SimpleRewarderPerSec.updatePool 0 - SimpleRewarderPerSec.emergencyWithdraw + SimpleRewarderPerSec.emergencyWithdraw 1 - SimpleRewarderPerSec.pendingTokens + SimpleRewarderPerSec.pendingTokens 1 - SimpleRewarderPerSec.setRewardRate + SimpleRewarderPerSec.rewardToken 1 - SimpleRewarderPerSec.balance + SimpleRewarderPerSec.setRewardRate + + 1 + + + + + SimpleRewarderPerSec.balance 2 - SimpleRewarderPerSec.onVoltReward + SimpleRewarderPerSec.initialize + + 10 + + + + + SimpleRewarderPerSec.onVoltReward 19 diff --git a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html index cef31e7..0a00759 100644 --- a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html +++ b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html @@ -31,18 +31,18 @@ lcov.info Lines: - 79.2 % - 53 - 42 + 80.4 % + 56 + 45 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: - 83.3 % - 6 - 5 + 87.5 % + 8 + 7 @@ -63,42 +63,56 @@ - SimpleRewarderPerSec.balance + SimpleRewarderPerSec.balance 2 - SimpleRewarderPerSec.emergencyWithdraw + SimpleRewarderPerSec.emergencyWithdraw 1 - SimpleRewarderPerSec.onVoltReward + SimpleRewarderPerSec.initialize + + 10 + + + + + SimpleRewarderPerSec.onVoltReward 19 - SimpleRewarderPerSec.pendingTokens + SimpleRewarderPerSec.pendingTokens + + 1 + + + + + SimpleRewarderPerSec.rewardToken 1 - SimpleRewarderPerSec.setRewardRate + SimpleRewarderPerSec.setRewardRate 1 - SimpleRewarderPerSec.updatePool + SimpleRewarderPerSec.updatePool 0 diff --git a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html index fcad538..49808bd 100644 --- a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html +++ b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html @@ -31,18 +31,18 @@ lcov.info Lines: - 79.2 % - 53 - 42 + 80.4 % + 56 + 45 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: - 83.3 % - 6 - 5 + 87.5 % + 8 + 7 @@ -63,8 +63,8 @@ 1 : // SPDX-License-Identifier: GPL-3.0 2 : pragma solidity ^0.8.25; 3 : - 4 : import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; - 5 : import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + 4 : import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; + 5 : import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 6 : import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 : import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 8 : import {ISimpleStakingChef} from "../interfaces/ISimpleStakingChef.sol"; @@ -78,7 +78,7 @@ 16 : * 100,000 XYZ and set the block reward accordingly so it's fully distributed after 30 days. 17 : * 18 : */ - 19 : contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { + 19 : contract SimpleRewarderPerSec is OwnableUpgradeable, ReentrancyGuardUpgradeable { 20 : using SafeERC20 for IERC20; 21 : 22 : IERC20 public immutable REWARD_TOKEN; @@ -125,132 +125,139 @@ 63 : _; 64 : } 65 : - 66 : constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative) - 67 : Ownable(msg.sender) - 68 : { - 69 : if (address(_rewardToken) == address(0)) revert InvalidRewardToken(); - 70 : if (address(_lpToken) == address(0)) revert InvalidLPToken(); - 71 : if (address(_mcj) == address(0)) revert InvalidMCJ(); - 72 : - 73 : REWARD_TOKEN = _rewardToken; - 74 : LP_TOKEN = _lpToken; - 75 : tokenPerSec = _tokenPerSec; - 76 : MCJ = _mcj; - 77 : IS_NATIVE = _isNative; - 78 : poolInfo = PoolInfo({lastRewardTimestamp: block.timestamp, accTokenPerShare: 0}); - 79 : } - 80 : - 81 : /// @notice Update reward variables of the given poolInfo. - 82 : /// @return pool Returns the pool that was updated. - 83 0 : function updatePool() public returns (PoolInfo memory pool) { - 84 40 : pool = poolInfo; - 85 : - 86 40 : if (block.timestamp > pool.lastRewardTimestamp) { - 87 6 : uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); + 66 : constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative) { + 67 : if (address(_rewardToken) == address(0)) revert InvalidRewardToken(); + 68 : if (address(_lpToken) == address(0)) revert InvalidLPToken(); + 69 : if (address(_mcj) == address(0)) revert InvalidMCJ(); + 70 : + 71 : REWARD_TOKEN = _rewardToken; + 72 : LP_TOKEN = _lpToken; + 73 : tokenPerSec = _tokenPerSec; + 74 : MCJ = _mcj; + 75 : IS_NATIVE = _isNative; + 76 : poolInfo = PoolInfo({lastRewardTimestamp: block.timestamp, accTokenPerShare: 0}); + 77 : } + 78 : + 79 10 : function initialize() external initializer { + 80 20 : __Ownable_init(msg.sender); + 81 20 : __ReentrancyGuard_init(); + 82 : } + 83 : + 84 : /// @notice Update reward variables of the given poolInfo. + 85 : /// @return pool Returns the pool that was updated. + 86 0 : function updatePool() public returns (PoolInfo memory pool) { + 87 40 : pool = poolInfo; 88 : - 89 4 : if (lpSupply > 0) { - 90 6 : uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; - 91 6 : uint256 tokenReward = timeElapsed * tokenPerSec; - 92 4 : pool.accTokenPerShare = pool.accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); - 93 : } - 94 : - 95 4 : pool.lastRewardTimestamp = block.timestamp; - 96 4 : poolInfo = pool; - 97 : } - 98 : } - 99 : - 100 : /// @notice Sets the distribution reward rate. This will also update the poolInfo. - 101 : /// @param _tokenPerSec The number of tokens to distribute per second - 102 1 : function setRewardRate(uint256 _tokenPerSec) external onlyOwner { - 103 2 : updatePool(); - 104 : - 105 2 : uint256 oldRate = tokenPerSec; - 106 2 : tokenPerSec = _tokenPerSec; + 89 40 : if (block.timestamp > pool.lastRewardTimestamp) { + 90 6 : uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); + 91 : + 92 4 : if (lpSupply > 0) { + 93 6 : uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; + 94 6 : uint256 tokenReward = timeElapsed * tokenPerSec; + 95 4 : pool.accTokenPerShare = pool.accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); + 96 : } + 97 : + 98 4 : pool.lastRewardTimestamp = block.timestamp; + 99 4 : poolInfo = pool; + 100 : } + 101 : } + 102 : + 103 : /// @notice Sets the distribution reward rate. This will also update the poolInfo. + 104 : /// @param _tokenPerSec The number of tokens to distribute per second + 105 1 : function setRewardRate(uint256 _tokenPerSec) external onlyOwner { + 106 2 : updatePool(); 107 : - 108 2 : emit RewardRateUpdated(oldRate, _tokenPerSec); - 109 : } + 108 2 : uint256 oldRate = tokenPerSec; + 109 2 : tokenPerSec = _tokenPerSec; 110 : - 111 : /// @notice Function called by MasterChefVolt whenever staker claims VOLT harvest. Allows staker to also receive a 2nd reward token. - 112 : /// @param _user Address of user - 113 : /// @param _lpAmount Number of LP tokens the user has - 114 19 : function onVoltReward(address _user, uint256 _lpAmount) external onlyMCJ nonReentrant { - 115 38 : updatePool(); - 116 38 : PoolInfo memory pool = poolInfo; - 117 38 : UserInfo storage user = userInfo[_user]; - 118 38 : uint256 pending; - 119 38 : if (user.amount > 0) { - 120 6 : pending = (user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; - 121 : - 122 3 : if (IS_NATIVE) { - 123 0 : uint256 bal = address(this).balance; - 124 0 : if (pending > bal) { - 125 0 : (bool success,) = _user.call{value: bal}(""); - 126 0 : if (!success) revert TransferFailed(); - 127 0 : user.unpaidRewards = pending - bal; - 128 : } else { - 129 0 : (bool success,) = _user.call{value: pending}(""); + 111 2 : emit RewardRateUpdated(oldRate, _tokenPerSec); + 112 : } + 113 : + 114 : /// @notice Function called by MasterChefVolt whenever staker claims VOLT harvest. Allows staker to also receive a 2nd reward token. + 115 : /// @param _user Address of user + 116 : /// @param _lpAmount Number of LP tokens the user has + 117 19 : function onVoltReward(address _user, uint256 _lpAmount) external onlyMCJ nonReentrant { + 118 38 : updatePool(); + 119 38 : PoolInfo memory pool = poolInfo; + 120 38 : UserInfo storage user = userInfo[_user]; + 121 38 : uint256 pending; + 122 38 : if (user.amount > 0) { + 123 6 : pending = (user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; + 124 : + 125 3 : if (IS_NATIVE) { + 126 0 : uint256 bal = address(this).balance; + 127 0 : if (pending > bal) { + 128 0 : user.unpaidRewards = pending - bal; + 129 0 : (bool success,) = _user.call{value: bal}(""); 130 0 : if (!success) revert TransferFailed(); - 131 0 : user.unpaidRewards = 0; - 132 : } - 133 : } else { - 134 9 : uint256 bal = REWARD_TOKEN.balanceOf(address(this)); - 135 6 : if (pending > bal) { - 136 4 : REWARD_TOKEN.safeTransfer(_user, bal); - 137 4 : user.unpaidRewards = pending - bal; - 138 : } else { - 139 2 : REWARD_TOKEN.safeTransfer(_user, pending); - 140 2 : user.unpaidRewards = 0; - 141 : } - 142 : } - 143 : } - 144 : - 145 38 : user.amount = _lpAmount; - 146 38 : user.rewardDebt = user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION; - 147 38 : emit OnReward(_user, pending - user.unpaidRewards); - 148 : } - 149 : - 150 : /// @notice View function to see pending tokens - 151 : /// @param _user Address of user. - 152 : /// @return pending reward for a given user. - 153 1 : function pendingTokens(address _user) external view returns (uint256 pending) { - 154 2 : PoolInfo memory pool = poolInfo; - 155 2 : UserInfo storage user = userInfo[_user]; - 156 : - 157 2 : uint256 accTokenPerShare = pool.accTokenPerShare; - 158 3 : uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); + 131 : } else { + 132 0 : user.unpaidRewards = 0; + 133 0 : (bool success,) = _user.call{value: pending}(""); + 134 0 : if (!success) revert TransferFailed(); + 135 : } + 136 : } else { + 137 9 : uint256 bal = REWARD_TOKEN.balanceOf(address(this)); + 138 6 : if (pending > bal) { + 139 4 : user.unpaidRewards = pending - bal; + 140 4 : REWARD_TOKEN.safeTransfer(_user, bal); + 141 : } else { + 142 2 : user.unpaidRewards = 0; + 143 2 : REWARD_TOKEN.safeTransfer(_user, pending); + 144 : } + 145 : } + 146 : } + 147 : + 148 38 : user.amount = _lpAmount; + 149 38 : user.rewardDebt = user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION; + 150 38 : emit OnReward(_user, pending - user.unpaidRewards); + 151 : } + 152 : + 153 : /// @notice View function to see pending tokens + 154 : /// @param _user Address of user. + 155 : /// @return pending reward for a given user. + 156 1 : function pendingTokens(address _user) external view returns (uint256 pending) { + 157 2 : PoolInfo memory pool = poolInfo; + 158 2 : UserInfo storage user = userInfo[_user]; 159 : - 160 4 : if (block.timestamp > pool.lastRewardTimestamp && lpSupply != 0) { - 161 3 : uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; - 162 3 : uint256 tokenReward = timeElapsed * tokenPerSec; - 163 2 : accTokenPerShare = accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); - 164 : } - 165 : - 166 2 : pending = (user.amount * accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; - 167 : } + 160 2 : uint256 accTokenPerShare = pool.accTokenPerShare; + 161 3 : uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); + 162 : + 163 4 : if (block.timestamp > pool.lastRewardTimestamp && lpSupply != 0) { + 164 3 : uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; + 165 3 : uint256 tokenReward = timeElapsed * tokenPerSec; + 166 2 : accTokenPerShare = accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); + 167 : } 168 : - 169 : /// @notice In case rewarder is stopped before emissions finished, this function allows - 170 : /// withdrawal of remaining tokens. - 171 1 : function emergencyWithdraw() public onlyOwner { - 172 1 : if (IS_NATIVE) { - 173 0 : (bool success,) = msg.sender.call{value: address(this).balance}(""); - 174 0 : if (!success) revert TransferFailed(); - 175 : } else { - 176 2 : REWARD_TOKEN.safeTransfer(address(msg.sender), REWARD_TOKEN.balanceOf(address(this))); - 177 : } - 178 : } - 179 : - 180 : /// @notice View function to see balance of reward token. - 181 2 : function balance() external view returns (uint256) { - 182 2 : if (IS_NATIVE) { - 183 0 : return address(this).balance; - 184 : } else { - 185 6 : return REWARD_TOKEN.balanceOf(address(this)); - 186 : } - 187 : } - 188 : - 189 : /// @notice payable function needed to receive AVAX - 190 : receive() external payable {} - 191 : } + 169 2 : pending = (user.amount * accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; + 170 : } + 171 : + 172 : /// @notice In case rewarder is stopped before emissions finished, this function allows + 173 : /// withdrawal of remaining tokens. + 174 1 : function emergencyWithdraw() public onlyOwner { + 175 1 : if (IS_NATIVE) { + 176 0 : (bool success,) = msg.sender.call{value: address(this).balance}(""); + 177 0 : if (!success) revert TransferFailed(); + 178 : } else { + 179 2 : REWARD_TOKEN.safeTransfer(address(msg.sender), REWARD_TOKEN.balanceOf(address(this))); + 180 : } + 181 : } + 182 : + 183 : /// @notice View function to see balance of reward token. + 184 2 : function balance() external view returns (uint256) { + 185 2 : if (IS_NATIVE) { + 186 0 : return address(this).balance; + 187 : } else { + 188 6 : return REWARD_TOKEN.balanceOf(address(this)); + 189 : } + 190 : } + 191 : + 192 1 : function rewardToken() external view returns (IERC20) { + 193 2 : return REWARD_TOKEN; + 194 : } + 195 : + 196 : /// @notice payable function needed to receive AVAX + 197 : receive() external payable {} + 198 : } diff --git a/coverage-report/src/rewarders/src/rewarders/index-sort-f.html b/coverage-report/src/rewarders/src/rewarders/index-sort-f.html index 6c54591..b42207b 100644 --- a/coverage-report/src/rewarders/src/rewarders/index-sort-f.html +++ b/coverage-report/src/rewarders/src/rewarders/index-sort-f.html @@ -31,18 +31,18 @@ lcov.info Lines: - 79.2 % - 53 - 42 + 80.4 % + 56 + 45 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: - 83.3 % - 6 - 5 + 87.5 % + 8 + 7 @@ -82,14 +82,14 @@ SimpleRewarderPerSec.sol -
79.2%79.2%
+
80.4%80.4%
- 79.2 % - 53 - 42 - 83.3 % - 6 - 5 + 80.4 % + 56 + 45 + 87.5 % + 8 + 7 diff --git a/coverage-report/src/rewarders/src/rewarders/index-sort-l.html b/coverage-report/src/rewarders/src/rewarders/index-sort-l.html index afcc44f..071aa10 100644 --- a/coverage-report/src/rewarders/src/rewarders/index-sort-l.html +++ b/coverage-report/src/rewarders/src/rewarders/index-sort-l.html @@ -31,18 +31,18 @@ lcov.info Lines: - 79.2 % - 53 - 42 + 80.4 % + 56 + 45 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: - 83.3 % - 6 - 5 + 87.5 % + 8 + 7 @@ -82,14 +82,14 @@ SimpleRewarderPerSec.sol -
79.2%79.2%
+
80.4%80.4%
- 79.2 % - 53 - 42 - 83.3 % - 6 - 5 + 80.4 % + 56 + 45 + 87.5 % + 8 + 7 diff --git a/coverage-report/src/rewarders/src/rewarders/index.html b/coverage-report/src/rewarders/src/rewarders/index.html index b313b1d..5cff7a2 100644 --- a/coverage-report/src/rewarders/src/rewarders/index.html +++ b/coverage-report/src/rewarders/src/rewarders/index.html @@ -31,18 +31,18 @@ lcov.info Lines: - 79.2 % - 53 - 42 + 80.4 % + 56 + 45 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: - 83.3 % - 6 - 5 + 87.5 % + 8 + 7 @@ -82,14 +82,14 @@ SimpleRewarderPerSec.sol -
79.2%79.2%
+
80.4%80.4%
- 79.2 % - 53 - 42 - 83.3 % - 6 - 5 + 80.4 % + 56 + 45 + 87.5 % + 8 + 7 diff --git a/coverage-report/src/src/SimpleStakingChef.sol.func-c.html b/coverage-report/src/src/SimpleStakingChef.sol.func-c.html index 2fef44b..b8ee6c0 100644 --- a/coverage-report/src/src/SimpleStakingChef.sol.func-c.html +++ b/coverage-report/src/src/SimpleStakingChef.sol.func-c.html @@ -32,17 +32,17 @@ Lines: 100.0 % - 59 - 59 + 61 + 61 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: 100.0 % - 8 - 8 + 9 + 9 @@ -63,56 +63,63 @@ - SimpleStakingChef.emergencyWithdraw + SimpleStakingChef.emergencyWithdraw 1 - SimpleStakingChef.pendingTokens + SimpleStakingChef.pendingTokens 1 - SimpleStakingChef.poolLength + SimpleStakingChef.poolLength 1 - SimpleStakingChef.set + SimpleStakingChef.set 1 - SimpleStakingChef.updatePool + SimpleStakingChef.updatePool 1 - SimpleStakingChef.withdraw + SimpleStakingChef.withdraw 2 - SimpleStakingChef.deposit + SimpleStakingChef.deposit 5 - SimpleStakingChef.add + SimpleStakingChef.add + + 10 + + + + + SimpleStakingChef.initialize 10 diff --git a/coverage-report/src/src/SimpleStakingChef.sol.func.html b/coverage-report/src/src/SimpleStakingChef.sol.func.html index e66216c..42a4ef9 100644 --- a/coverage-report/src/src/SimpleStakingChef.sol.func.html +++ b/coverage-report/src/src/SimpleStakingChef.sol.func.html @@ -32,17 +32,17 @@ Lines: 100.0 % - 59 - 59 + 61 + 61 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: 100.0 % - 8 - 8 + 9 + 9 @@ -63,56 +63,63 @@ - SimpleStakingChef.add + SimpleStakingChef.add 10 - SimpleStakingChef.deposit + SimpleStakingChef.deposit 5 - SimpleStakingChef.emergencyWithdraw + SimpleStakingChef.emergencyWithdraw 1 - SimpleStakingChef.pendingTokens + SimpleStakingChef.initialize + + 10 + + + + + SimpleStakingChef.pendingTokens 1 - SimpleStakingChef.poolLength + SimpleStakingChef.poolLength 1 - SimpleStakingChef.set + SimpleStakingChef.set 1 - SimpleStakingChef.updatePool + SimpleStakingChef.updatePool 1 - SimpleStakingChef.withdraw + SimpleStakingChef.withdraw 2 diff --git a/coverage-report/src/src/SimpleStakingChef.sol.gcov.html b/coverage-report/src/src/SimpleStakingChef.sol.gcov.html index ddcb424..f3bcabe 100644 --- a/coverage-report/src/src/SimpleStakingChef.sol.gcov.html +++ b/coverage-report/src/src/SimpleStakingChef.sol.gcov.html @@ -32,17 +32,17 @@ Lines: 100.0 % - 59 - 59 + 61 + 61 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: 100.0 % - 8 - 8 + 9 + 9 @@ -63,8 +63,8 @@ 1 : // SPDX-License-Identifier: MIT 2 : pragma solidity ^0.8.25; 3 : - 4 : import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - 5 : import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + 4 : import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + 5 : import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; 6 : import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; 7 : import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 8 : import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -76,7 +76,7 @@ 14 : /// The idea for this MasterChefVoltV3 (MCJV3) contract is therefore to be the owner of a dummy token 15 : /// that is deposited into the MasterChefVoltV2 (MCJV2) contract. 16 : /// The allocation point for this pool on MCJV3 is the total allocation point for all pools that receive double incentives. - 17 : contract SimpleStakingChef is Ownable, ReentrancyGuard { + 17 : contract SimpleStakingChef is OwnableUpgradeable, ReentrancyGuardUpgradeable { 18 : using SafeERC20 for IERC20; 19 : using EnumerableSet for EnumerableSet.AddressSet; 20 : @@ -117,166 +117,169 @@ 55 : 56 : error LPAlreadyAdded(); 57 : - 58 : constructor() Ownable(msg.sender) {} - 59 : - 60 : /// @notice Returns the number of MCJV3 pools. - 61 1 : function poolLength() external view returns (uint256 pools) { - 62 2 : pools = poolInfo.length; - 63 : } - 64 : - 65 : /// @notice Add a new LP to the pool. Can only be called by the owner. - 66 : /// DO NOT add the same LP token more than once. Rewards will be messed up if you do. - 67 : /// @param _allocPoint AP of the new pool. - 68 : /// @param _lpToken Address of the LP ERC-20 token. - 69 : /// @param _rewarder Address of the rewarder delegate. - 70 10 : function add(uint256 _allocPoint, IERC20 _lpToken, ISimpleRewarderPerSec _rewarder) external onlyOwner { - 71 20 : if (lpTokens.contains(address(_lpToken))) revert LPAlreadyAdded(); - 72 : // Sanity check to ensure _lpToken is an ERC20 token - 73 20 : _lpToken.balanceOf(address(this)); - 74 : // Sanity check if we add a rewarder - 75 40 : if (address(_rewarder) != address(0)) { - 76 20 : _rewarder.onVoltReward(address(0), 0); - 77 : } - 78 : - 79 20 : uint256 lastRewardTimestamp = block.timestamp; - 80 : - 81 20 : poolInfo.push( - 82 : PoolInfo({ - 83 : lpToken: _lpToken, - 84 : allocPoint: _allocPoint, - 85 : lastRewardTimestamp: lastRewardTimestamp, - 86 : accVoltPerShare: 0, - 87 : rewarder: _rewarder - 88 : }) - 89 : ); - 90 20 : lpTokens.add(address(_lpToken)); - 91 20 : emit Add(poolInfo.length - 1, _allocPoint, _lpToken, _rewarder); - 92 : } - 93 : - 94 : /// @notice Update the given pool's VOLT allocation point and `IRewarder` contract. Can only be called by the owner. - 95 : /// @param _pid The index of the pool. See `poolInfo`. - 96 : /// @param _allocPoint New AP of the pool. - 97 : /// @param _rewarder Address of the rewarder delegate. - 98 : /// @param overwrite True if _rewarder should be `set`. Otherwise `_rewarder` is ignored. - 99 1 : function set(uint256 _pid, uint256 _allocPoint, ISimpleRewarderPerSec _rewarder, bool overwrite) - 100 : external - 101 : onlyOwner - 102 : { - 103 2 : PoolInfo memory pool = poolInfo[_pid]; - 104 2 : pool.allocPoint = _allocPoint; - 105 1 : if (overwrite) { - 106 2 : _rewarder.onVoltReward(address(0), 0); // sanity check - 107 2 : pool.rewarder = _rewarder; - 108 : } - 109 2 : poolInfo[_pid] = pool; - 110 2 : emit Set(_pid, _allocPoint, overwrite ? _rewarder : pool.rewarder, overwrite); - 111 : } - 112 : - 113 : /// @notice View function to see pending VOLT on frontend. - 114 : /// @param _pid The index of the pool. See `poolInfo`. - 115 : /// @param _user Address of user. - 116 : /// @return pendingVolt VOLT reward for a given user. - 117 : // bonusTokenAddress The address of the bonus reward. - 118 : // bonusTokenSymbol The symbol of the bonus token. - 119 : // pendingBonusToken The amount of bonus rewards pending. - 120 1 : function pendingTokens(uint256 _pid, address _user) - 121 : external - 122 : view - 123 : returns ( - 124 : uint256 pendingVolt, - 125 : address bonusTokenAddress, - 126 : string memory bonusTokenSymbol, - 127 : uint256 pendingBonusToken - 128 : ) - 129 : { - 130 2 : PoolInfo memory pool = poolInfo[_pid]; - 131 : // If it's a double reward farm, we return info about the bonus token - 132 4 : if (address(pool.rewarder) != address(0)) { - 133 2 : bonusTokenAddress = address(pool.rewarder.REWARD_TOKEN()); - 134 2 : bonusTokenSymbol = IERC20Metadata(bonusTokenAddress).symbol(); - 135 2 : pendingBonusToken = pool.rewarder.pendingTokens(_user); - 136 : } - 137 : } - 138 : - 139 : /// @notice Update reward variables of the given pool. - 140 : /// @param pid The index of the pool. See `poolInfo`. - 141 1 : function updatePool(uint256 pid) public { - 142 16 : PoolInfo memory pool = poolInfo[pid]; - 143 16 : if (block.timestamp > pool.lastRewardTimestamp) { - 144 6 : uint256 lpSupply = pool.lpToken.balanceOf(address(this)); - 145 4 : pool.lastRewardTimestamp = block.timestamp; - 146 4 : poolInfo[pid] = pool; - 147 4 : emit UpdatePool(pid, pool.lastRewardTimestamp, lpSupply, pool.accVoltPerShare); - 148 : } - 149 : } - 150 : - 151 : /// @notice Deposit LP tokens to MCJV3 for VOLT allocation. - 152 : /// @param pid The index of the pool. See `poolInfo`. - 153 : /// @param amount LP token amount to deposit. - 154 5 : function deposit(uint256 pid, uint256 amount) external nonReentrant { - 155 10 : updatePool(pid); - 156 10 : PoolInfo memory pool = poolInfo[pid]; - 157 10 : UserInfo storage user = userInfo[pid][msg.sender]; - 158 : - 159 15 : uint256 balanceBefore = pool.lpToken.balanceOf(address(this)); - 160 10 : pool.lpToken.safeTransferFrom(msg.sender, address(this), amount); - 161 20 : uint256 receivedAmount = pool.lpToken.balanceOf(address(this)) - balanceBefore; - 162 : - 163 : // Effects - 164 10 : user.amount = user.amount + receivedAmount; - 165 10 : user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; - 166 : - 167 : // Interactions - 168 10 : ISimpleRewarderPerSec _rewarder = pool.rewarder; - 169 20 : if (address(_rewarder) != address(0)) { - 170 10 : _rewarder.onVoltReward(msg.sender, user.amount); - 171 : } - 172 : - 173 10 : emit Deposit(msg.sender, pid, receivedAmount); - 174 : } + 58 10 : function initialize() external initializer { + 59 20 : __Ownable_init(msg.sender); + 60 20 : __ReentrancyGuard_init(); + 61 : } + 62 : + 63 : /// @notice Returns the number of MCJV3 pools. + 64 1 : function poolLength() external view returns (uint256 pools) { + 65 2 : pools = poolInfo.length; + 66 : } + 67 : + 68 : /// @notice Add a new LP to the pool. Can only be called by the owner. + 69 : /// DO NOT add the same LP token more than once. Rewards will be messed up if you do. + 70 : /// @param _allocPoint AP of the new pool. + 71 : /// @param _lpToken Address of the LP ERC-20 token. + 72 : /// @param _rewarder Address of the rewarder delegate. + 73 10 : function add(uint256 _allocPoint, IERC20 _lpToken, ISimpleRewarderPerSec _rewarder) external onlyOwner { + 74 20 : if (lpTokens.contains(address(_lpToken))) revert LPAlreadyAdded(); + 75 : // Sanity check to ensure _lpToken is an ERC20 token + 76 20 : _lpToken.balanceOf(address(this)); + 77 : // Sanity check if we add a rewarder + 78 40 : if (address(_rewarder) != address(0)) { + 79 20 : _rewarder.onVoltReward(address(0), 0); + 80 : } + 81 : + 82 20 : uint256 lastRewardTimestamp = block.timestamp; + 83 : + 84 20 : poolInfo.push( + 85 : PoolInfo({ + 86 : lpToken: _lpToken, + 87 : allocPoint: _allocPoint, + 88 : lastRewardTimestamp: lastRewardTimestamp, + 89 : accVoltPerShare: 0, + 90 : rewarder: _rewarder + 91 : }) + 92 : ); + 93 20 : lpTokens.add(address(_lpToken)); + 94 20 : emit Add(poolInfo.length - 1, _allocPoint, _lpToken, _rewarder); + 95 : } + 96 : + 97 : /// @notice Update the given pool's VOLT allocation point and `IRewarder` contract. Can only be called by the owner. + 98 : /// @param _pid The index of the pool. See `poolInfo`. + 99 : /// @param _allocPoint New AP of the pool. + 100 : /// @param _rewarder Address of the rewarder delegate. + 101 : /// @param overwrite True if _rewarder should be `set`. Otherwise `_rewarder` is ignored. + 102 1 : function set(uint256 _pid, uint256 _allocPoint, ISimpleRewarderPerSec _rewarder, bool overwrite) + 103 : external + 104 : onlyOwner + 105 : { + 106 2 : PoolInfo memory pool = poolInfo[_pid]; + 107 2 : pool.allocPoint = _allocPoint; + 108 1 : if (overwrite) { + 109 2 : _rewarder.onVoltReward(address(0), 0); // sanity check + 110 2 : pool.rewarder = _rewarder; + 111 : } + 112 2 : poolInfo[_pid] = pool; + 113 2 : emit Set(_pid, _allocPoint, overwrite ? _rewarder : pool.rewarder, overwrite); + 114 : } + 115 : + 116 : /// @notice View function to see pending VOLT on frontend. + 117 : /// @param _pid The index of the pool. See `poolInfo`. + 118 : /// @param _user Address of user. + 119 : /// @return pendingVolt VOLT reward for a given user. + 120 : // bonusTokenAddress The address of the bonus reward. + 121 : // bonusTokenSymbol The symbol of the bonus token. + 122 : // pendingBonusToken The amount of bonus rewards pending. + 123 1 : function pendingTokens(uint256 _pid, address _user) + 124 : external + 125 : view + 126 : returns ( + 127 : uint256 pendingVolt, + 128 : address bonusTokenAddress, + 129 : string memory bonusTokenSymbol, + 130 : uint256 pendingBonusToken + 131 : ) + 132 : { + 133 2 : PoolInfo memory pool = poolInfo[_pid]; + 134 : // If it's a double reward farm, we return info about the bonus token + 135 4 : if (address(pool.rewarder) != address(0)) { + 136 2 : bonusTokenAddress = address(pool.rewarder.rewardToken()); + 137 2 : bonusTokenSymbol = IERC20Metadata(bonusTokenAddress).symbol(); + 138 2 : pendingBonusToken = pool.rewarder.pendingTokens(_user); + 139 : } + 140 : } + 141 : + 142 : /// @notice Update reward variables of the given pool. + 143 : /// @param pid The index of the pool. See `poolInfo`. + 144 1 : function updatePool(uint256 pid) public { + 145 16 : PoolInfo memory pool = poolInfo[pid]; + 146 16 : if (block.timestamp > pool.lastRewardTimestamp) { + 147 6 : uint256 lpSupply = pool.lpToken.balanceOf(address(this)); + 148 4 : pool.lastRewardTimestamp = block.timestamp; + 149 4 : poolInfo[pid] = pool; + 150 4 : emit UpdatePool(pid, pool.lastRewardTimestamp, lpSupply, pool.accVoltPerShare); + 151 : } + 152 : } + 153 : + 154 : /// @notice Deposit LP tokens to MCJV3 for VOLT allocation. + 155 : /// @param pid The index of the pool. See `poolInfo`. + 156 : /// @param amount LP token amount to deposit. + 157 5 : function deposit(uint256 pid, uint256 amount) external nonReentrant { + 158 10 : updatePool(pid); + 159 10 : PoolInfo memory pool = poolInfo[pid]; + 160 10 : UserInfo storage user = userInfo[pid][msg.sender]; + 161 : + 162 15 : uint256 balanceBefore = pool.lpToken.balanceOf(address(this)); + 163 10 : pool.lpToken.safeTransferFrom(msg.sender, address(this), amount); + 164 20 : uint256 receivedAmount = pool.lpToken.balanceOf(address(this)) - balanceBefore; + 165 : + 166 : // Effects + 167 10 : user.amount = user.amount + receivedAmount; + 168 10 : user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; + 169 : + 170 : // Interactions + 171 10 : ISimpleRewarderPerSec _rewarder = pool.rewarder; + 172 20 : if (address(_rewarder) != address(0)) { + 173 10 : _rewarder.onVoltReward(msg.sender, user.amount); + 174 : } 175 : - 176 : /// @notice Withdraw LP tokens from MCJV3. - 177 : /// @param pid The index of the pool. See `poolInfo`. - 178 : /// @param amount LP token amount to withdraw. - 179 2 : function withdraw(uint256 pid, uint256 amount) external nonReentrant { - 180 4 : updatePool(pid); - 181 4 : PoolInfo memory pool = poolInfo[pid]; - 182 4 : UserInfo storage user = userInfo[pid][msg.sender]; - 183 : - 184 : // Effects - 185 4 : user.amount = user.amount - amount; - 186 4 : user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; - 187 : - 188 : // Interactions - 189 4 : ISimpleRewarderPerSec _rewarder = pool.rewarder; - 190 8 : if (address(_rewarder) != address(0)) { - 191 4 : _rewarder.onVoltReward(msg.sender, user.amount); - 192 : } - 193 : - 194 4 : pool.lpToken.safeTransfer(msg.sender, amount); - 195 : - 196 4 : emit Withdraw(msg.sender, pid, amount); - 197 : } + 176 10 : emit Deposit(msg.sender, pid, receivedAmount); + 177 : } + 178 : + 179 : /// @notice Withdraw LP tokens from MCJV3. + 180 : /// @param pid The index of the pool. See `poolInfo`. + 181 : /// @param amount LP token amount to withdraw. + 182 2 : function withdraw(uint256 pid, uint256 amount) external nonReentrant { + 183 4 : updatePool(pid); + 184 4 : PoolInfo memory pool = poolInfo[pid]; + 185 4 : UserInfo storage user = userInfo[pid][msg.sender]; + 186 : + 187 : // Effects + 188 4 : user.amount = user.amount - amount; + 189 4 : user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; + 190 : + 191 : // Interactions + 192 4 : ISimpleRewarderPerSec _rewarder = pool.rewarder; + 193 8 : if (address(_rewarder) != address(0)) { + 194 4 : _rewarder.onVoltReward(msg.sender, user.amount); + 195 : } + 196 : + 197 4 : pool.lpToken.safeTransfer(msg.sender, amount); 198 : - 199 : /// @notice Withdraw without caring about rewards. EMERGENCY ONLY. - 200 : /// @param pid The index of the pool. See `poolInfo`. - 201 1 : function emergencyWithdraw(uint256 pid) external nonReentrant { - 202 2 : PoolInfo memory pool = poolInfo[pid]; - 203 2 : UserInfo storage user = userInfo[pid][msg.sender]; - 204 2 : uint256 amount = user.amount; - 205 2 : user.amount = 0; - 206 2 : user.rewardDebt = 0; - 207 : - 208 2 : ISimpleRewarderPerSec _rewarder = pool.rewarder; - 209 4 : if (address(_rewarder) != address(0)) { - 210 2 : _rewarder.onVoltReward(msg.sender, 0); - 211 : } - 212 : - 213 : // Note: transfer can fail or succeed if `amount` is zero. - 214 2 : pool.lpToken.safeTransfer(msg.sender, amount); - 215 2 : emit EmergencyWithdraw(msg.sender, pid, amount); - 216 : } - 217 : } + 199 4 : emit Withdraw(msg.sender, pid, amount); + 200 : } + 201 : + 202 : /// @notice Withdraw without caring about rewards. EMERGENCY ONLY. + 203 : /// @param pid The index of the pool. See `poolInfo`. + 204 1 : function emergencyWithdraw(uint256 pid) external nonReentrant { + 205 2 : PoolInfo memory pool = poolInfo[pid]; + 206 2 : UserInfo storage user = userInfo[pid][msg.sender]; + 207 2 : uint256 amount = user.amount; + 208 2 : user.amount = 0; + 209 2 : user.rewardDebt = 0; + 210 : + 211 2 : ISimpleRewarderPerSec _rewarder = pool.rewarder; + 212 4 : if (address(_rewarder) != address(0)) { + 213 2 : _rewarder.onVoltReward(msg.sender, 0); + 214 : } + 215 : + 216 : // Note: transfer can fail or succeed if `amount` is zero. + 217 2 : pool.lpToken.safeTransfer(msg.sender, amount); + 218 2 : emit EmergencyWithdraw(msg.sender, pid, amount); + 219 : } + 220 : } diff --git a/coverage-report/src/src/index-sort-f.html b/coverage-report/src/src/index-sort-f.html index 32d3d99..61bcfef 100644 --- a/coverage-report/src/src/index-sort-f.html +++ b/coverage-report/src/src/index-sort-f.html @@ -32,17 +32,17 @@ Lines: 100.0 % - 59 - 59 + 61 + 61 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: 100.0 % - 8 - 8 + 9 + 9 @@ -85,11 +85,11 @@
100.0%
100.0 % - 59 - 59 + 61 + 61 100.0 % - 8 - 8 + 9 + 9 diff --git a/coverage-report/src/src/index-sort-l.html b/coverage-report/src/src/index-sort-l.html index 4bd7e6e..cf48ad6 100644 --- a/coverage-report/src/src/index-sort-l.html +++ b/coverage-report/src/src/index-sort-l.html @@ -32,17 +32,17 @@ Lines: 100.0 % - 59 - 59 + 61 + 61 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: 100.0 % - 8 - 8 + 9 + 9 @@ -85,11 +85,11 @@
100.0%
100.0 % - 59 - 59 + 61 + 61 100.0 % - 8 - 8 + 9 + 9 diff --git a/coverage-report/src/src/index.html b/coverage-report/src/src/index.html index dd7f0d5..2605c92 100644 --- a/coverage-report/src/src/index.html +++ b/coverage-report/src/src/index.html @@ -32,17 +32,17 @@ Lines: 100.0 % - 59 - 59 + 61 + 61 Test Date: - 2025-02-13 10:34:31 + 2025-02-13 13:30:09 Functions: 100.0 % - 8 - 8 + 9 + 9 @@ -85,11 +85,11 @@
100.0%
100.0 % - 59 - 59 + 61 + 61 100.0 % - 8 - 8 + 9 + 9 diff --git a/lcov.info b/lcov.info index 08976b0..a055f24 100644 --- a/lcov.info +++ b/lcov.info @@ -1,154 +1,165 @@ TN: SF:src/SimpleStakingChef.sol -FN:120,SimpleStakingChef.pendingTokens -FN:141,SimpleStakingChef.updatePool -FN:154,SimpleStakingChef.deposit -FN:179,SimpleStakingChef.withdraw -FN:201,SimpleStakingChef.emergencyWithdraw -FN:61,SimpleStakingChef.poolLength -FN:70,SimpleStakingChef.add -FN:99,SimpleStakingChef.set +FN:102,SimpleStakingChef.set +FN:123,SimpleStakingChef.pendingTokens +FN:144,SimpleStakingChef.updatePool +FN:157,SimpleStakingChef.deposit +FN:182,SimpleStakingChef.withdraw +FN:204,SimpleStakingChef.emergencyWithdraw +FN:58,SimpleStakingChef.initialize +FN:64,SimpleStakingChef.poolLength +FN:73,SimpleStakingChef.add +FNDA:1,SimpleStakingChef.set FNDA:1,SimpleStakingChef.pendingTokens FNDA:1,SimpleStakingChef.updatePool FNDA:5,SimpleStakingChef.deposit FNDA:2,SimpleStakingChef.withdraw FNDA:1,SimpleStakingChef.emergencyWithdraw +FNDA:10,SimpleStakingChef.initialize FNDA:1,SimpleStakingChef.poolLength FNDA:10,SimpleStakingChef.add -FNDA:1,SimpleStakingChef.set -FNF:8 -FNH:8 -DA:62,2 -DA:71,20 -DA:73,20 -DA:75,40 +FNF:9 +FNH:9 +DA:59,20 +DA:60,20 +DA:65,2 +DA:74,20 DA:76,20 +DA:78,40 DA:79,20 -DA:81,20 -DA:90,20 -DA:91,20 -DA:103,2 -DA:104,2 -DA:105,1 +DA:82,20 +DA:84,20 +DA:93,20 +DA:94,20 DA:106,2 DA:107,2 +DA:108,1 DA:109,2 DA:110,2 -DA:130,2 -DA:132,4 +DA:112,2 +DA:113,2 DA:133,2 -DA:134,2 -DA:135,2 -DA:142,16 -DA:143,16 -DA:144,6 -DA:145,4 -DA:146,4 -DA:147,4 -DA:155,10 -DA:156,10 -DA:157,10 -DA:159,15 +DA:135,4 +DA:136,2 +DA:137,2 +DA:138,2 +DA:145,16 +DA:146,16 +DA:147,6 +DA:148,4 +DA:149,4 +DA:150,4 +DA:158,10 +DA:159,10 DA:160,10 -DA:161,20 -DA:164,10 -DA:165,10 +DA:162,15 +DA:163,10 +DA:164,20 +DA:167,10 DA:168,10 -DA:169,20 -DA:170,10 +DA:171,10 +DA:172,20 DA:173,10 -DA:180,4 -DA:181,4 -DA:182,4 +DA:176,10 +DA:183,4 +DA:184,4 DA:185,4 -DA:186,4 +DA:188,4 DA:189,4 -DA:190,8 -DA:191,4 +DA:192,4 +DA:193,8 DA:194,4 -DA:196,4 -DA:202,2 -DA:203,2 -DA:204,2 +DA:197,4 +DA:199,4 DA:205,2 DA:206,2 +DA:207,2 DA:208,2 -DA:209,4 -DA:210,2 -DA:214,2 -DA:215,2 -LF:59 -LH:59 +DA:209,2 +DA:211,2 +DA:212,4 +DA:213,2 +DA:217,2 +DA:218,2 +LF:61 +LH:61 end_of_record TN: SF:src/rewarders/SimpleRewarderPerSec.sol -FN:102,SimpleRewarderPerSec.setRewardRate -FN:114,SimpleRewarderPerSec.onVoltReward -FN:153,SimpleRewarderPerSec.pendingTokens -FN:171,SimpleRewarderPerSec.emergencyWithdraw -FN:181,SimpleRewarderPerSec.balance -FN:83,SimpleRewarderPerSec.updatePool +FN:105,SimpleRewarderPerSec.setRewardRate +FN:117,SimpleRewarderPerSec.onVoltReward +FN:156,SimpleRewarderPerSec.pendingTokens +FN:174,SimpleRewarderPerSec.emergencyWithdraw +FN:184,SimpleRewarderPerSec.balance +FN:192,SimpleRewarderPerSec.rewardToken +FN:79,SimpleRewarderPerSec.initialize +FN:86,SimpleRewarderPerSec.updatePool FNDA:1,SimpleRewarderPerSec.setRewardRate FNDA:19,SimpleRewarderPerSec.onVoltReward FNDA:1,SimpleRewarderPerSec.pendingTokens FNDA:1,SimpleRewarderPerSec.emergencyWithdraw FNDA:2,SimpleRewarderPerSec.balance +FNDA:1,SimpleRewarderPerSec.rewardToken +FNDA:10,SimpleRewarderPerSec.initialize FNDA:0,SimpleRewarderPerSec.updatePool -FNF:6 -FNH:5 -DA:84,40 -DA:86,40 -DA:87,6 -DA:89,4 +FNF:8 +FNH:7 +DA:80,20 +DA:81,20 +DA:87,40 +DA:89,40 DA:90,6 -DA:91,6 DA:92,4 +DA:93,6 +DA:94,6 DA:95,4 -DA:96,4 -DA:103,2 -DA:105,2 +DA:98,4 +DA:99,4 DA:106,2 DA:108,2 -DA:115,38 -DA:116,38 -DA:117,38 +DA:109,2 +DA:111,2 DA:118,38 DA:119,38 -DA:120,6 -DA:122,3 -DA:123,0 -DA:124,0 -DA:125,0 +DA:120,38 +DA:121,38 +DA:122,38 +DA:123,6 +DA:125,3 DA:126,0 DA:127,0 +DA:128,0 DA:129,0 DA:130,0 -DA:131,0 -DA:134,9 -DA:135,6 -DA:136,4 -DA:137,4 -DA:139,2 -DA:140,2 -DA:145,38 -DA:146,38 -DA:147,38 -DA:154,2 -DA:155,2 +DA:132,0 +DA:133,0 +DA:134,0 +DA:137,9 +DA:138,6 +DA:139,4 +DA:140,4 +DA:142,2 +DA:143,2 +DA:148,38 +DA:149,38 +DA:150,38 DA:157,2 -DA:158,3 -DA:160,4 +DA:158,2 +DA:160,2 DA:161,3 -DA:162,3 -DA:163,2 +DA:163,4 +DA:164,3 +DA:165,3 DA:166,2 -DA:172,1 -DA:173,0 -DA:174,0 -DA:176,2 -DA:182,2 -DA:183,0 -DA:185,6 -LF:53 -LH:42 +DA:169,2 +DA:175,1 +DA:176,0 +DA:177,0 +DA:179,2 +DA:185,2 +DA:186,0 +DA:188,6 +DA:193,2 +LF:56 +LH:45 end_of_record diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000..cbce1e0 --- /dev/null +++ b/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit cbce1e00305e943aa1661d43f41e5ac72c662b07 diff --git a/remappings.txt b/remappings.txt index 918ed31..2039af3 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,8 @@ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ -erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ +erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/ forge-std/=lib/forge-std/src/ -halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/ +halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/ +openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ openzeppelin-contracts/=lib/openzeppelin-contracts/ +openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..02292a2 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script, console2} from "forge-std/Script.sol"; +import {DeploySimpleStakingChefScript} from "./DeploySimpleStakingChef.s.sol"; +import {DeploySimpleRewarderPerSecScript} from "./DeploySimpleRewarderPerSec.s.sol"; +import {ISimpleStakingChef} from "../src/interfaces/ISimpleStakingChef.sol"; +import {ISimpleRewarderPerSec} from "../src/interfaces/ISimpleRewarderPerSec.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract DeployScript is Script { + function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + // Get parameters from environment + address lpToken = vm.envAddress("LP_TOKEN"); + address rewardToken = vm.envAddress("REWARD_TOKEN"); + uint256 tokenPerSec = vm.envUint("TOKEN_PER_SEC"); + + // Deploy staking chef first + DeploySimpleStakingChefScript stakingDeployer = new DeploySimpleStakingChefScript(); + (, address stakingChefProxy) = stakingDeployer.run(); + + // Deploy rewarder with staking chef address + DeploySimpleRewarderPerSecScript rewarderDeployer = new DeploySimpleRewarderPerSecScript(); + (, address rewarderProxy) = rewarderDeployer.run(lpToken, rewardToken, stakingChefProxy, tokenPerSec); + + // Set up the pool in staking chef + ISimpleStakingChef(stakingChefProxy).add(0, IERC20(lpToken), ISimpleRewarderPerSec(rewarderProxy)); + + vm.stopBroadcast(); + + console2.log("Deployment Addresses:"); + console2.log("SimpleStakingChef Proxy:", stakingChefProxy); + console2.log("SimpleRewarderPerSec Proxy:", rewarderProxy); + } +} diff --git a/script/DeploySimpleRewarderPerSec.s.sol b/script/DeploySimpleRewarderPerSec.s.sol new file mode 100644 index 0000000..b496e70 --- /dev/null +++ b/script/DeploySimpleRewarderPerSec.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script} from "forge-std/Script.sol"; +import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import {SimpleRewarderPerSec} from "../src/rewarders/SimpleRewarderPerSec.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ISimpleStakingChef} from "../src/interfaces/ISimpleStakingChef.sol"; +import {Options} from "openzeppelin-foundry-upgrades/Options.sol"; + +contract DeploySimpleRewarderPerSecScript is Script { + function run(address lpToken, address rewardToken, address mcj, uint256 tokenPerSec) + external + returns (address implementation, address proxy) + { + bytes memory constructorArgs = abi.encode( + IERC20(rewardToken), + IERC20(lpToken), + tokenPerSec, + ISimpleStakingChef(mcj), + false // isNative + ); + + Options memory opts; + opts.constructorData = constructorArgs; + opts.unsafeSkipProxyAdminCheck = true; + opts.unsafeSkipAllChecks = true; + + proxy = Upgrades.deployTransparentProxy( + "SimpleRewarderPerSec.sol", msg.sender, abi.encodeCall(SimpleRewarderPerSec.initialize, ()), opts + ); + + address implementationAddress = Upgrades.getImplementationAddress(proxy); + + return (implementationAddress, proxy); + } +} diff --git a/script/DeploySimpleStakingChef.s.sol b/script/DeploySimpleStakingChef.s.sol new file mode 100644 index 0000000..fb3f2ce --- /dev/null +++ b/script/DeploySimpleStakingChef.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Script} from "forge-std/Script.sol"; +import {SimpleStakingChef} from "../src/SimpleStakingChef.sol"; +import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; + +contract DeploySimpleStakingChefScript is Script { + function run() external returns (address, address) { + //we need to declare the sender's private key here to sign the deploy transaction + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + // Deploy the upgradeable contract + address _proxyAddress = Upgrades.deployTransparentProxy( + "SimpleStakingChef.sol", msg.sender, abi.encodeCall(SimpleStakingChef.initialize, ()) + ); + + // Get the implementation address + address implementationAddress = Upgrades.getImplementationAddress(_proxyAddress); + + vm.stopBroadcast(); + + return (implementationAddress, _proxyAddress); + } +} diff --git a/src/SimpleStakingChef.sol b/src/SimpleStakingChef.sol index d90699e..9aa222b 100644 --- a/src/SimpleStakingChef.sol +++ b/src/SimpleStakingChef.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -14,7 +14,7 @@ import {ISimpleRewarderPerSec} from "./interfaces/ISimpleRewarderPerSec.sol"; /// The idea for this MasterChefVoltV3 (MCJV3) contract is therefore to be the owner of a dummy token /// that is deposited into the MasterChefVoltV2 (MCJV2) contract. /// The allocation point for this pool on MCJV3 is the total allocation point for all pools that receive double incentives. -contract SimpleStakingChef is Ownable, ReentrancyGuard { +contract SimpleStakingChef is OwnableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; using EnumerableSet for EnumerableSet.AddressSet; @@ -55,7 +55,10 @@ contract SimpleStakingChef is Ownable, ReentrancyGuard { error LPAlreadyAdded(); - constructor() Ownable(msg.sender) {} + function initialize() external initializer { + __Ownable_init(msg.sender); + __ReentrancyGuard_init(); + } /// @notice Returns the number of MCJV3 pools. function poolLength() external view returns (uint256 pools) { @@ -130,7 +133,7 @@ contract SimpleStakingChef is Ownable, ReentrancyGuard { PoolInfo memory pool = poolInfo[_pid]; // If it's a double reward farm, we return info about the bonus token if (address(pool.rewarder) != address(0)) { - bonusTokenAddress = address(pool.rewarder.REWARD_TOKEN()); + bonusTokenAddress = address(pool.rewarder.rewardToken()); bonusTokenSymbol = IERC20Metadata(bonusTokenAddress).symbol(); pendingBonusToken = pool.rewarder.pendingTokens(_user); } diff --git a/src/interfaces/ISimpleRewarderPerSec.sol b/src/interfaces/ISimpleRewarderPerSec.sol index 4bd14d0..3af1e10 100644 --- a/src/interfaces/ISimpleRewarderPerSec.sol +++ b/src/interfaces/ISimpleRewarderPerSec.sol @@ -8,5 +8,5 @@ interface ISimpleRewarderPerSec { function pendingTokens(address user) external view returns (uint256 pending); - function REWARD_TOKEN() external view returns (IERC20); + function rewardToken() external view returns (IERC20); } diff --git a/src/interfaces/ISimpleStakingChef.sol b/src/interfaces/ISimpleStakingChef.sol index 29b4655..f3ec6bd 100644 --- a/src/interfaces/ISimpleStakingChef.sol +++ b/src/interfaces/ISimpleStakingChef.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.25; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ISimpleRewarderPerSec} from "./ISimpleRewarderPerSec.sol"; interface ISimpleStakingChef { struct UserInfo { @@ -21,4 +22,6 @@ interface ISimpleStakingChef { function totalAllocPoint() external view returns (uint256); function deposit(uint256 _pid, uint256 _amount) external; + + function add(uint256 _allocPoint, IERC20 _lpToken, ISimpleRewarderPerSec _rewarder) external; } diff --git a/src/rewarders/SimpleRewarderPerSec.sol b/src/rewarders/SimpleRewarderPerSec.sol index dc392ca..c08542d 100644 --- a/src/rewarders/SimpleRewarderPerSec.sol +++ b/src/rewarders/SimpleRewarderPerSec.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ISimpleStakingChef} from "../interfaces/ISimpleStakingChef.sol"; @@ -16,7 +16,7 @@ import {ISimpleStakingChef} from "../interfaces/ISimpleStakingChef.sol"; * 100,000 XYZ and set the block reward accordingly so it's fully distributed after 30 days. * */ -contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { +contract SimpleRewarderPerSec is OwnableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; IERC20 public immutable REWARD_TOKEN; @@ -63,9 +63,7 @@ contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { _; } - constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative) - Ownable(msg.sender) - { + constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative) { if (address(_rewardToken) == address(0)) revert InvalidRewardToken(); if (address(_lpToken) == address(0)) revert InvalidLPToken(); if (address(_mcj) == address(0)) revert InvalidMCJ(); @@ -78,6 +76,11 @@ contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { poolInfo = PoolInfo({lastRewardTimestamp: block.timestamp, accTokenPerShare: 0}); } + function initialize() external initializer { + __Ownable_init(msg.sender); + __ReentrancyGuard_init(); + } + /// @notice Update reward variables of the given poolInfo. /// @return pool Returns the pool that was updated. function updatePool() public returns (PoolInfo memory pool) { @@ -186,6 +189,10 @@ contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { } } + function rewardToken() external view returns (IERC20) { + return REWARD_TOKEN; + } + /// @notice payable function needed to receive AVAX receive() external payable {} } diff --git a/test/SimpleStaking.t.sol b/test/SimpleStaking.t.sol index 105d09b..c01b76e 100644 --- a/test/SimpleStaking.t.sol +++ b/test/SimpleStaking.t.sol @@ -25,6 +25,7 @@ contract SimpleStakingTest is Test { // Deploy main contracts chef = new SimpleStakingChef(); + chef.initialize(); rewarder = new SimpleRewarderPerSec( rewardToken, lpToken, @@ -32,6 +33,7 @@ contract SimpleStakingTest is Test { ISimpleStakingChef(address(chef)), false // not native token ); + rewarder.initialize(); // Setup initial state chef.add(0, lpToken, ISimpleRewarderPerSec(address(rewarder))); From fb17267a4c9ea7091ae2246e64a76765fd7ae644 Mon Sep 17 00:00:00 2001 From: Supeeerpower Date: Thu, 13 Feb 2025 13:31:33 +0200 Subject: [PATCH 7/8] chore: env update --- .env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.env.example b/.env.example index 69affbc..067be3e 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ PRIVATE_KEY= TEST_RPC_URL= +LP_TOKEN= +REWARD_TOKEN= +TOKEN_PER_SEC= From 3edc7ec4f7f61e3e0e19ddf20793435f4a90a726 Mon Sep 17 00:00:00 2001 From: Supeeerpower Date: Thu, 13 Feb 2025 17:18:33 +0200 Subject: [PATCH 8/8] fix: remove upgrade for rewarder --- Makefile | 3 +- coverage-report/index-sort-f.html | 42 ++- coverage-report/index-sort-l.html | 42 ++- coverage-report/index.html | 42 ++- .../SimpleRewarderPerSec.sol.func-c.html | 35 ++- .../SimpleRewarderPerSec.sol.func.html | 37 ++- .../SimpleRewarderPerSec.sol.gcov.html | 275 ++++++++--------- .../rewarders/src/rewarders/index-sort-f.html | 24 +- .../rewarders/src/rewarders/index-sort-l.html | 24 +- .../src/rewarders/src/rewarders/index.html | 24 +- .../src/src/SimpleStakingChef.sol.func-c.html | 6 +- .../src/src/SimpleStakingChef.sol.func.html | 6 +- .../src/src/SimpleStakingChef.sol.gcov.html | 126 ++++---- coverage-report/src/src/index-sort-f.html | 10 +- coverage-report/src/src/index-sort-l.html | 10 +- coverage-report/src/src/index.html | 10 +- .../test/src/test/MockERC20.sol.func-c.html | 82 +++++ .../src/test/src/test/MockERC20.sol.func.html | 82 +++++ .../src/test/src/test/MockERC20.sol.gcov.html | 87 ++++++ .../src/test/src/test/index-sort-f.html | 105 +++++++ .../src/test/src/test/index-sort-l.html | 105 +++++++ coverage-report/src/test/src/test/index.html | 105 +++++++ foundry.toml | 6 +- lcov.info | 291 ++++++++++-------- lib/openzeppelin-contracts | 2 +- lib/openzeppelin-contracts-upgradeable | 2 +- script/Deploy.s.sol | 22 +- script/DeploySimpleRewarderPerSec.s.sol | 27 +- script/DeploySimpleStakingChef.s.sol | 17 +- src/rewarders/SimpleRewarderPerSec.sol | 15 +- test/SimpleStaking.t.sol | 1 - 31 files changed, 1149 insertions(+), 516 deletions(-) create mode 100644 coverage-report/src/test/src/test/MockERC20.sol.func-c.html create mode 100644 coverage-report/src/test/src/test/MockERC20.sol.func.html create mode 100644 coverage-report/src/test/src/test/MockERC20.sol.gcov.html create mode 100644 coverage-report/src/test/src/test/index-sort-f.html create mode 100644 coverage-report/src/test/src/test/index-sort-l.html create mode 100644 coverage-report/src/test/src/test/index.html diff --git a/Makefile b/Makefile index c919281..4d61aa3 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,6 @@ inspect :; forge inspect ${contract} storage-layout --pretty # specify which fork to use. set this in our .env # if we want to test multiple forks in one go, remove this as an argument below FORK_URL := ${TEST_RPC_URL} # BASE_RPC_URL, ETH_RPC_URL, ARBITRUM_RPC_URL -PRIVATE_KEY := ${PRIVATE_KEY} # if we want to run only matching tests, set that here test := test_ @@ -41,7 +40,7 @@ clean :; forge clean format :; forge fmt format-check :; forge fmt --check -deploy :; forge script script/Deploy.s.sol:DeployScript --rpc-url ${FORK_URL} --private-key ${PRIVATE_KEY} --broadcast +deploy :; forge script script/Deploy.s.sol:DeployScript --verify -vvvv --force coverage-html: @echo "Running coverage..." diff --git a/coverage-report/index-sort-f.html b/coverage-report/index-sort-f.html index 4bd4c66..699f957 100644 --- a/coverage-report/index-sort-f.html +++ b/coverage-report/index-sort-f.html @@ -31,18 +31,18 @@ lcov.info Lines: - 90.6 % - 117 - 106 + 89.7 % + 145 + 130 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: - 94.1 % - 17 - 16 + 94.7 % + 19 + 18 @@ -82,14 +82,26 @@ src/rewarders/src/rewarders -
80.4%80.4%
+
79.5%79.5%
- 80.4 % - 56 - 45 - 87.5 % + 79.5 % + 73 + 58 + 88.9 % + 9 8 - 7 + + + src/test/src/test + +
100.0%
+ + 100.0 % + 2 + 2 + 100.0 % + 1 + 1 src/src @@ -97,8 +109,8 @@
100.0%
100.0 % - 61 - 61 + 70 + 70 100.0 % 9 9 diff --git a/coverage-report/index-sort-l.html b/coverage-report/index-sort-l.html index fd89af5..66f7ebe 100644 --- a/coverage-report/index-sort-l.html +++ b/coverage-report/index-sort-l.html @@ -31,18 +31,18 @@ lcov.info Lines: - 90.6 % - 117 - 106 + 89.7 % + 145 + 130 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: - 94.1 % - 17 - 16 + 94.7 % + 19 + 18 @@ -82,14 +82,26 @@ src/rewarders/src/rewarders -
80.4%80.4%
+
79.5%79.5%
- 80.4 % - 56 - 45 - 87.5 % + 79.5 % + 73 + 58 + 88.9 % + 9 8 - 7 + + + src/test/src/test + +
100.0%
+ + 100.0 % + 2 + 2 + 100.0 % + 1 + 1 src/src @@ -97,8 +109,8 @@
100.0%
100.0 % - 61 - 61 + 70 + 70 100.0 % 9 9 diff --git a/coverage-report/index.html b/coverage-report/index.html index f8b85c0..51f47a3 100644 --- a/coverage-report/index.html +++ b/coverage-report/index.html @@ -31,18 +31,18 @@ lcov.info Lines: - 90.6 % - 117 - 106 + 89.7 % + 145 + 130 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: - 94.1 % - 17 - 16 + 94.7 % + 19 + 18 @@ -82,14 +82,14 @@ src/rewarders/src/rewarders -
80.4%80.4%
+
79.5%79.5%
- 80.4 % - 56 - 45 - 87.5 % + 79.5 % + 73 + 58 + 88.9 % + 9 8 - 7 src/src @@ -97,12 +97,24 @@
100.0%
100.0 % - 61 - 61 + 70 + 70 100.0 % 9 9 + + src/test/src/test + +
100.0%
+ + 100.0 % + 2 + 2 + 100.0 % + 1 + 1 +
diff --git a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html index e9ccefb..88620cd 100644 --- a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html +++ b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func-c.html @@ -31,18 +31,18 @@ lcov.info Lines: - 80.4 % - 56 - 45 + 79.5 % + 73 + 58 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: - 87.5 % + 88.9 % + 9 8 - 7 @@ -63,56 +63,63 @@ - SimpleRewarderPerSec.updatePool + SimpleRewarderPerSec.updatePool 0 - SimpleRewarderPerSec.emergencyWithdraw + SimpleRewarderPerSec.emergencyWithdraw 1 - SimpleRewarderPerSec.pendingTokens + SimpleRewarderPerSec.pendingTokens 1 - SimpleRewarderPerSec.rewardToken + SimpleRewarderPerSec.rewardToken 1 - SimpleRewarderPerSec.setRewardRate + SimpleRewarderPerSec.setRewardRate 1 - SimpleRewarderPerSec.balance + SimpleRewarderPerSec.balance 2 - SimpleRewarderPerSec.initialize + SimpleRewarderPerSec.constructor 10 - SimpleRewarderPerSec.onVoltReward + SimpleRewarderPerSec.onVoltReward + + 19 + + + + + SimpleRewarderPerSec.onlyMCJ 19 diff --git a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html index 0a00759..e2b7212 100644 --- a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html +++ b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.func.html @@ -31,18 +31,18 @@ lcov.info Lines: - 80.4 % - 56 - 45 + 79.5 % + 73 + 58 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: - 87.5 % + 88.9 % + 9 8 - 7 @@ -63,56 +63,63 @@ - SimpleRewarderPerSec.balance + SimpleRewarderPerSec.balance 2 - SimpleRewarderPerSec.emergencyWithdraw + SimpleRewarderPerSec.constructor + + 10 + + + + + SimpleRewarderPerSec.emergencyWithdraw 1 - SimpleRewarderPerSec.initialize + SimpleRewarderPerSec.onVoltReward - 10 + 19 - SimpleRewarderPerSec.onVoltReward + SimpleRewarderPerSec.onlyMCJ 19 - SimpleRewarderPerSec.pendingTokens + SimpleRewarderPerSec.pendingTokens 1 - SimpleRewarderPerSec.rewardToken + SimpleRewarderPerSec.rewardToken 1 - SimpleRewarderPerSec.setRewardRate + SimpleRewarderPerSec.setRewardRate 1 - SimpleRewarderPerSec.updatePool + SimpleRewarderPerSec.updatePool 0 diff --git a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html index 49808bd..0aa1a5e 100644 --- a/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html +++ b/coverage-report/src/rewarders/src/rewarders/SimpleRewarderPerSec.sol.gcov.html @@ -31,18 +31,18 @@ lcov.info Lines: - 80.4 % - 56 - 45 + 79.5 % + 73 + 58 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: - 87.5 % + 88.9 % + 9 8 - 7 @@ -63,8 +63,8 @@ 1 : // SPDX-License-Identifier: GPL-3.0 2 : pragma solidity ^0.8.25; 3 : - 4 : import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; - 5 : import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + 4 : import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + 5 : import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 6 : import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 : import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 8 : import {ISimpleStakingChef} from "../interfaces/ISimpleStakingChef.sol"; @@ -78,7 +78,7 @@ 16 : * 100,000 XYZ and set the block reward accordingly so it's fully distributed after 30 days. 17 : * 18 : */ - 19 : contract SimpleRewarderPerSec is OwnableUpgradeable, ReentrancyGuardUpgradeable { + 19 : contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { 20 : using SafeERC20 for IERC20; 21 : 22 : IERC20 public immutable REWARD_TOKEN; @@ -120,144 +120,141 @@ 58 : error InvalidMCJ(); 59 : error TransferFailed(); 60 : - 61 : modifier onlyMCJ() { - 62 : if (msg.sender != address(MCJ)) revert OnlyMCJ(); + 61 19 : modifier onlyMCJ() { + 62 19 : if (msg.sender != address(MCJ)) revert OnlyMCJ(); 63 : _; 64 : } 65 : - 66 : constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative) { - 67 : if (address(_rewardToken) == address(0)) revert InvalidRewardToken(); - 68 : if (address(_lpToken) == address(0)) revert InvalidLPToken(); - 69 : if (address(_mcj) == address(0)) revert InvalidMCJ(); - 70 : - 71 : REWARD_TOKEN = _rewardToken; - 72 : LP_TOKEN = _lpToken; - 73 : tokenPerSec = _tokenPerSec; - 74 : MCJ = _mcj; - 75 : IS_NATIVE = _isNative; - 76 : poolInfo = PoolInfo({lastRewardTimestamp: block.timestamp, accTokenPerShare: 0}); - 77 : } - 78 : - 79 10 : function initialize() external initializer { - 80 20 : __Ownable_init(msg.sender); - 81 20 : __ReentrancyGuard_init(); - 82 : } - 83 : - 84 : /// @notice Update reward variables of the given poolInfo. - 85 : /// @return pool Returns the pool that was updated. - 86 0 : function updatePool() public returns (PoolInfo memory pool) { - 87 40 : pool = poolInfo; + 66 10 : constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative) + 67 : Ownable(msg.sender) + 68 : { + 69 10 : if (address(_rewardToken) == address(0)) revert InvalidRewardToken(); + 70 10 : if (address(_lpToken) == address(0)) revert InvalidLPToken(); + 71 10 : if (address(_mcj) == address(0)) revert InvalidMCJ(); + 72 : + 73 10 : REWARD_TOKEN = _rewardToken; + 74 10 : LP_TOKEN = _lpToken; + 75 10 : tokenPerSec = _tokenPerSec; + 76 10 : MCJ = _mcj; + 77 10 : IS_NATIVE = _isNative; + 78 10 : poolInfo = PoolInfo({lastRewardTimestamp: block.timestamp, accTokenPerShare: 0}); + 79 : } + 80 : + 81 : /// @notice Update reward variables of the given poolInfo. + 82 : /// @return pool Returns the pool that was updated. + 83 0 : function updatePool() public returns (PoolInfo memory pool) { + 84 20 : pool = poolInfo; + 85 : + 86 20 : if (block.timestamp > pool.lastRewardTimestamp) { + 87 2 : uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); 88 : - 89 40 : if (block.timestamp > pool.lastRewardTimestamp) { - 90 6 : uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); - 91 : - 92 4 : if (lpSupply > 0) { - 93 6 : uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; - 94 6 : uint256 tokenReward = timeElapsed * tokenPerSec; - 95 4 : pool.accTokenPerShare = pool.accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); - 96 : } - 97 : - 98 4 : pool.lastRewardTimestamp = block.timestamp; - 99 4 : poolInfo = pool; - 100 : } - 101 : } - 102 : - 103 : /// @notice Sets the distribution reward rate. This will also update the poolInfo. - 104 : /// @param _tokenPerSec The number of tokens to distribute per second - 105 1 : function setRewardRate(uint256 _tokenPerSec) external onlyOwner { - 106 2 : updatePool(); + 89 2 : if (lpSupply > 0) { + 90 2 : uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; + 91 2 : uint256 tokenReward = timeElapsed * tokenPerSec; + 92 2 : pool.accTokenPerShare = pool.accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); + 93 : } + 94 : + 95 2 : pool.lastRewardTimestamp = block.timestamp; + 96 2 : poolInfo = pool; + 97 : } + 98 : } + 99 : + 100 : /// @notice Sets the distribution reward rate. This will also update the poolInfo. + 101 : /// @param _tokenPerSec The number of tokens to distribute per second + 102 1 : function setRewardRate(uint256 _tokenPerSec) external onlyOwner { + 103 1 : updatePool(); + 104 : + 105 1 : uint256 oldRate = tokenPerSec; + 106 1 : tokenPerSec = _tokenPerSec; 107 : - 108 2 : uint256 oldRate = tokenPerSec; - 109 2 : tokenPerSec = _tokenPerSec; + 108 1 : emit RewardRateUpdated(oldRate, _tokenPerSec); + 109 : } 110 : - 111 2 : emit RewardRateUpdated(oldRate, _tokenPerSec); - 112 : } - 113 : - 114 : /// @notice Function called by MasterChefVolt whenever staker claims VOLT harvest. Allows staker to also receive a 2nd reward token. - 115 : /// @param _user Address of user - 116 : /// @param _lpAmount Number of LP tokens the user has - 117 19 : function onVoltReward(address _user, uint256 _lpAmount) external onlyMCJ nonReentrant { - 118 38 : updatePool(); - 119 38 : PoolInfo memory pool = poolInfo; - 120 38 : UserInfo storage user = userInfo[_user]; - 121 38 : uint256 pending; - 122 38 : if (user.amount > 0) { - 123 6 : pending = (user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; - 124 : - 125 3 : if (IS_NATIVE) { - 126 0 : uint256 bal = address(this).balance; - 127 0 : if (pending > bal) { - 128 0 : user.unpaidRewards = pending - bal; - 129 0 : (bool success,) = _user.call{value: bal}(""); - 130 0 : if (!success) revert TransferFailed(); - 131 : } else { - 132 0 : user.unpaidRewards = 0; - 133 0 : (bool success,) = _user.call{value: pending}(""); - 134 0 : if (!success) revert TransferFailed(); - 135 : } - 136 : } else { - 137 9 : uint256 bal = REWARD_TOKEN.balanceOf(address(this)); - 138 6 : if (pending > bal) { - 139 4 : user.unpaidRewards = pending - bal; - 140 4 : REWARD_TOKEN.safeTransfer(_user, bal); - 141 : } else { - 142 2 : user.unpaidRewards = 0; - 143 2 : REWARD_TOKEN.safeTransfer(_user, pending); - 144 : } - 145 : } - 146 : } - 147 : - 148 38 : user.amount = _lpAmount; - 149 38 : user.rewardDebt = user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION; - 150 38 : emit OnReward(_user, pending - user.unpaidRewards); - 151 : } - 152 : - 153 : /// @notice View function to see pending tokens - 154 : /// @param _user Address of user. - 155 : /// @return pending reward for a given user. - 156 1 : function pendingTokens(address _user) external view returns (uint256 pending) { - 157 2 : PoolInfo memory pool = poolInfo; - 158 2 : UserInfo storage user = userInfo[_user]; + 111 : /// @notice Function called by MasterChefVolt whenever staker claims VOLT harvest. Allows staker to also receive a 2nd reward token. + 112 : /// @param _user Address of user + 113 : /// @param _lpAmount Number of LP tokens the user has + 114 19 : function onVoltReward(address _user, uint256 _lpAmount) external onlyMCJ nonReentrant { + 115 19 : updatePool(); + 116 19 : PoolInfo memory pool = poolInfo; + 117 19 : UserInfo storage user = userInfo[_user]; + 118 19 : uint256 pending; + 119 19 : if (user.amount > 0) { + 120 3 : pending = (user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; + 121 : + 122 0 : if (IS_NATIVE) { + 123 0 : uint256 bal = address(this).balance; + 124 0 : if (pending > bal) { + 125 0 : user.unpaidRewards = pending - bal; + 126 0 : (bool success,) = _user.call{value: bal}(""); + 127 0 : if (!success) revert TransferFailed(); + 128 : } else { + 129 0 : user.unpaidRewards = 0; + 130 0 : (bool success,) = _user.call{value: pending}(""); + 131 0 : if (!success) revert TransferFailed(); + 132 : } + 133 : } else { + 134 3 : uint256 bal = REWARD_TOKEN.balanceOf(address(this)); + 135 3 : if (pending > bal) { + 136 2 : user.unpaidRewards = pending - bal; + 137 2 : REWARD_TOKEN.safeTransfer(_user, bal); + 138 : } else { + 139 1 : user.unpaidRewards = 0; + 140 1 : REWARD_TOKEN.safeTransfer(_user, pending); + 141 : } + 142 : } + 143 : } + 144 : + 145 19 : user.amount = _lpAmount; + 146 19 : user.rewardDebt = user.amount * pool.accTokenPerShare / ACC_TOKEN_PRECISION; + 147 19 : emit OnReward(_user, pending - user.unpaidRewards); + 148 : } + 149 : + 150 : /// @notice View function to see pending tokens + 151 : /// @param _user Address of user. + 152 : /// @return pending reward for a given user. + 153 1 : function pendingTokens(address _user) external view returns (uint256 pending) { + 154 1 : PoolInfo memory pool = poolInfo; + 155 1 : UserInfo storage user = userInfo[_user]; + 156 : + 157 1 : uint256 accTokenPerShare = pool.accTokenPerShare; + 158 1 : uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); 159 : - 160 2 : uint256 accTokenPerShare = pool.accTokenPerShare; - 161 3 : uint256 lpSupply = LP_TOKEN.balanceOf(address(MCJ)); - 162 : - 163 4 : if (block.timestamp > pool.lastRewardTimestamp && lpSupply != 0) { - 164 3 : uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; - 165 3 : uint256 tokenReward = timeElapsed * tokenPerSec; - 166 2 : accTokenPerShare = accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); - 167 : } + 160 1 : if (block.timestamp > pool.lastRewardTimestamp && lpSupply != 0) { + 161 1 : uint256 timeElapsed = block.timestamp - pool.lastRewardTimestamp; + 162 1 : uint256 tokenReward = timeElapsed * tokenPerSec; + 163 1 : accTokenPerShare = accTokenPerShare + (tokenReward * ACC_TOKEN_PRECISION / lpSupply); + 164 : } + 165 : + 166 1 : pending = (user.amount * accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; + 167 : } 168 : - 169 2 : pending = (user.amount * accTokenPerShare / ACC_TOKEN_PRECISION) - user.rewardDebt + user.unpaidRewards; - 170 : } - 171 : - 172 : /// @notice In case rewarder is stopped before emissions finished, this function allows - 173 : /// withdrawal of remaining tokens. - 174 1 : function emergencyWithdraw() public onlyOwner { - 175 1 : if (IS_NATIVE) { - 176 0 : (bool success,) = msg.sender.call{value: address(this).balance}(""); - 177 0 : if (!success) revert TransferFailed(); - 178 : } else { - 179 2 : REWARD_TOKEN.safeTransfer(address(msg.sender), REWARD_TOKEN.balanceOf(address(this))); - 180 : } - 181 : } - 182 : - 183 : /// @notice View function to see balance of reward token. - 184 2 : function balance() external view returns (uint256) { - 185 2 : if (IS_NATIVE) { - 186 0 : return address(this).balance; - 187 : } else { - 188 6 : return REWARD_TOKEN.balanceOf(address(this)); - 189 : } - 190 : } - 191 : - 192 1 : function rewardToken() external view returns (IERC20) { - 193 2 : return REWARD_TOKEN; - 194 : } - 195 : - 196 : /// @notice payable function needed to receive AVAX - 197 : receive() external payable {} - 198 : } + 169 : /// @notice In case rewarder is stopped before emissions finished, this function allows + 170 : /// withdrawal of remaining tokens. + 171 1 : function emergencyWithdraw() public onlyOwner { + 172 0 : if (IS_NATIVE) { + 173 0 : (bool success,) = msg.sender.call{value: address(this).balance}(""); + 174 0 : if (!success) revert TransferFailed(); + 175 : } else { + 176 1 : REWARD_TOKEN.safeTransfer(address(msg.sender), REWARD_TOKEN.balanceOf(address(this))); + 177 : } + 178 : } + 179 : + 180 : /// @notice View function to see balance of reward token. + 181 2 : function balance() external view returns (uint256) { + 182 0 : if (IS_NATIVE) { + 183 0 : return address(this).balance; + 184 : } else { + 185 2 : return REWARD_TOKEN.balanceOf(address(this)); + 186 : } + 187 : } + 188 : + 189 1 : function rewardToken() external view returns (IERC20) { + 190 1 : return REWARD_TOKEN; + 191 : } + 192 : + 193 : /// @notice payable function needed to receive AVAX + 194 : receive() external payable {} + 195 : } diff --git a/coverage-report/src/rewarders/src/rewarders/index-sort-f.html b/coverage-report/src/rewarders/src/rewarders/index-sort-f.html index b42207b..ac3880a 100644 --- a/coverage-report/src/rewarders/src/rewarders/index-sort-f.html +++ b/coverage-report/src/rewarders/src/rewarders/index-sort-f.html @@ -31,18 +31,18 @@ lcov.info Lines: - 80.4 % - 56 - 45 + 79.5 % + 73 + 58 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: - 87.5 % + 88.9 % + 9 8 - 7 @@ -82,14 +82,14 @@ SimpleRewarderPerSec.sol -
80.4%80.4%
+
79.5%79.5%
- 80.4 % - 56 - 45 - 87.5 % + 79.5 % + 73 + 58 + 88.9 % + 9 8 - 7 diff --git a/coverage-report/src/rewarders/src/rewarders/index-sort-l.html b/coverage-report/src/rewarders/src/rewarders/index-sort-l.html index 071aa10..a5a5ea9 100644 --- a/coverage-report/src/rewarders/src/rewarders/index-sort-l.html +++ b/coverage-report/src/rewarders/src/rewarders/index-sort-l.html @@ -31,18 +31,18 @@ lcov.info Lines: - 80.4 % - 56 - 45 + 79.5 % + 73 + 58 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: - 87.5 % + 88.9 % + 9 8 - 7 @@ -82,14 +82,14 @@ SimpleRewarderPerSec.sol -
80.4%80.4%
+
79.5%79.5%
- 80.4 % - 56 - 45 - 87.5 % + 79.5 % + 73 + 58 + 88.9 % + 9 8 - 7 diff --git a/coverage-report/src/rewarders/src/rewarders/index.html b/coverage-report/src/rewarders/src/rewarders/index.html index 5cff7a2..89ff1d7 100644 --- a/coverage-report/src/rewarders/src/rewarders/index.html +++ b/coverage-report/src/rewarders/src/rewarders/index.html @@ -31,18 +31,18 @@ lcov.info Lines: - 80.4 % - 56 - 45 + 79.5 % + 73 + 58 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: - 87.5 % + 88.9 % + 9 8 - 7 @@ -82,14 +82,14 @@ SimpleRewarderPerSec.sol -
80.4%80.4%
+
79.5%79.5%
- 80.4 % - 56 - 45 - 87.5 % + 79.5 % + 73 + 58 + 88.9 % + 9 8 - 7 diff --git a/coverage-report/src/src/SimpleStakingChef.sol.func-c.html b/coverage-report/src/src/SimpleStakingChef.sol.func-c.html index b8ee6c0..dd1adc0 100644 --- a/coverage-report/src/src/SimpleStakingChef.sol.func-c.html +++ b/coverage-report/src/src/SimpleStakingChef.sol.func-c.html @@ -32,12 +32,12 @@ Lines: 100.0 % - 61 - 61 + 70 + 70 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: 100.0 % diff --git a/coverage-report/src/src/SimpleStakingChef.sol.func.html b/coverage-report/src/src/SimpleStakingChef.sol.func.html index 42a4ef9..d8e3766 100644 --- a/coverage-report/src/src/SimpleStakingChef.sol.func.html +++ b/coverage-report/src/src/SimpleStakingChef.sol.func.html @@ -32,12 +32,12 @@ Lines: 100.0 % - 61 - 61 + 70 + 70 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: 100.0 % diff --git a/coverage-report/src/src/SimpleStakingChef.sol.gcov.html b/coverage-report/src/src/SimpleStakingChef.sol.gcov.html index f3bcabe..5323a30 100644 --- a/coverage-report/src/src/SimpleStakingChef.sol.gcov.html +++ b/coverage-report/src/src/SimpleStakingChef.sol.gcov.html @@ -32,12 +32,12 @@ Lines: 100.0 % - 61 - 61 + 70 + 70 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: 100.0 % @@ -118,13 +118,13 @@ 56 : error LPAlreadyAdded(); 57 : 58 10 : function initialize() external initializer { - 59 20 : __Ownable_init(msg.sender); - 60 20 : __ReentrancyGuard_init(); + 59 10 : __Ownable_init(msg.sender); + 60 10 : __ReentrancyGuard_init(); 61 : } 62 : 63 : /// @notice Returns the number of MCJV3 pools. 64 1 : function poolLength() external view returns (uint256 pools) { - 65 2 : pools = poolInfo.length; + 65 1 : pools = poolInfo.length; 66 : } 67 : 68 : /// @notice Add a new LP to the pool. Can only be called by the owner. @@ -133,17 +133,17 @@ 71 : /// @param _lpToken Address of the LP ERC-20 token. 72 : /// @param _rewarder Address of the rewarder delegate. 73 10 : function add(uint256 _allocPoint, IERC20 _lpToken, ISimpleRewarderPerSec _rewarder) external onlyOwner { - 74 20 : if (lpTokens.contains(address(_lpToken))) revert LPAlreadyAdded(); + 74 10 : if (lpTokens.contains(address(_lpToken))) revert LPAlreadyAdded(); 75 : // Sanity check to ensure _lpToken is an ERC20 token - 76 20 : _lpToken.balanceOf(address(this)); + 76 10 : _lpToken.balanceOf(address(this)); 77 : // Sanity check if we add a rewarder - 78 40 : if (address(_rewarder) != address(0)) { - 79 20 : _rewarder.onVoltReward(address(0), 0); + 78 10 : if (address(_rewarder) != address(0)) { + 79 10 : _rewarder.onVoltReward(address(0), 0); 80 : } 81 : - 82 20 : uint256 lastRewardTimestamp = block.timestamp; + 82 10 : uint256 lastRewardTimestamp = block.timestamp; 83 : - 84 20 : poolInfo.push( + 84 10 : poolInfo.push( 85 : PoolInfo({ 86 : lpToken: _lpToken, 87 : allocPoint: _allocPoint, @@ -152,8 +152,8 @@ 90 : rewarder: _rewarder 91 : }) 92 : ); - 93 20 : lpTokens.add(address(_lpToken)); - 94 20 : emit Add(poolInfo.length - 1, _allocPoint, _lpToken, _rewarder); + 93 10 : lpTokens.add(address(_lpToken)); + 94 10 : emit Add(poolInfo.length - 1, _allocPoint, _lpToken, _rewarder); 95 : } 96 : 97 : /// @notice Update the given pool's VOLT allocation point and `IRewarder` contract. Can only be called by the owner. @@ -165,14 +165,14 @@ 103 : external 104 : onlyOwner 105 : { - 106 2 : PoolInfo memory pool = poolInfo[_pid]; - 107 2 : pool.allocPoint = _allocPoint; + 106 1 : PoolInfo memory pool = poolInfo[_pid]; + 107 1 : pool.allocPoint = _allocPoint; 108 1 : if (overwrite) { - 109 2 : _rewarder.onVoltReward(address(0), 0); // sanity check - 110 2 : pool.rewarder = _rewarder; + 109 1 : _rewarder.onVoltReward(address(0), 0); // sanity check + 110 1 : pool.rewarder = _rewarder; 111 : } - 112 2 : poolInfo[_pid] = pool; - 113 2 : emit Set(_pid, _allocPoint, overwrite ? _rewarder : pool.rewarder, overwrite); + 112 1 : poolInfo[_pid] = pool; + 113 1 : emit Set(_pid, _allocPoint, overwrite ? _rewarder : pool.rewarder, overwrite); 114 : } 115 : 116 : /// @notice View function to see pending VOLT on frontend. @@ -192,24 +192,24 @@ 130 : uint256 pendingBonusToken 131 : ) 132 : { - 133 2 : PoolInfo memory pool = poolInfo[_pid]; + 133 1 : PoolInfo memory pool = poolInfo[_pid]; 134 : // If it's a double reward farm, we return info about the bonus token - 135 4 : if (address(pool.rewarder) != address(0)) { - 136 2 : bonusTokenAddress = address(pool.rewarder.rewardToken()); - 137 2 : bonusTokenSymbol = IERC20Metadata(bonusTokenAddress).symbol(); - 138 2 : pendingBonusToken = pool.rewarder.pendingTokens(_user); + 135 1 : if (address(pool.rewarder) != address(0)) { + 136 1 : bonusTokenAddress = address(pool.rewarder.rewardToken()); + 137 1 : bonusTokenSymbol = IERC20Metadata(bonusTokenAddress).symbol(); + 138 1 : pendingBonusToken = pool.rewarder.pendingTokens(_user); 139 : } 140 : } 141 : 142 : /// @notice Update reward variables of the given pool. 143 : /// @param pid The index of the pool. See `poolInfo`. 144 1 : function updatePool(uint256 pid) public { - 145 16 : PoolInfo memory pool = poolInfo[pid]; - 146 16 : if (block.timestamp > pool.lastRewardTimestamp) { - 147 6 : uint256 lpSupply = pool.lpToken.balanceOf(address(this)); - 148 4 : pool.lastRewardTimestamp = block.timestamp; - 149 4 : poolInfo[pid] = pool; - 150 4 : emit UpdatePool(pid, pool.lastRewardTimestamp, lpSupply, pool.accVoltPerShare); + 145 8 : PoolInfo memory pool = poolInfo[pid]; + 146 8 : if (block.timestamp > pool.lastRewardTimestamp) { + 147 2 : uint256 lpSupply = pool.lpToken.balanceOf(address(this)); + 148 2 : pool.lastRewardTimestamp = block.timestamp; + 149 2 : poolInfo[pid] = pool; + 150 2 : emit UpdatePool(pid, pool.lastRewardTimestamp, lpSupply, pool.accVoltPerShare); 151 : } 152 : } 153 : @@ -217,67 +217,67 @@ 155 : /// @param pid The index of the pool. See `poolInfo`. 156 : /// @param amount LP token amount to deposit. 157 5 : function deposit(uint256 pid, uint256 amount) external nonReentrant { - 158 10 : updatePool(pid); - 159 10 : PoolInfo memory pool = poolInfo[pid]; - 160 10 : UserInfo storage user = userInfo[pid][msg.sender]; + 158 5 : updatePool(pid); + 159 5 : PoolInfo memory pool = poolInfo[pid]; + 160 5 : UserInfo storage user = userInfo[pid][msg.sender]; 161 : - 162 15 : uint256 balanceBefore = pool.lpToken.balanceOf(address(this)); - 163 10 : pool.lpToken.safeTransferFrom(msg.sender, address(this), amount); - 164 20 : uint256 receivedAmount = pool.lpToken.balanceOf(address(this)) - balanceBefore; + 162 5 : uint256 balanceBefore = pool.lpToken.balanceOf(address(this)); + 163 5 : pool.lpToken.safeTransferFrom(msg.sender, address(this), amount); + 164 5 : uint256 receivedAmount = pool.lpToken.balanceOf(address(this)) - balanceBefore; 165 : 166 : // Effects - 167 10 : user.amount = user.amount + receivedAmount; - 168 10 : user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; + 167 5 : user.amount = user.amount + receivedAmount; + 168 5 : user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; 169 : 170 : // Interactions - 171 10 : ISimpleRewarderPerSec _rewarder = pool.rewarder; - 172 20 : if (address(_rewarder) != address(0)) { - 173 10 : _rewarder.onVoltReward(msg.sender, user.amount); + 171 5 : ISimpleRewarderPerSec _rewarder = pool.rewarder; + 172 5 : if (address(_rewarder) != address(0)) { + 173 5 : _rewarder.onVoltReward(msg.sender, user.amount); 174 : } 175 : - 176 10 : emit Deposit(msg.sender, pid, receivedAmount); + 176 5 : emit Deposit(msg.sender, pid, receivedAmount); 177 : } 178 : 179 : /// @notice Withdraw LP tokens from MCJV3. 180 : /// @param pid The index of the pool. See `poolInfo`. 181 : /// @param amount LP token amount to withdraw. 182 2 : function withdraw(uint256 pid, uint256 amount) external nonReentrant { - 183 4 : updatePool(pid); - 184 4 : PoolInfo memory pool = poolInfo[pid]; - 185 4 : UserInfo storage user = userInfo[pid][msg.sender]; + 183 2 : updatePool(pid); + 184 2 : PoolInfo memory pool = poolInfo[pid]; + 185 2 : UserInfo storage user = userInfo[pid][msg.sender]; 186 : 187 : // Effects - 188 4 : user.amount = user.amount - amount; - 189 4 : user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; + 188 2 : user.amount = user.amount - amount; + 189 2 : user.rewardDebt = user.amount * pool.accVoltPerShare / ACC_TOKEN_PRECISION; 190 : 191 : // Interactions - 192 4 : ISimpleRewarderPerSec _rewarder = pool.rewarder; - 193 8 : if (address(_rewarder) != address(0)) { - 194 4 : _rewarder.onVoltReward(msg.sender, user.amount); + 192 2 : ISimpleRewarderPerSec _rewarder = pool.rewarder; + 193 2 : if (address(_rewarder) != address(0)) { + 194 2 : _rewarder.onVoltReward(msg.sender, user.amount); 195 : } 196 : - 197 4 : pool.lpToken.safeTransfer(msg.sender, amount); + 197 2 : pool.lpToken.safeTransfer(msg.sender, amount); 198 : - 199 4 : emit Withdraw(msg.sender, pid, amount); + 199 2 : emit Withdraw(msg.sender, pid, amount); 200 : } 201 : 202 : /// @notice Withdraw without caring about rewards. EMERGENCY ONLY. 203 : /// @param pid The index of the pool. See `poolInfo`. 204 1 : function emergencyWithdraw(uint256 pid) external nonReentrant { - 205 2 : PoolInfo memory pool = poolInfo[pid]; - 206 2 : UserInfo storage user = userInfo[pid][msg.sender]; - 207 2 : uint256 amount = user.amount; - 208 2 : user.amount = 0; - 209 2 : user.rewardDebt = 0; + 205 1 : PoolInfo memory pool = poolInfo[pid]; + 206 1 : UserInfo storage user = userInfo[pid][msg.sender]; + 207 1 : uint256 amount = user.amount; + 208 1 : user.amount = 0; + 209 1 : user.rewardDebt = 0; 210 : - 211 2 : ISimpleRewarderPerSec _rewarder = pool.rewarder; - 212 4 : if (address(_rewarder) != address(0)) { - 213 2 : _rewarder.onVoltReward(msg.sender, 0); + 211 1 : ISimpleRewarderPerSec _rewarder = pool.rewarder; + 212 1 : if (address(_rewarder) != address(0)) { + 213 1 : _rewarder.onVoltReward(msg.sender, 0); 214 : } 215 : 216 : // Note: transfer can fail or succeed if `amount` is zero. - 217 2 : pool.lpToken.safeTransfer(msg.sender, amount); - 218 2 : emit EmergencyWithdraw(msg.sender, pid, amount); + 217 1 : pool.lpToken.safeTransfer(msg.sender, amount); + 218 1 : emit EmergencyWithdraw(msg.sender, pid, amount); 219 : } 220 : } diff --git a/coverage-report/src/src/index-sort-f.html b/coverage-report/src/src/index-sort-f.html index 61bcfef..fed6bfa 100644 --- a/coverage-report/src/src/index-sort-f.html +++ b/coverage-report/src/src/index-sort-f.html @@ -32,12 +32,12 @@ Lines: 100.0 % - 61 - 61 + 70 + 70 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: 100.0 % @@ -85,8 +85,8 @@
100.0%
100.0 % - 61 - 61 + 70 + 70 100.0 % 9 9 diff --git a/coverage-report/src/src/index-sort-l.html b/coverage-report/src/src/index-sort-l.html index cf48ad6..5278faf 100644 --- a/coverage-report/src/src/index-sort-l.html +++ b/coverage-report/src/src/index-sort-l.html @@ -32,12 +32,12 @@ Lines: 100.0 % - 61 - 61 + 70 + 70 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: 100.0 % @@ -85,8 +85,8 @@
100.0%
100.0 % - 61 - 61 + 70 + 70 100.0 % 9 9 diff --git a/coverage-report/src/src/index.html b/coverage-report/src/src/index.html index 2605c92..fb2ce24 100644 --- a/coverage-report/src/src/index.html +++ b/coverage-report/src/src/index.html @@ -32,12 +32,12 @@ Lines: 100.0 % - 61 - 61 + 70 + 70 Test Date: - 2025-02-13 13:30:09 + 2025-02-13 17:15:49 Functions: 100.0 % @@ -85,8 +85,8 @@
100.0%
100.0 % - 61 - 61 + 70 + 70 100.0 % 9 9 diff --git a/coverage-report/src/test/src/test/MockERC20.sol.func-c.html b/coverage-report/src/test/src/test/MockERC20.sol.func-c.html new file mode 100644 index 0000000..7629f35 --- /dev/null +++ b/coverage-report/src/test/src/test/MockERC20.sol.func-c.html @@ -0,0 +1,82 @@ + + + + + + + LCOV - lcov.info - src/test/src/test/MockERC20.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/test/src/test - MockERC20.sol (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %22
Test Date:2025-02-13 17:15:49Functions:100.0 %11
+
+ +
+ + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
MockERC20.constructor20
+
+
+ + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/test/src/test/MockERC20.sol.func.html b/coverage-report/src/test/src/test/MockERC20.sol.func.html new file mode 100644 index 0000000..65ba3be --- /dev/null +++ b/coverage-report/src/test/src/test/MockERC20.sol.func.html @@ -0,0 +1,82 @@ + + + + + + + LCOV - lcov.info - src/test/src/test/MockERC20.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/test/src/test - MockERC20.sol (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %22
Test Date:2025-02-13 17:15:49Functions:100.0 %11
+
+ +
+ + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
MockERC20.constructor20
+
+
+ + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/test/src/test/MockERC20.sol.gcov.html b/coverage-report/src/test/src/test/MockERC20.sol.gcov.html new file mode 100644 index 0000000..a69b536 --- /dev/null +++ b/coverage-report/src/test/src/test/MockERC20.sol.gcov.html @@ -0,0 +1,87 @@ + + + + + + + LCOV - lcov.info - src/test/src/test/MockERC20.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/test/src/test - MockERC20.sol (source / functions)CoverageTotalHit
Test:lcov.infoLines:100.0 %22
Test Date:2025-02-13 17:15:49Functions:100.0 %11
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : // SPDX-License-Identifier: MIT
+       2              : pragma solidity ^0.8.25;
+       3              : 
+       4              : import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+       5              : 
+       6              : // Mock ERC20 for testing
+       7              : contract MockERC20 is ERC20 {
+       8           20 :     constructor(string memory name, string memory symbol) ERC20(name, symbol) {
+       9           20 :         _mint(msg.sender, 10000 * 10 ** decimals());
+      10              :     }
+      11              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/test/src/test/index-sort-f.html b/coverage-report/src/test/src/test/index-sort-f.html new file mode 100644 index 0000000..4ace071 --- /dev/null +++ b/coverage-report/src/test/src/test/index-sort-f.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - src/test/src/test + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/test/src/testCoverageTotalHit
Test:lcov.infoLines:100.0 %22
Test Date:2025-02-13 17:15:49Functions:100.0 %11
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
MockERC20.sol +
100.0%
+
100.0 %22100.0 %11
+
+
+ + + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/test/src/test/index-sort-l.html b/coverage-report/src/test/src/test/index-sort-l.html new file mode 100644 index 0000000..0b67bb1 --- /dev/null +++ b/coverage-report/src/test/src/test/index-sort-l.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - src/test/src/test + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/test/src/testCoverageTotalHit
Test:lcov.infoLines:100.0 %22
Test Date:2025-02-13 17:15:49Functions:100.0 %11
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
MockERC20.sol +
100.0%
+
100.0 %22100.0 %11
+
+
+ + + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/coverage-report/src/test/src/test/index.html b/coverage-report/src/test/src/test/index.html new file mode 100644 index 0000000..b12a730 --- /dev/null +++ b/coverage-report/src/test/src/test/index.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - src/test/src/test + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/test/src/testCoverageTotalHit
Test:lcov.infoLines:100.0 %22
Test Date:2025-02-13 17:15:49Functions:100.0 %11
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
MockERC20.sol +
100.0%
+
100.0 %22100.0 %11
+
+
+ + + + +
Generated by: LCOV version 2.0-1
+
+ + + diff --git a/foundry.toml b/foundry.toml index 64c6aae..48eb24d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,11 +2,15 @@ src = "src" out = "out" libs = ["node_modules", "lib"] -evm_version = "cancun" +evm_version = "london" optimizer = true optimizer_runs = 10_000_000 solc = "0.8.25" verbosity = 3 +ffi = true +ast = true +build_info = true +extra_output = ["storageLayout"] [profile.ci] fuzz = { runs = 5000 } diff --git a/lcov.info b/lcov.info index a055f24..6bc27bd 100644 --- a/lcov.info +++ b/lcov.info @@ -20,146 +20,185 @@ FNDA:1,SimpleStakingChef.poolLength FNDA:10,SimpleStakingChef.add FNF:9 FNH:9 -DA:59,20 -DA:60,20 -DA:65,2 -DA:74,20 -DA:76,20 -DA:78,40 -DA:79,20 -DA:82,20 -DA:84,20 -DA:93,20 -DA:94,20 -DA:106,2 -DA:107,2 +DA:58,10 +DA:59,10 +DA:60,10 +DA:64,1 +DA:65,1 +DA:73,10 +DA:74,10 +DA:76,10 +DA:78,10 +DA:79,10 +DA:82,10 +DA:84,10 +DA:93,10 +DA:94,10 +DA:102,1 +DA:106,1 +DA:107,1 DA:108,1 -DA:109,2 -DA:110,2 -DA:112,2 -DA:113,2 -DA:133,2 -DA:135,4 -DA:136,2 -DA:137,2 -DA:138,2 -DA:145,16 -DA:146,16 -DA:147,6 -DA:148,4 -DA:149,4 -DA:150,4 -DA:158,10 -DA:159,10 -DA:160,10 -DA:162,15 -DA:163,10 -DA:164,20 -DA:167,10 -DA:168,10 -DA:171,10 -DA:172,20 -DA:173,10 -DA:176,10 -DA:183,4 -DA:184,4 -DA:185,4 -DA:188,4 -DA:189,4 -DA:192,4 -DA:193,8 -DA:194,4 -DA:197,4 -DA:199,4 -DA:205,2 -DA:206,2 -DA:207,2 -DA:208,2 -DA:209,2 -DA:211,2 -DA:212,4 -DA:213,2 -DA:217,2 -DA:218,2 -LF:61 -LH:61 +DA:109,1 +DA:110,1 +DA:112,1 +DA:113,1 +DA:123,1 +DA:133,1 +DA:135,1 +DA:136,1 +DA:137,1 +DA:138,1 +DA:144,1 +DA:145,8 +DA:146,8 +DA:147,2 +DA:148,2 +DA:149,2 +DA:150,2 +DA:157,5 +DA:158,5 +DA:159,5 +DA:160,5 +DA:162,5 +DA:163,5 +DA:164,5 +DA:167,5 +DA:168,5 +DA:171,5 +DA:172,5 +DA:173,5 +DA:176,5 +DA:182,2 +DA:183,2 +DA:184,2 +DA:185,2 +DA:188,2 +DA:189,2 +DA:192,2 +DA:193,2 +DA:194,2 +DA:197,2 +DA:199,2 +DA:204,1 +DA:205,1 +DA:206,1 +DA:207,1 +DA:208,1 +DA:209,1 +DA:211,1 +DA:212,1 +DA:213,1 +DA:217,1 +DA:218,1 +LF:70 +LH:70 end_of_record TN: SF:src/rewarders/SimpleRewarderPerSec.sol -FN:105,SimpleRewarderPerSec.setRewardRate -FN:117,SimpleRewarderPerSec.onVoltReward -FN:156,SimpleRewarderPerSec.pendingTokens -FN:174,SimpleRewarderPerSec.emergencyWithdraw -FN:184,SimpleRewarderPerSec.balance -FN:192,SimpleRewarderPerSec.rewardToken -FN:79,SimpleRewarderPerSec.initialize -FN:86,SimpleRewarderPerSec.updatePool +FN:102,SimpleRewarderPerSec.setRewardRate +FN:114,SimpleRewarderPerSec.onVoltReward +FN:153,SimpleRewarderPerSec.pendingTokens +FN:171,SimpleRewarderPerSec.emergencyWithdraw +FN:181,SimpleRewarderPerSec.balance +FN:189,SimpleRewarderPerSec.rewardToken +FN:61,SimpleRewarderPerSec.onlyMCJ +FN:66,SimpleRewarderPerSec.constructor +FN:83,SimpleRewarderPerSec.updatePool FNDA:1,SimpleRewarderPerSec.setRewardRate FNDA:19,SimpleRewarderPerSec.onVoltReward FNDA:1,SimpleRewarderPerSec.pendingTokens FNDA:1,SimpleRewarderPerSec.emergencyWithdraw FNDA:2,SimpleRewarderPerSec.balance FNDA:1,SimpleRewarderPerSec.rewardToken -FNDA:10,SimpleRewarderPerSec.initialize +FNDA:19,SimpleRewarderPerSec.onlyMCJ +FNDA:10,SimpleRewarderPerSec.constructor FNDA:0,SimpleRewarderPerSec.updatePool -FNF:8 -FNH:7 -DA:80,20 -DA:81,20 -DA:87,40 -DA:89,40 -DA:90,6 -DA:92,4 -DA:93,6 -DA:94,6 -DA:95,4 -DA:98,4 -DA:99,4 -DA:106,2 -DA:108,2 -DA:109,2 -DA:111,2 -DA:118,38 -DA:119,38 -DA:120,38 -DA:121,38 -DA:122,38 -DA:123,6 -DA:125,3 +FNF:9 +FNH:8 +DA:61,19 +DA:62,19 +DA:66,10 +DA:69,10 +DA:70,10 +DA:71,10 +DA:73,10 +DA:74,10 +DA:75,10 +DA:76,10 +DA:77,10 +DA:78,10 +DA:83,0 +DA:84,20 +DA:86,20 +DA:87,2 +DA:89,2 +DA:90,2 +DA:91,2 +DA:92,2 +DA:95,2 +DA:96,2 +DA:102,1 +DA:103,1 +DA:105,1 +DA:106,1 +DA:108,1 +DA:114,19 +DA:115,19 +DA:116,19 +DA:117,19 +DA:118,19 +DA:119,19 +DA:120,3 +DA:122,0 +DA:123,0 +DA:124,0 +DA:125,0 DA:126,0 DA:127,0 -DA:128,0 DA:129,0 DA:130,0 -DA:132,0 -DA:133,0 -DA:134,0 -DA:137,9 -DA:138,6 -DA:139,4 -DA:140,4 -DA:142,2 -DA:143,2 -DA:148,38 -DA:149,38 -DA:150,38 -DA:157,2 -DA:158,2 -DA:160,2 -DA:161,3 -DA:163,4 -DA:164,3 -DA:165,3 -DA:166,2 -DA:169,2 -DA:175,1 -DA:176,0 -DA:177,0 -DA:179,2 +DA:131,0 +DA:134,3 +DA:135,3 +DA:136,2 +DA:137,2 +DA:139,1 +DA:140,1 +DA:145,19 +DA:146,19 +DA:147,19 +DA:153,1 +DA:154,1 +DA:155,1 +DA:157,1 +DA:158,1 +DA:160,1 +DA:161,1 +DA:162,1 +DA:163,1 +DA:166,1 +DA:171,1 +DA:172,0 +DA:173,0 +DA:174,0 +DA:176,1 +DA:181,2 +DA:182,0 +DA:183,0 DA:185,2 -DA:186,0 -DA:188,6 -DA:193,2 -LF:56 -LH:45 +DA:189,1 +DA:190,1 +LF:73 +LH:58 +end_of_record +TN: +SF:src/test/MockERC20.sol +FN:8,MockERC20.constructor +FNDA:20,MockERC20.constructor +FNF:1 +FNH:1 +DA:8,20 +DA:9,20 +LF:2 +LH:2 end_of_record diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index acd4ff7..7ccb79f 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit acd4ff74de833399287ed6b31b4debf6b2b35527 +Subproject commit 7ccb79f6ce7c69b0c0c311235c3ce3bad99dccca diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index 3d5fa5c..5eec6a4 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 3d5fa5c24c411112bab47bec25cfa9ad0af0e6e8 +Subproject commit 5eec6a4983cad8638bea3199d2ea46470b956e9b diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 02292a2..5539482 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -9,7 +9,14 @@ import {ISimpleRewarderPerSec} from "../src/interfaces/ISimpleRewarderPerSec.sol import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract DeployScript is Script { - function run() external { + string public forkUrl; + uint256 public fork; + + function run() public { + forkUrl = vm.envString("TEST_RPC_URL"); + fork = vm.createFork(forkUrl); + vm.selectFork(fork); + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -20,19 +27,18 @@ contract DeployScript is Script { // Deploy staking chef first DeploySimpleStakingChefScript stakingDeployer = new DeploySimpleStakingChefScript(); - (, address stakingChefProxy) = stakingDeployer.run(); + (address chefImplementation, address stakingChefProxy) = stakingDeployer.run(); + console2.log("SimpleStakingChef Proxy:", stakingChefProxy); + console2.log("SimpleStakingChef Implementation:", chefImplementation); // Deploy rewarder with staking chef address DeploySimpleRewarderPerSecScript rewarderDeployer = new DeploySimpleRewarderPerSecScript(); - (, address rewarderProxy) = rewarderDeployer.run(lpToken, rewardToken, stakingChefProxy, tokenPerSec); + address rewarderImplementation = rewarderDeployer.run(lpToken, rewardToken, stakingChefProxy, tokenPerSec); + console2.log("SimpleRewarderPerSec Implementation:", rewarderImplementation); // Set up the pool in staking chef - ISimpleStakingChef(stakingChefProxy).add(0, IERC20(lpToken), ISimpleRewarderPerSec(rewarderProxy)); + ISimpleStakingChef(stakingChefProxy).add(0, IERC20(lpToken), ISimpleRewarderPerSec(rewarderImplementation)); vm.stopBroadcast(); - - console2.log("Deployment Addresses:"); - console2.log("SimpleStakingChef Proxy:", stakingChefProxy); - console2.log("SimpleRewarderPerSec Proxy:", rewarderProxy); } } diff --git a/script/DeploySimpleRewarderPerSec.s.sol b/script/DeploySimpleRewarderPerSec.s.sol index b496e70..f007429 100644 --- a/script/DeploySimpleRewarderPerSec.s.sol +++ b/script/DeploySimpleRewarderPerSec.s.sol @@ -2,36 +2,19 @@ pragma solidity ^0.8.25; import {Script} from "forge-std/Script.sol"; -import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; import {SimpleRewarderPerSec} from "../src/rewarders/SimpleRewarderPerSec.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ISimpleStakingChef} from "../src/interfaces/ISimpleStakingChef.sol"; -import {Options} from "openzeppelin-foundry-upgrades/Options.sol"; contract DeploySimpleRewarderPerSecScript is Script { function run(address lpToken, address rewardToken, address mcj, uint256 tokenPerSec) - external - returns (address implementation, address proxy) + public + returns (address implementation) { - bytes memory constructorArgs = abi.encode( - IERC20(rewardToken), - IERC20(lpToken), - tokenPerSec, - ISimpleStakingChef(mcj), - false // isNative + implementation = address( + new SimpleRewarderPerSec(IERC20(rewardToken), IERC20(lpToken), tokenPerSec, ISimpleStakingChef(mcj), false) ); - Options memory opts; - opts.constructorData = constructorArgs; - opts.unsafeSkipProxyAdminCheck = true; - opts.unsafeSkipAllChecks = true; - - proxy = Upgrades.deployTransparentProxy( - "SimpleRewarderPerSec.sol", msg.sender, abi.encodeCall(SimpleRewarderPerSec.initialize, ()), opts - ); - - address implementationAddress = Upgrades.getImplementationAddress(proxy); - - return (implementationAddress, proxy); + return implementation; } } diff --git a/script/DeploySimpleStakingChef.s.sol b/script/DeploySimpleStakingChef.s.sol index fb3f2ce..53383a3 100644 --- a/script/DeploySimpleStakingChef.s.sol +++ b/script/DeploySimpleStakingChef.s.sol @@ -2,25 +2,18 @@ pragma solidity ^0.8.25; import {Script} from "forge-std/Script.sol"; -import {SimpleStakingChef} from "../src/SimpleStakingChef.sol"; import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import {SimpleStakingChef} from "../src/SimpleStakingChef.sol"; contract DeploySimpleStakingChefScript is Script { - function run() external returns (address, address) { - //we need to declare the sender's private key here to sign the deploy transaction - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - + function run() public returns (address implementation, address proxy) { // Deploy the upgradeable contract - address _proxyAddress = Upgrades.deployTransparentProxy( + proxy = Upgrades.deployTransparentProxy( "SimpleStakingChef.sol", msg.sender, abi.encodeCall(SimpleStakingChef.initialize, ()) ); - // Get the implementation address - address implementationAddress = Upgrades.getImplementationAddress(_proxyAddress); - - vm.stopBroadcast(); + implementation = Upgrades.getImplementationAddress(proxy); - return (implementationAddress, _proxyAddress); + return (implementation, proxy); } } diff --git a/src/rewarders/SimpleRewarderPerSec.sol b/src/rewarders/SimpleRewarderPerSec.sol index c08542d..c4b583a 100644 --- a/src/rewarders/SimpleRewarderPerSec.sol +++ b/src/rewarders/SimpleRewarderPerSec.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.25; -import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ISimpleStakingChef} from "../interfaces/ISimpleStakingChef.sol"; @@ -16,7 +16,7 @@ import {ISimpleStakingChef} from "../interfaces/ISimpleStakingChef.sol"; * 100,000 XYZ and set the block reward accordingly so it's fully distributed after 30 days. * */ -contract SimpleRewarderPerSec is OwnableUpgradeable, ReentrancyGuardUpgradeable { +contract SimpleRewarderPerSec is Ownable, ReentrancyGuard { using SafeERC20 for IERC20; IERC20 public immutable REWARD_TOKEN; @@ -63,7 +63,9 @@ contract SimpleRewarderPerSec is OwnableUpgradeable, ReentrancyGuardUpgradeable _; } - constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative) { + constructor(IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerSec, ISimpleStakingChef _mcj, bool _isNative) + Ownable(msg.sender) + { if (address(_rewardToken) == address(0)) revert InvalidRewardToken(); if (address(_lpToken) == address(0)) revert InvalidLPToken(); if (address(_mcj) == address(0)) revert InvalidMCJ(); @@ -76,11 +78,6 @@ contract SimpleRewarderPerSec is OwnableUpgradeable, ReentrancyGuardUpgradeable poolInfo = PoolInfo({lastRewardTimestamp: block.timestamp, accTokenPerShare: 0}); } - function initialize() external initializer { - __Ownable_init(msg.sender); - __ReentrancyGuard_init(); - } - /// @notice Update reward variables of the given poolInfo. /// @return pool Returns the pool that was updated. function updatePool() public returns (PoolInfo memory pool) { diff --git a/test/SimpleStaking.t.sol b/test/SimpleStaking.t.sol index c01b76e..770fdf8 100644 --- a/test/SimpleStaking.t.sol +++ b/test/SimpleStaking.t.sol @@ -33,7 +33,6 @@ contract SimpleStakingTest is Test { ISimpleStakingChef(address(chef)), false // not native token ); - rewarder.initialize(); // Setup initial state chef.add(0, lpToken, ISimpleRewarderPerSec(address(rewarder)));