From 6c6b7d79654fc502a4b0325fd7a1b7f7fa1f6f4b Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Sat, 6 May 2023 19:43:49 +0200 Subject: [PATCH 01/19] chore: forge init --- foundry.toml | 15 +++++++++++++++ script/Counter.s.sol | 12 ++++++++++++ src/Counter.sol | 14 ++++++++++++++ test/Counter.t.sol | 24 ++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 foundry.toml create mode 100644 script/Counter.s.sol create mode 100644 src/Counter.sol create mode 100644 test/Counter.t.sol diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 00000000..b402902d --- /dev/null +++ b/foundry.toml @@ -0,0 +1,15 @@ +[profile.default] +src = 'src' +out = 'out' +libs = ['node_modules'] +remappings = [ + '@ensdomains/=node_modules/@ensdomains/', + '@openzeppelin/=node_modules/@openzeppelin/', + '@solidity-parser/=node_modules/truffle-flattener/node_modules/@solidity-parser/', + '@uniswap/=node_modules/@uniswap/', + 'eth-gas-reporter/=node_modules/eth-gas-reporter/', + 'hardhat-deploy/=node_modules/hardhat-deploy/', + 'hardhat/=node_modules/hardhat/', +] + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/script/Counter.s.sol b/script/Counter.s.sol new file mode 100644 index 00000000..0e546aba --- /dev/null +++ b/script/Counter.s.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "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 new file mode 100644 index 00000000..aded7997 --- /dev/null +++ b/src/Counter.sol @@ -0,0 +1,14 @@ +// 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/test/Counter.t.sol b/test/Counter.t.sol new file mode 100644 index 00000000..30235e8a --- /dev/null +++ b/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testIncrement() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testSetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} From c0a4e05365a9fa332193a476eb1249c348066eb4 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Sat, 6 May 2023 19:43:49 +0200 Subject: [PATCH 02/19] forge install: forge-std v1.5.5 --- .gitmodules | 4 ++++ lib/forge-std | 1 + 2 files changed, 5 insertions(+) create mode 100644 .gitmodules create mode 160000 lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e977ca25 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std + branch = v1.5.5 diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 00000000..73d44ec7 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 73d44ec7d124e3831bc5f832267889ffb6f9bc3f From 4b2b0e273f556839ac37317b257d9e31f948bf40 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Sat, 6 May 2023 19:45:16 +0200 Subject: [PATCH 03/19] remove unused files --- script/Counter.s.sol | 12 ------------ src/Counter.sol | 14 -------------- test/Counter.t.sol | 24 ------------------------ 3 files changed, 50 deletions(-) delete mode 100644 script/Counter.s.sol delete mode 100644 src/Counter.sol delete mode 100644 test/Counter.t.sol diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index 0e546aba..00000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "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 aded7997..00000000 --- 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/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 30235e8a..00000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function testIncrement() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testSetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} From a7677e53338eed9cec81ef4c4fe550c9c7caff4a Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Sat, 6 May 2023 19:45:23 +0200 Subject: [PATCH 04/19] update foundry params --- foundry.toml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/foundry.toml b/foundry.toml index b402902d..d0441b87 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,7 +1,10 @@ [profile.default] -src = 'src' +src = 'contracts' out = 'out' -libs = ['node_modules'] +libs = ["node_modules", "lib"] +test = 'test/foundry' +cache_path = 'forge-cache' + remappings = [ '@ensdomains/=node_modules/@ensdomains/', '@openzeppelin/=node_modules/@openzeppelin/', @@ -12,4 +15,16 @@ remappings = [ 'hardhat/=node_modules/hardhat/', ] +[rpc_endpoints] +avalanche = "https://api.avax.network/ext/bc/C/rpc" +fuji = "https://api.avax-test.network/ext/bc/C/rpc" +arbitrum = "https://arb1.arbitrum.io/rpc" +bsc = "https://bscrpc.com" + +[etherscan] +arbitrum = { key = "${ARBISCAN_API_KEY}", chain = 42161 } +avalanche = { key = "${SNOWTRACE_API_KEY}", chain = 43114 } +arbitrum_goerli = { key = "${ARBISCAN_API_KEY}", chain = 421613 } +fuji = { key = "${SNOWTRACE_API_KEY}", chain = 43113 } + # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file From 12f74d2e6a13d7db1d2377fff63e5ddb95020cf6 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Sat, 6 May 2023 19:48:32 +0200 Subject: [PATCH 05/19] add sweep function --- contracts/StableJoeStaking.sol | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index 46177e3c..4f809aad 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -87,6 +87,9 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { /// @notice Emitted when owner removes a token from the reward tokens list event RewardTokenRemoved(address token); + /// @notice Emitted when owner sweeps a token + event TokenSwept(address token, address to, uint256 amount); + /** * @notice Initialize a new StableJoeStaking contract * @dev This contract needs to receive an ERC20 `_rewardToken` in order to distribute them @@ -328,6 +331,20 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { lastRewardBalance[_token] = _rewardBalance; } + /** + * @notice Sweep token to the `_to` address + * @param _token The address of the token to sweep + * @param _to The address that will receive `_token` balance + */ + function sweep(IERC20Upgradeable _token, address _to) external onlyOwner { + require(!isRewardToken[_token] && address(_token) != address(joe), "StableJoeStaking: token can't be swept"); + + uint256 _balance = _token.balanceOf(address(this)); + _token.safeTransfer(_to, _balance); + + emit TokenSwept(address(_token), _to, _balance); + } + /** * @notice Safe token transfer function, just in case if rounding error * causes pool to not have enough reward tokens From a1b430ec3c30dd6e33a58ea7273871444f79f476 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:21:23 +0200 Subject: [PATCH 06/19] update gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 546c3d63..7fc6b899 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ contracts/.deps/ .idea .openzeppelin .vscode + +out +broadcast +forge-cache/* \ No newline at end of file From ac3ac80419c0adc560f5cb0eea7a4029487df0e6 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:22:53 +0200 Subject: [PATCH 07/19] format --- contracts/StableJoeStaking.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index 4f809aad..77b8e3ba 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -149,15 +149,17 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { .div(ACC_REWARD_PER_SHARE_PRECISION) .sub(_previousRewardDebt); if (_pending != 0) { - safeTokenTransfer(_token, _msgSender(), _pending); + _safeTokenTransfer(_token, _msgSender(), _pending); emit ClaimReward(_msgSender(), address(_token), _pending); } } } internalJoeBalance = internalJoeBalance.add(_amountMinusFee); - joe.safeTransferFrom(_msgSender(), feeCollector, _fee); - joe.safeTransferFrom(_msgSender(), address(this), _amountMinusFee); + + if (_fee > 0) joe.safeTransferFrom(_msgSender(), feeCollector, _fee); + if (_amountMinusFee > 0) joe.safeTransferFrom(_msgSender(), address(this), _amountMinusFee); + emit Deposit(_msgSender(), _amountMinusFee, _fee); } @@ -276,7 +278,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { user.rewardDebt[_token] = _newAmount.mul(accRewardPerShare[_token]).div(ACC_REWARD_PER_SHARE_PRECISION); if (_pending != 0) { - safeTokenTransfer(_token, _msgSender(), _pending); + _safeTokenTransfer(_token, _msgSender(), _pending); emit ClaimReward(_msgSender(), address(_token), _pending); } } @@ -352,7 +354,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { * @param _to The address that will receive `_amount` `rewardToken` * @param _amount The amount to send to `_to` */ - function safeTokenTransfer( + function _safeTokenTransfer( IERC20Upgradeable _token, address _to, uint256 _amount From 420da64a72ecf3fb214ad0824446c8feb5765de7 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:24:07 +0200 Subject: [PATCH 08/19] revert on 0 --- contracts/StableJoeStaking.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index 77b8e3ba..d07f8089 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -126,6 +126,8 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { * @param _amount The amount of JOE to deposit */ function deposit(uint256 _amount) external { + require(_amount > 0, "StableJoeStaking: can't deposit 0"); + UserInfo storage user = userInfo[_msgSender()]; uint256 _fee = _amount.mul(depositFeePercent).div(DEPOSIT_FEE_PERCENT_PRECISION); @@ -259,6 +261,8 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { * @param _amount The amount of JOE to withdraw */ function withdraw(uint256 _amount) external { + require(_amount > 0, "StableJoeStaking: can't withdraw 0"); + UserInfo storage user = userInfo[_msgSender()]; uint256 _previousAmount = user.amount; require(_amount <= _previousAmount, "StableJoeStaking: withdraw amount exceeds balance"); @@ -296,6 +300,9 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { UserInfo storage user = userInfo[_msgSender()]; uint256 _amount = user.amount; + + require(_amount > 0, "StableJoeStaking: can't withdraw 0"); + user.amount = 0; uint256 _len = rewardTokens.length; for (uint256 i; i < _len; i++) { @@ -342,6 +349,9 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { require(!isRewardToken[_token] && address(_token) != address(joe), "StableJoeStaking: token can't be swept"); uint256 _balance = _token.balanceOf(address(this)); + + require(_balance > 0, "StableJoeStaking: can't sweep 0"); + _token.safeTransfer(_to, _balance); emit TokenSwept(address(_token), _to, _balance); From 158705cb5c4b72a37bdc4d2aec2f20179ee0474c Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:24:35 +0200 Subject: [PATCH 09/19] prevent re-adding a reward token, and gas opt --- contracts/StableJoeStaking.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index d07f8089..ebcbc6b6 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -195,9 +195,11 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { "StableJoeStaking: token can't be added" ); require(rewardTokens.length < 25, "StableJoeStaking: list of token too big"); + require(accRewardPerShare[_rewardToken] == 0, "StableJoeStaking: reward token can't be re-added"); + rewardTokens.push(_rewardToken); isRewardToken[_rewardToken] = true; - updateReward(_rewardToken); + emit RewardTokenAdded(address(_rewardToken)); } @@ -207,7 +209,6 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { */ function removeRewardToken(IERC20Upgradeable _rewardToken) external onlyOwner { require(isRewardToken[_rewardToken], "StableJoeStaking: token can't be removed"); - updateReward(_rewardToken); isRewardToken[_rewardToken] = false; uint256 _len = rewardTokens.length; for (uint256 i; i < _len; i++) { From 9c0db25e825aee60ba093e410ac2edeabb3c040f Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:26:43 +0200 Subject: [PATCH 10/19] gas optimisation, keeping storage slot ordering --- contracts/StableJoeStaking.sol | 48 ++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index ebcbc6b6..565503b1 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -40,28 +40,45 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { */ } - IERC20Upgradeable public joe; + // @dev gap to keep the storage ordering, replace `IERC20Upgradeable public joe;` + uint256[1] private __gap0; + + /// @notice The address of the JOE token + IERC20Upgradeable public immutable joe; /// @dev Internal balance of JOE, this gets updated on user deposits / withdrawals /// this allows to reward users with JOE uint256 public internalJoeBalance; + /// @notice Array of tokens that users can claim IERC20Upgradeable[] public rewardTokens; + + /// @notice Mapping to check if a token is a reward token mapping(IERC20Upgradeable => bool) public isRewardToken; + /// @notice Last reward balance of `token` mapping(IERC20Upgradeable => uint256) public lastRewardBalance; + // @notice The address where deposit fees will be sent address public feeCollector; - /// @notice The deposit fee, scaled to `DEPOSIT_FEE_PERCENT_PRECISION` - uint256 public depositFeePercent; + uint88 public depositFeePercent; + + // @dev gap to keep the storage ordering, replace `uint256 public depositFeePercent;` + // and `uint256 public DEPOSIT_FEE_PERCENT_PRECISION;` + uint256[2] private __gap1; + /// @notice The precision of `depositFeePercent` - uint256 public DEPOSIT_FEE_PERCENT_PRECISION; + uint256 public constant DEPOSIT_FEE_PERCENT_PRECISION = 1e18; /// @notice Accumulated `token` rewards per share, scaled to `ACC_REWARD_PER_SHARE_PRECISION` mapping(IERC20Upgradeable => uint256) public accRewardPerShare; + + // @dev gap to keep the storage ordering, replace `uint256 public DEPOSIT_FEE_PERCENT_PRECISION;` + uint256[1] private __gap3; + /// @notice The precision of `accRewardPerShare` - uint256 public ACC_REWARD_PER_SHARE_PRECISION; + uint256 public constant ACC_REWARD_PER_SHARE_PRECISION = 1e24; /// @dev Info of each user that stakes JOE mapping(address => UserInfo) private userInfo; @@ -90,35 +107,38 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { /// @notice Emitted when owner sweeps a token event TokenSwept(address token, address to, uint256 amount); + /** + * @notice Construct a new StableJoeStaking contract + * @param _joe The address of the JOE token + */ + constructor(IERC20Upgradeable _joe) initializer { + require(address(_joe) != address(0), "StableJoeStaking: joe can't be address(0)"); + + joe = _joe; + } + /** * @notice Initialize a new StableJoeStaking contract * @dev This contract needs to receive an ERC20 `_rewardToken` in order to distribute them - * (with MoneyMaker in our case) * @param _rewardToken The address of the ERC20 reward token - * @param _joe The address of the JOE token * @param _feeCollector The address where deposit fees will be sent * @param _depositFeePercent The deposit fee percent, scalled to 1e18, e.g. 3% is 3e16 */ function initialize( IERC20Upgradeable _rewardToken, - IERC20Upgradeable _joe, address _feeCollector, - uint256 _depositFeePercent + uint88 _depositFeePercent ) external initializer { __Ownable_init(); require(address(_rewardToken) != address(0), "StableJoeStaking: reward token can't be address(0)"); - require(address(_joe) != address(0), "StableJoeStaking: joe can't be address(0)"); require(_feeCollector != address(0), "StableJoeStaking: fee collector can't be address(0)"); require(_depositFeePercent <= 5e17, "StableJoeStaking: max deposit fee can't be greater than 50%"); - joe = _joe; depositFeePercent = _depositFeePercent; feeCollector = _feeCollector; isRewardToken[_rewardToken] = true; rewardTokens.push(_rewardToken); - DEPOSIT_FEE_PERCENT_PRECISION = 1e18; - ACC_REWARD_PER_SHARE_PRECISION = 1e24; } /** @@ -225,7 +245,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { * @notice Set the deposit fee percent * @param _depositFeePercent The new deposit fee percent */ - function setDepositFeePercent(uint256 _depositFeePercent) external onlyOwner { + function setDepositFeePercent(uint88 _depositFeePercent) external onlyOwner { require(_depositFeePercent <= 5e17, "StableJoeStaking: deposit fee can't be greater than 50%"); uint256 oldFee = depositFeePercent; depositFeePercent = _depositFeePercent; From 7d4ccc40464d5816982b48949cd4811e313d91d7 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:27:15 +0200 Subject: [PATCH 11/19] reentrancy guard --- contracts/StableJoeStaking.sol | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index 565503b1..de66a0f4 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -63,6 +63,8 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { address public feeCollector; /// @notice The deposit fee, scaled to `DEPOSIT_FEE_PERCENT_PRECISION` uint88 public depositFeePercent; + /// @notice Reentrancy guard + bool public reentrant; // @dev gap to keep the storage ordering, replace `uint256 public depositFeePercent;` // and `uint256 public DEPOSIT_FEE_PERCENT_PRECISION;` @@ -107,6 +109,16 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { /// @notice Emitted when owner sweeps a token event TokenSwept(address token, address to, uint256 amount); + /** + * @notice Reentrancy guard + */ + modifier nonReentrant() { + require(!reentrant, "StableJoeStaking: reentrant call"); + reentrant = true; + _; + reentrant = false; + } + /** * @notice Construct a new StableJoeStaking contract * @param _joe The address of the JOE token @@ -145,7 +157,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { * @notice Deposit JOE for reward token allocation * @param _amount The amount of JOE to deposit */ - function deposit(uint256 _amount) external { + function deposit(uint256 _amount) external nonReentrant { require(_amount > 0, "StableJoeStaking: can't deposit 0"); UserInfo storage user = userInfo[_msgSender()]; @@ -281,7 +293,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { * @notice Withdraw JOE and harvest the rewards * @param _amount The amount of JOE to withdraw */ - function withdraw(uint256 _amount) external { + function withdraw(uint256 _amount) external nonReentrant { require(_amount > 0, "StableJoeStaking: can't withdraw 0"); UserInfo storage user = userInfo[_msgSender()]; @@ -317,7 +329,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { /** * @notice Withdraw without caring about rewards. EMERGENCY ONLY */ - function emergencyWithdraw() external { + function emergencyWithdraw() external nonReentrant { UserInfo storage user = userInfo[_msgSender()]; uint256 _amount = user.amount; From ad15dbe86073d69c7b89f1208d580186d74a9c73 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:29:15 +0200 Subject: [PATCH 12/19] lock updateReward function internally, and natspec updates --- contracts/StableJoeStaking.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index de66a0f4..2ffce621 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -15,7 +15,7 @@ import "@openzeppelin/contracts-upgradeable/token/ERC20/SafeERC20Upgradeable.sol * harvests. Users deposit JOE and receive a share of what has been sent by MoneyMaker based on their participation of * the total deposited JOE. It is similar to a MasterChef, but we allow for claiming of different reward tokens * (in case at some point we wish to change the stablecoin rewarded). - * Every time `updateReward(token)` is called, We distribute the balance of that tokens as rewards to users that are + * Every time `_updateReward(token)` is called, We distribute the balance of that tokens as rewards to users that are * currently staking inside this contract, and they can claim it using `withdraw(0)` */ contract StableJoeStaking is Initializable, OwnableUpgradeable { @@ -172,7 +172,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { uint256 _len = rewardTokens.length; for (uint256 i; i < _len; i++) { IERC20Upgradeable _token = rewardTokens[i]; - updateReward(_token); + _updateReward(_token); uint256 _previousRewardDebt = user.rewardDebt[_token]; user.rewardDebt[_token] = _newAmount.mul(accRewardPerShare[_token]).div(ACC_REWARD_PER_SHARE_PRECISION); @@ -306,7 +306,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { if (_previousAmount != 0) { for (uint256 i; i < _len; i++) { IERC20Upgradeable _token = rewardTokens[i]; - updateReward(_token); + _updateReward(_token); uint256 _pending = _previousAmount .mul(accRewardPerShare[_token]) @@ -348,11 +348,11 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { } /** - * @notice Update reward variables + * @dev Update reward variables + * Needs to be called before any deposit or withdrawal * @param _token The address of the reward token - * @dev Needs to be called before any deposit or withdrawal */ - function updateReward(IERC20Upgradeable _token) public { + function _updateReward(IERC20Upgradeable _token) internal { require(isRewardToken[_token], "StableJoeStaking: wrong reward token"); uint256 _totalJoe = internalJoeBalance; @@ -391,7 +391,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { } /** - * @notice Safe token transfer function, just in case if rounding error + * @dev Safe token transfer function, just in case if rounding error * causes pool to not have enough reward tokens * @param _token The address of then token to transfer * @param _to The address that will receive `_amount` `rewardToken` From d878c721645051381777749024a6c89db816b6bb Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:45:06 +0200 Subject: [PATCH 13/19] allow deposit and withdraw 0 for backward comp and claim --- contracts/StableJoeStaking.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index 2ffce621..9f1b34a1 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -158,8 +158,6 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { * @param _amount The amount of JOE to deposit */ function deposit(uint256 _amount) external nonReentrant { - require(_amount > 0, "StableJoeStaking: can't deposit 0"); - UserInfo storage user = userInfo[_msgSender()]; uint256 _fee = _amount.mul(depositFeePercent).div(DEPOSIT_FEE_PERCENT_PRECISION); @@ -294,8 +292,6 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { * @param _amount The amount of JOE to withdraw */ function withdraw(uint256 _amount) external nonReentrant { - require(_amount > 0, "StableJoeStaking: can't withdraw 0"); - UserInfo storage user = userInfo[_msgSender()]; uint256 _previousAmount = user.amount; require(_amount <= _previousAmount, "StableJoeStaking: withdraw amount exceeds balance"); From dd0b87338c1bc24062a0f5803747ef42d2e62e91 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:46:51 +0200 Subject: [PATCH 14/19] test upgrade sjoe --- test/foundry/UpgradeSJoe.t.sol | 177 +++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 test/foundry/UpgradeSJoe.t.sol diff --git a/test/foundry/UpgradeSJoe.t.sol b/test/foundry/UpgradeSJoe.t.sol new file mode 100644 index 00000000..42a8cf4f --- /dev/null +++ b/test/foundry/UpgradeSJoe.t.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +pragma experimental ABIEncoderV2; +pragma solidity 0.7.6; + +import "forge-std/Test.sol"; + +import "@openzeppelin/contracts/proxy/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/TransparentUpgradeableProxy.sol"; + +import "../../contracts/StableJoeStaking.sol"; + +contract TestUpgradeSJoe is Test { + address constant joe = 0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd; + address constant wavax = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; + + address constant owner = 0x2fbB61a10B96254900C03F1644E9e1d2f5E76DD2; + address constant joePOL = 0x3876183b75916e20d2ADAB202D1A3F9e9bf320ad; + + ProxyAdmin constant defaultProxyAdmin = ProxyAdmin(0x246ABeC8f8a542E892934232DB3Fd97A61E3193c); + StableJoeStaking constant sjoe = StableJoeStaking(0x1a731B2299E22FbAC282E7094EdA41046343Cb51); + + StableJoeStaking imp; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl("avalanche"), 35393036); + + imp = new StableJoeStaking(IERC20Upgradeable(joe)); + } + + function test_CantInitialize() public { + vm.expectRevert("Initializable: contract is already initialized"); + imp.initialize( + IERC20Upgradeable(address(0)), + address(0), + 0 + ); + + _upgrade(); + + vm.expectRevert("Initializable: contract is already initialized"); + imp.initialize( + IERC20Upgradeable(address(0)), + address(0), + 0 + ); + } + + function test_VerifyStorage() public { + bytes32[] memory slots = new bytes32[](50); + + uint256 i; + + slots[i++] = bytes32(uint256(uint160(address(sjoe.joe())))); + slots[i++] = bytes32(sjoe.internalJoeBalance()); + + uint256 l = sjoe.rewardTokensLength(); + slots[i++] = bytes32(l); + + for(uint256 ii = 0; ii < l; ii++) { + IERC20Upgradeable token = sjoe.rewardTokens(ii); + + slots[i++] = bytes32(uint256(uint160(address(token)))); + slots[i++] = bytes32(sjoe.lastRewardBalance(token)); + slots[i++] = bytes32(sjoe.accRewardPerShare(token)); + + (uint256 amount, uint256 rewardDebt) = sjoe.getUserInfo(joePOL, token); + + assert(amount > 0); + assert(rewardDebt > 0); + + slots[i++] = bytes32(amount); + slots[i++] = bytes32(rewardDebt); + } + + slots[i++] = bytes32(sjoe.DEPOSIT_FEE_PERCENT_PRECISION()); + slots[i++] = bytes32(sjoe.ACC_REWARD_PER_SHARE_PRECISION()); + + _upgrade(); + + uint256 j; + + assertEq(slots[j++], bytes32(uint256(uint160(address(sjoe.joe())))), "test_VerifyStorage::1"); + assertEq(slots[j++], bytes32(sjoe.internalJoeBalance()), "test_VerifyStorage::2"); + assertEq(slots[j++], bytes32(sjoe.rewardTokensLength()), "test_VerifyStorage::3"); + + for(uint256 jj = 0; jj < l; jj++) { + IERC20Upgradeable token = sjoe.rewardTokens(jj); + + assertEq(slots[j++], bytes32(uint256(uint160(address(token)))), "test_VerifyStorage::4"); + assertEq(slots[j++], bytes32(sjoe.lastRewardBalance(token)), "test_VerifyStorage::5"); + assertEq(slots[j++], bytes32(sjoe.accRewardPerShare(token)), "test_VerifyStorage::6"); + + (uint256 amount, uint256 rewardDebt) = sjoe.getUserInfo(joePOL, token); + + assertEq(slots[j++], bytes32(amount), "test_VerifyStorage::7"); + assertEq(slots[j++], bytes32(rewardDebt), "test_VerifyStorage::8"); + } + + assertEq(slots[j++], bytes32(sjoe.DEPOSIT_FEE_PERCENT_PRECISION()), "test_VerifyStorage::9"); + assertEq(slots[j++], bytes32(sjoe.ACC_REWARD_PER_SHARE_PRECISION()), "test_VerifyStorage::10"); + + assertEq(j, i, "test_VerifyStorage::11"); + } + + function test_Sweep() public { + _upgrade(); + + vm.expectRevert("Ownable: caller is not the owner"); + sjoe.sweep(IERC20Upgradeable(joe), address(this)); + + vm.startPrank(owner); + + vm.expectRevert("StableJoeStaking: token can't be swept"); + sjoe.sweep(IERC20Upgradeable(joe), address(this)); + + IERC20Upgradeable rewardToken = IERC20Upgradeable(sjoe.rewardTokens(0)); + + vm.expectRevert("StableJoeStaking: token can't be swept"); + sjoe.sweep(rewardToken, address(this)); + + vm.expectRevert("StableJoeStaking: can't sweep 0"); + sjoe.sweep(IERC20Upgradeable(wavax), address(this)); + + deal(wavax, address(sjoe), 1e18); + + assertEq(IERC20Upgradeable(wavax).balanceOf(address(this)), 0, "test_Sweep::1"); + + sjoe.sweep(IERC20Upgradeable(wavax), address(this)); + + assertEq(IERC20Upgradeable(wavax).balanceOf(address(this)), 1e18, "test_Sweep::2"); + + sjoe.removeRewardToken(rewardToken); + + assertEq(rewardToken.balanceOf(address(this)), 0, "test_Sweep::3"); + + sjoe.sweep(rewardToken, address(this)); + + assertGt(rewardToken.balanceOf(address(this)), 0, "test_Sweep::4"); + + vm.stopPrank(); + } + + function test_ReAddRewardToken() public { + _upgrade(); + + IERC20Upgradeable rewardToken = IERC20Upgradeable(sjoe.rewardTokens(0)); + + vm.startPrank(owner); + + sjoe.removeRewardToken(rewardToken); + + vm.expectRevert("StableJoeStaking: reward token can't be re-added"); + sjoe.addRewardToken(rewardToken); + + sjoe.addRewardToken(IERC20Upgradeable(wavax)); + + sjoe.removeRewardToken(IERC20Upgradeable(wavax)); + + sjoe.addRewardToken(IERC20Upgradeable(wavax)); // Safe as wavax was never updated + + vm.expectRevert("StableJoeStaking: reward token can't be re-added"); + sjoe.addRewardToken(rewardToken); + + vm.stopPrank(); + } + + function _upgrade() internal { + vm.prank(owner); + defaultProxyAdmin.upgrade(TransparentUpgradeableProxy(payable(address(sjoe))), address(imp)); + } +} + +interface IMoneyMaker { + function setTokenToAddress(address _tokenTo) external; + function convert(address token0, address token1, uint256 slippage) external; + function transferOwnership(address newOwner) external; +} From 67852e6d8b2631a14441bc9e365cb93d2d7e035c Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:46:57 +0200 Subject: [PATCH 15/19] fix hardhat test --- test/StableJoeStaking.test.js | 77 +++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/test/StableJoeStaking.test.js b/test/StableJoeStaking.test.js index b595095a..9da7c73c 100644 --- a/test/StableJoeStaking.test.js +++ b/test/StableJoeStaking.test.js @@ -36,10 +36,13 @@ describe("Stable Joe Staking", function () { this.StableJoeStakingCF, [ this.rewardToken.address, - this.joe.address, this.penaltyCollector.address, ethers.utils.parseEther("0.03"), - ] + ], + { + unsafeAllow: ["constructor", "state-variable-immutable"], + constructorArgs: [this.joe.address] + } ); await this.joe @@ -196,13 +199,18 @@ describe("Stable Joe Staking", function () { ).to.be.equal(ethers.utils.parseEther("1")); // Making sure that `pendingReward` still return the accurate tokens even after updating pools - await this.stableJoeStaking.updateReward(this.rewardToken.address); + await this.stableJoeStaking.connect(this.alice).deposit("1"); + + expect( + await this.rewardToken.balanceOf(this.alice.address) + ).to.be.equal(ethers.utils.parseEther("1")); + expect( await this.stableJoeStaking.pendingReward( this.alice.address, this.rewardToken.address ) - ).to.be.equal(ethers.utils.parseEther("1")); + ).to.be.equal(ethers.utils.parseEther("0")); await this.rewardToken .connect(this.joeMaker) @@ -215,16 +223,21 @@ describe("Stable Joe Staking", function () { this.alice.address, this.rewardToken.address ) - ).to.be.equal(ethers.utils.parseEther("2")); + ).to.be.equal(ethers.utils.parseEther("1")); // Making sure that `pendingReward` still return the accurate tokens even after updating pools - await this.stableJoeStaking.updateReward(this.rewardToken.address); + await this.stableJoeStaking.connect(this.alice).deposit("1"); + + expect( + await this.rewardToken.balanceOf(this.alice.address) + ).to.be.equal(ethers.utils.parseEther("2")); + expect( await this.stableJoeStaking.pendingReward( this.alice.address, this.rewardToken.address ) - ).to.be.equal(ethers.utils.parseEther("2")); + ).to.be.equal(ethers.utils.parseEther("0")); }); it("should allow deposits and withdraws of multiple users and distribute rewards accordingly", async function () { @@ -241,8 +254,6 @@ describe("Stable Joe Staking", function () { await this.rewardToken .connect(this.joeMaker) .transfer(this.stableJoeStaking.address, ethers.utils.parseEther("6")); - await this.stableJoeStaking.updateReward(this.rewardToken.address); - await increase(86400); await this.stableJoeStaking .connect(this.alice) @@ -705,6 +716,54 @@ describe("Stable Joe Staking", function () { expect(userInfo[0]).to.be.equal(0); expect(userInfo[1]).to.be.equal(0); }); + + it("should allow owner to sweep stuck tokens that are not rewards", async function () { + await this.stableJoeStaking + .connect(this.alice) + .deposit(ethers.utils.parseEther("300")); + expect(await this.joe.balanceOf(this.alice.address)).to.be.equal( + ethers.utils.parseEther("700") + ); + expect( + await this.joe.balanceOf(this.stableJoeStaking.address) + ).to.be.equal(ethers.utils.parseEther("291")); + + const stuckToken = await this.JoeTokenCF.deploy(); + await stuckToken.mint( + this.stableJoeStaking.address, + ethers.utils.parseEther("100") + ); // We send 100 Tokens to sJoe's address + + await this.stableJoeStaking.connect(this.dev).sweep( + stuckToken.address, + this.dev.address + ); + + expect(await stuckToken.balanceOf(this.dev.address)).to.be.equal( + ethers.utils.parseEther("100") + ); + expect(await stuckToken.balanceOf(this.stableJoeStaking.address)).to.be.equal( + 0 + ); + + // Should fail for joe + await expect( + this.stableJoeStaking.connect(this.dev).sweep( + this.joe.address, + this.dev.address + ) + ).to.be.revertedWith("StableJoeStaking: token can't be swept"); + + // Should fail if stuckToken is added as a reward token + await this.stableJoeStaking.connect(this.dev).addRewardToken(stuckToken.address); + + await expect( + this.stableJoeStaking.connect(this.dev).sweep( + stuckToken.address, + this.dev.address + ) + ).to.be.revertedWith("StableJoeStaking: token can't be swept"); + }); }); after(async function () { From a6c8cc11708a803643879b3e59b4ba46be5a95c8 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 19 Sep 2023 19:50:28 +0200 Subject: [PATCH 16/19] clean up --- test/foundry/UpgradeSJoe.t.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/foundry/UpgradeSJoe.t.sol b/test/foundry/UpgradeSJoe.t.sol index 42a8cf4f..b2b4da9c 100644 --- a/test/foundry/UpgradeSJoe.t.sol +++ b/test/foundry/UpgradeSJoe.t.sol @@ -169,9 +169,3 @@ contract TestUpgradeSJoe is Test { defaultProxyAdmin.upgrade(TransparentUpgradeableProxy(payable(address(sjoe))), address(imp)); } } - -interface IMoneyMaker { - function setTokenToAddress(address _tokenTo) external; - function convert(address token0, address token1, uint256 slippage) external; - function transferOwnership(address newOwner) external; -} From 5e19170d3e8fc41beae095c213e9db6a221e6526 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Tue, 19 Sep 2023 17:52:06 +0000 Subject: [PATCH 17/19] Fix code style issues with Prettier --- contracts/StableJoeStaking.sol | 4 +-- test/StableJoeStaking.test.js | 45 +++++++++++++++++----------------- test/foundry/UpgradeSJoe.t.sol | 26 +++++++------------- 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index 9f1b34a1..02b2adb2 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -75,10 +75,10 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { /// @notice Accumulated `token` rewards per share, scaled to `ACC_REWARD_PER_SHARE_PRECISION` mapping(IERC20Upgradeable => uint256) public accRewardPerShare; - + // @dev gap to keep the storage ordering, replace `uint256 public DEPOSIT_FEE_PERCENT_PRECISION;` uint256[1] private __gap3; - + /// @notice The precision of `accRewardPerShare` uint256 public constant ACC_REWARD_PER_SHARE_PRECISION = 1e24; diff --git a/test/StableJoeStaking.test.js b/test/StableJoeStaking.test.js index 9da7c73c..51d1525e 100644 --- a/test/StableJoeStaking.test.js +++ b/test/StableJoeStaking.test.js @@ -41,7 +41,7 @@ describe("Stable Joe Staking", function () { ], { unsafeAllow: ["constructor", "state-variable-immutable"], - constructorArgs: [this.joe.address] + constructorArgs: [this.joe.address], } ); @@ -201,9 +201,9 @@ describe("Stable Joe Staking", function () { // Making sure that `pendingReward` still return the accurate tokens even after updating pools await this.stableJoeStaking.connect(this.alice).deposit("1"); - expect( - await this.rewardToken.balanceOf(this.alice.address) - ).to.be.equal(ethers.utils.parseEther("1")); + expect(await this.rewardToken.balanceOf(this.alice.address)).to.be.equal( + ethers.utils.parseEther("1") + ); expect( await this.stableJoeStaking.pendingReward( @@ -228,9 +228,9 @@ describe("Stable Joe Staking", function () { // Making sure that `pendingReward` still return the accurate tokens even after updating pools await this.stableJoeStaking.connect(this.alice).deposit("1"); - expect( - await this.rewardToken.balanceOf(this.alice.address) - ).to.be.equal(ethers.utils.parseEther("2")); + expect(await this.rewardToken.balanceOf(this.alice.address)).to.be.equal( + ethers.utils.parseEther("2") + ); expect( await this.stableJoeStaking.pendingReward( @@ -734,34 +734,33 @@ describe("Stable Joe Staking", function () { ethers.utils.parseEther("100") ); // We send 100 Tokens to sJoe's address - await this.stableJoeStaking.connect(this.dev).sweep( - stuckToken.address, - this.dev.address - ); + await this.stableJoeStaking + .connect(this.dev) + .sweep(stuckToken.address, this.dev.address); expect(await stuckToken.balanceOf(this.dev.address)).to.be.equal( ethers.utils.parseEther("100") ); - expect(await stuckToken.balanceOf(this.stableJoeStaking.address)).to.be.equal( - 0 - ); + expect( + await stuckToken.balanceOf(this.stableJoeStaking.address) + ).to.be.equal(0); // Should fail for joe await expect( - this.stableJoeStaking.connect(this.dev).sweep( - this.joe.address, - this.dev.address - ) + this.stableJoeStaking + .connect(this.dev) + .sweep(this.joe.address, this.dev.address) ).to.be.revertedWith("StableJoeStaking: token can't be swept"); // Should fail if stuckToken is added as a reward token - await this.stableJoeStaking.connect(this.dev).addRewardToken(stuckToken.address); + await this.stableJoeStaking + .connect(this.dev) + .addRewardToken(stuckToken.address); await expect( - this.stableJoeStaking.connect(this.dev).sweep( - stuckToken.address, - this.dev.address - ) + this.stableJoeStaking + .connect(this.dev) + .sweep(stuckToken.address, this.dev.address) ).to.be.revertedWith("StableJoeStaking: token can't be swept"); }); }); diff --git a/test/foundry/UpgradeSJoe.t.sol b/test/foundry/UpgradeSJoe.t.sol index b2b4da9c..db5c310b 100644 --- a/test/foundry/UpgradeSJoe.t.sol +++ b/test/foundry/UpgradeSJoe.t.sol @@ -26,25 +26,17 @@ contract TestUpgradeSJoe is Test { imp = new StableJoeStaking(IERC20Upgradeable(joe)); } - + function test_CantInitialize() public { vm.expectRevert("Initializable: contract is already initialized"); - imp.initialize( - IERC20Upgradeable(address(0)), - address(0), - 0 - ); + imp.initialize(IERC20Upgradeable(address(0)), address(0), 0); _upgrade(); vm.expectRevert("Initializable: contract is already initialized"); - imp.initialize( - IERC20Upgradeable(address(0)), - address(0), - 0 - ); + imp.initialize(IERC20Upgradeable(address(0)), address(0), 0); } - + function test_VerifyStorage() public { bytes32[] memory slots = new bytes32[](50); @@ -52,11 +44,11 @@ contract TestUpgradeSJoe is Test { slots[i++] = bytes32(uint256(uint160(address(sjoe.joe())))); slots[i++] = bytes32(sjoe.internalJoeBalance()); - + uint256 l = sjoe.rewardTokensLength(); slots[i++] = bytes32(l); - for(uint256 ii = 0; ii < l; ii++) { + for (uint256 ii = 0; ii < l; ii++) { IERC20Upgradeable token = sjoe.rewardTokens(ii); slots[i++] = bytes32(uint256(uint160(address(token)))); @@ -74,7 +66,7 @@ contract TestUpgradeSJoe is Test { slots[i++] = bytes32(sjoe.DEPOSIT_FEE_PERCENT_PRECISION()); slots[i++] = bytes32(sjoe.ACC_REWARD_PER_SHARE_PRECISION()); - + _upgrade(); uint256 j; @@ -83,7 +75,7 @@ contract TestUpgradeSJoe is Test { assertEq(slots[j++], bytes32(sjoe.internalJoeBalance()), "test_VerifyStorage::2"); assertEq(slots[j++], bytes32(sjoe.rewardTokensLength()), "test_VerifyStorage::3"); - for(uint256 jj = 0; jj < l; jj++) { + for (uint256 jj = 0; jj < l; jj++) { IERC20Upgradeable token = sjoe.rewardTokens(jj); assertEq(slots[j++], bytes32(uint256(uint160(address(token)))), "test_VerifyStorage::4"); @@ -109,7 +101,7 @@ contract TestUpgradeSJoe is Test { sjoe.sweep(IERC20Upgradeable(joe), address(this)); vm.startPrank(owner); - + vm.expectRevert("StableJoeStaking: token can't be swept"); sjoe.sweep(IERC20Upgradeable(joe), address(this)); From 1a3aa4fcb2fac5ee00277edc99861e57aeaf752e Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 20 Sep 2023 10:03:45 +0200 Subject: [PATCH 18/19] typo and safer storage ordering --- contracts/StableJoeStaking.sol | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index 02b2adb2..a0b126f0 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -59,24 +59,24 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { /// @notice Last reward balance of `token` mapping(IERC20Upgradeable => uint256) public lastRewardBalance; - // @notice The address where deposit fees will be sent + /// @notice The address where deposit fees will be sent address public feeCollector; - /// @notice The deposit fee, scaled to `DEPOSIT_FEE_PERCENT_PRECISION` - uint88 public depositFeePercent; /// @notice Reentrancy guard bool public reentrant; - // @dev gap to keep the storage ordering, replace `uint256 public depositFeePercent;` - // and `uint256 public DEPOSIT_FEE_PERCENT_PRECISION;` - uint256[2] private __gap1; + /// @notice The deposit fee, scaled to `DEPOSIT_FEE_PERCENT_PRECISION` + uint256 public depositFeePercent; + + /// @dev gap to keep the storage ordering, replace `uint256 public DEPOSIT_FEE_PERCENT_PRECISION;` + uint256[1] private __gap1; /// @notice The precision of `depositFeePercent` uint256 public constant DEPOSIT_FEE_PERCENT_PRECISION = 1e18; /// @notice Accumulated `token` rewards per share, scaled to `ACC_REWARD_PER_SHARE_PRECISION` mapping(IERC20Upgradeable => uint256) public accRewardPerShare; - - // @dev gap to keep the storage ordering, replace `uint256 public DEPOSIT_FEE_PERCENT_PRECISION;` + + /// @dev gap to keep the storage ordering, replace `uint256 public ACC_REWARD_PER_SHARE_PRECISION;` uint256[1] private __gap3; /// @notice The precision of `accRewardPerShare` @@ -139,7 +139,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { function initialize( IERC20Upgradeable _rewardToken, address _feeCollector, - uint88 _depositFeePercent + uint256 _depositFeePercent ) external initializer { __Ownable_init(); require(address(_rewardToken) != address(0), "StableJoeStaking: reward token can't be address(0)"); @@ -255,7 +255,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { * @notice Set the deposit fee percent * @param _depositFeePercent The new deposit fee percent */ - function setDepositFeePercent(uint88 _depositFeePercent) external onlyOwner { + function setDepositFeePercent(uint256 _depositFeePercent) external onlyOwner { require(_depositFeePercent <= 5e17, "StableJoeStaking: deposit fee can't be greater than 50%"); uint256 oldFee = depositFeePercent; depositFeePercent = _depositFeePercent; From 06dd8565e1161abccb03d847e0750270fc0b376e Mon Sep 17 00:00:00 2001 From: Lint Action Date: Wed, 20 Sep 2023 08:05:21 +0000 Subject: [PATCH 19/19] Fix code style issues with Prettier --- contracts/StableJoeStaking.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/StableJoeStaking.sol b/contracts/StableJoeStaking.sol index a0b126f0..b09ba234 100644 --- a/contracts/StableJoeStaking.sol +++ b/contracts/StableJoeStaking.sol @@ -75,7 +75,7 @@ contract StableJoeStaking is Initializable, OwnableUpgradeable { /// @notice Accumulated `token` rewards per share, scaled to `ACC_REWARD_PER_SHARE_PRECISION` mapping(IERC20Upgradeable => uint256) public accRewardPerShare; - + /// @dev gap to keep the storage ordering, replace `uint256 public ACC_REWARD_PER_SHARE_PRECISION;` uint256[1] private __gap3;