From 7d2a95fc7eff40b4e4b2ea335a6c643c643ac37f Mon Sep 17 00:00:00 2001 From: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com> Date: Wed, 10 Apr 2024 09:41:03 +1000 Subject: [PATCH 1/8] feat: added initializer --- foundry/src/FoxStaking.sol | 24 +++++++++++++++--------- foundry/test/FoxStaking.t.sol | 4 ++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/foundry/src/FoxStaking.sol b/foundry/src/FoxStaking.sol index 42a176c..6a86535 100644 --- a/foundry/src/FoxStaking.sol +++ b/foundry/src/FoxStaking.sol @@ -5,25 +5,24 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; import {IFoxStaking, StakingInfo} from "./IFoxStaking.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; contract FoxStaking is IFoxStaking, Ownable(msg.sender), // Deployer is the owner - Pausable + Pausable, + Initializable { using SafeERC20 for IERC20; IERC20 public foxToken; mapping(address => StakingInfo) public stakingInfo; - - bool public stakingPaused = false; - bool public withdrawalsPaused = false; - bool public unstakingPaused = false; - - uint256 public cooldownPeriod = 28 days; + bool public stakingPaused; + bool public withdrawalsPaused; + bool public unstakingPaused; + uint256 public cooldownPeriod; event UpdateCooldownPeriod(uint256 newCooldownPeriod); - event Stake( address indexed account, uint256 amount, @@ -37,6 +36,13 @@ contract FoxStaking is string indexed newRuneAddress ); + function initialize() external initializer { + stakingPaused = false; + withdrawalsPaused = false; + unstakingPaused = false; + cooldownPeriod = 28 days; + } + constructor(address foxTokenAddress) { foxToken = IERC20(foxTokenAddress); } diff --git a/foundry/test/FoxStaking.t.sol b/foundry/test/FoxStaking.t.sol index c79b68e..f8f539a 100644 --- a/foundry/test/FoxStaking.t.sol +++ b/foundry/test/FoxStaking.t.sol @@ -89,6 +89,7 @@ contract FOXStakingTestOwnership is Test { function setUp() public { foxToken = new MockFOXToken(); foxStaking = new FoxStaking(address(foxToken)); + foxStaking.initialize(); } function testOwnerCanUpdateCooldownPeriod() public { @@ -123,6 +124,7 @@ contract FOXStakingTestStaking is Test { function setUp() public { foxToken = new MockFOXToken(); foxStaking = new FoxStaking(address(foxToken)); + foxStaking.initialize(); } function testCannotStakeWhenStakingPaused() public { @@ -346,6 +348,7 @@ contract FOXStakingTestUnstake is Test { function setUp() public { foxToken = new MockFOXToken(); foxStaking = new FoxStaking(address(foxToken)); + foxStaking.initialize(); // Free FOX tokens for user foxToken.makeItRain(user, amount); @@ -627,6 +630,7 @@ contract FOXStakingTestWithdraw is Test { function setUp() public { foxToken = new MockFOXToken(); foxStaking = new FoxStaking(address(foxToken)); + foxStaking.initialize(); // Free FOX tokens for user foxToken.makeItRain(user, amount); From dffb1370bb87ac15e9b34a6a9a466eef0b4be131 Mon Sep 17 00:00:00 2001 From: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:40:44 +1000 Subject: [PATCH 2/8] feat: added proxy upgrade boilerplate --- foundry/src/FoxStaking.sol | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/foundry/src/FoxStaking.sol b/foundry/src/FoxStaking.sol index 6a86535..d598a06 100644 --- a/foundry/src/FoxStaking.sol +++ b/foundry/src/FoxStaking.sol @@ -2,17 +2,18 @@ pragma solidity ^0.8.25; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; -import {IFoxStaking, StakingInfo} from "./IFoxStaking.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; +import {IFoxStaking, StakingInfo} from "./IFoxStaking.sol"; contract FoxStaking is - IFoxStaking, - Ownable(msg.sender), // Deployer is the owner - Pausable, - Initializable + Initializable, + PausableUpgradeable, + UUPSUpgradeable, + OwnableUpgradeable { using SafeERC20 for IERC20; IERC20 public foxToken; @@ -36,16 +37,25 @@ contract FoxStaking is string indexed newRuneAddress ); - function initialize() external initializer { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address foxTokenAddress) external initializer { + __Ownable_init(msg.sender); + __UUPSUpgradeable_init(); + __Pausable_init(); + foxToken = IERC20(foxTokenAddress); stakingPaused = false; withdrawalsPaused = false; unstakingPaused = false; cooldownPeriod = 28 days; } - constructor(address foxTokenAddress) { - foxToken = IERC20(foxTokenAddress); - } + function _authorizeUpgrade( + address newImplementation + ) internal override onlyOwner {} function pauseStaking() external onlyOwner { stakingPaused = true; From 71df49bfafcd8cc33b30c2aa561f4c14d88453ad Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:18:14 +0200 Subject: [PATCH 3/8] forge install: openzeppelin-foundry-upgrades v0.2.1 --- .gitmodules | 3 +++ foundry/lib/openzeppelin-foundry-upgrades | 1 + 2 files changed, 4 insertions(+) create mode 160000 foundry/lib/openzeppelin-foundry-upgrades diff --git a/.gitmodules b/.gitmodules index ebdedaf..5bb1992 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "foundry/lib/openzeppelin-contracts-upgradeable"] path = foundry/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "foundry/lib/openzeppelin-foundry-upgrades"] + path = foundry/lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades diff --git a/foundry/lib/openzeppelin-foundry-upgrades b/foundry/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000..3595893 --- /dev/null +++ b/foundry/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit 359589365aeba6cf41d39bae69867446b194e582 From d87af63862e19dce88944b14be01e6f7a36ad817 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:18:40 +0200 Subject: [PATCH 4/8] feat: update foundry.toml --- foundry/foundry.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/foundry/foundry.toml b/foundry/foundry.toml index 25b918f..21f761a 100644 --- a/foundry/foundry.toml +++ b/foundry/foundry.toml @@ -2,5 +2,9 @@ src = "src" out = "out" libs = ["lib"] +ffi = true +ast = true +build_info = true +extra_output = ["storageLayout"] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options From 1b07606e371b026d15554bdd45886783a5ab22a1 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:25:32 +0200 Subject: [PATCH 5/8] feat: upgradeable tests --- foundry/test/FoxStaking.t.sol | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/foundry/test/FoxStaking.t.sol b/foundry/test/FoxStaking.t.sol index f8f539a..2a202b2 100644 --- a/foundry/test/FoxStaking.t.sol +++ b/foundry/test/FoxStaking.t.sol @@ -7,6 +7,7 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; +import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; contract MockFOXToken is ERC20 { constructor() ERC20("Mock FOX Token", "FOX") { @@ -88,8 +89,11 @@ contract FOXStakingTestOwnership is Test { function setUp() public { foxToken = new MockFOXToken(); - foxStaking = new FoxStaking(address(foxToken)); - foxStaking.initialize(); + address foxStakingProxy = Upgrades.deployUUPSProxy( + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + ); + foxStaking = FoxStaking(foxStakingProxy); } function testOwnerCanUpdateCooldownPeriod() public { @@ -123,8 +127,11 @@ contract FOXStakingTestStaking is Test { function setUp() public { foxToken = new MockFOXToken(); - foxStaking = new FoxStaking(address(foxToken)); - foxStaking.initialize(); + address foxStakingProxy = Upgrades.deployUUPSProxy( + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + ); + foxStaking = FoxStaking(foxStakingProxy); } function testCannotStakeWhenStakingPaused() public { @@ -347,8 +354,11 @@ contract FOXStakingTestUnstake is Test { function setUp() public { foxToken = new MockFOXToken(); - foxStaking = new FoxStaking(address(foxToken)); - foxStaking.initialize(); + address foxStakingProxy = Upgrades.deployUUPSProxy( + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + ); + foxStaking = FoxStaking(foxStakingProxy); // Free FOX tokens for user foxToken.makeItRain(user, amount); @@ -629,8 +639,11 @@ contract FOXStakingTestWithdraw is Test { function setUp() public { foxToken = new MockFOXToken(); - foxStaking = new FoxStaking(address(foxToken)); - foxStaking.initialize(); + address foxStakingProxy = Upgrades.deployUUPSProxy( + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + ); + foxStaking = FoxStaking(foxStakingProxy); // Free FOX tokens for user foxToken.makeItRain(user, amount); From 916c9559642ffe630fefb918ddcbdc02a3da088d Mon Sep 17 00:00:00 2001 From: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:25:54 +1000 Subject: [PATCH 6/8] chore: update .vscode/settings.json --- .vscode/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ad3e95a..b6b5ba0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,8 @@ "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/", "forge-std/=lib/forge-std/src/", "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/", - "openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/" + "openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/", + "openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/", + "solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/" ] } \ No newline at end of file From 275a8deb6ce969c2dc4f70a8246fb3449570b17e Mon Sep 17 00:00:00 2001 From: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:26:04 +1000 Subject: [PATCH 7/8] fix: tests --- foundry/test/FoxStaking.t.sol | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/foundry/test/FoxStaking.t.sol b/foundry/test/FoxStaking.t.sol index 2a202b2..38c121a 100644 --- a/foundry/test/FoxStaking.t.sol +++ b/foundry/test/FoxStaking.t.sol @@ -27,7 +27,11 @@ contract FOXStakingTestRuneAddress is Test { function setUp() public { foxToken = new MockFOXToken(); - foxStaking = new FoxStaking(address(foxToken)); + address foxStakingProxy = Upgrades.deployUUPSProxy( + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + ); + foxStaking = FoxStaking(foxStakingProxy); } function testCanSetRuneAddress() public { @@ -90,8 +94,8 @@ contract FOXStakingTestOwnership is Test { function setUp() public { foxToken = new MockFOXToken(); address foxStakingProxy = Upgrades.deployUUPSProxy( - "FoxStaking.sol", - abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) ); foxStaking = FoxStaking(foxStakingProxy); } @@ -128,8 +132,8 @@ contract FOXStakingTestStaking is Test { function setUp() public { foxToken = new MockFOXToken(); address foxStakingProxy = Upgrades.deployUUPSProxy( - "FoxStaking.sol", - abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) ); foxStaking = FoxStaking(foxStakingProxy); } @@ -355,8 +359,8 @@ contract FOXStakingTestUnstake is Test { function setUp() public { foxToken = new MockFOXToken(); address foxStakingProxy = Upgrades.deployUUPSProxy( - "FoxStaking.sol", - abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) ); foxStaking = FoxStaking(foxStakingProxy); @@ -640,8 +644,8 @@ contract FOXStakingTestWithdraw is Test { function setUp() public { foxToken = new MockFOXToken(); address foxStakingProxy = Upgrades.deployUUPSProxy( - "FoxStaking.sol", - abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) ); foxStaking = FoxStaking(foxStakingProxy); From c369acf54189e451ae0076f8680fb382a3ddea1f Mon Sep 17 00:00:00 2001 From: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:53:29 +1000 Subject: [PATCH 8/8] chore: placeholder upgrade tests --- foundry/src/FoxStaking.sol | 2 ++ foundry/test/FoxStakingTestUpgrades.t.sol | 30 +++++++++++++++++++++++ foundry/test/MockFOXToken.sol | 16 ++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 foundry/test/FoxStakingTestUpgrades.t.sol create mode 100644 foundry/test/MockFOXToken.sol diff --git a/foundry/src/FoxStaking.sol b/foundry/src/FoxStaking.sol index d598a06..9f995f6 100644 --- a/foundry/src/FoxStaking.sol +++ b/foundry/src/FoxStaking.sol @@ -16,6 +16,7 @@ contract FoxStaking is OwnableUpgradeable { using SafeERC20 for IERC20; + uint256 public version; IERC20 public foxToken; mapping(address => StakingInfo) public stakingInfo; bool public stakingPaused; @@ -46,6 +47,7 @@ contract FoxStaking is __Ownable_init(msg.sender); __UUPSUpgradeable_init(); __Pausable_init(); + version = 1; foxToken = IERC20(foxTokenAddress); stakingPaused = false; withdrawalsPaused = false; diff --git a/foundry/test/FoxStakingTestUpgrades.t.sol b/foundry/test/FoxStakingTestUpgrades.t.sol new file mode 100644 index 0000000..dfb4248 --- /dev/null +++ b/foundry/test/FoxStakingTestUpgrades.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import "forge-std/Test.sol"; +import "../src/FoxStaking.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; +import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import {MockFOXToken} from "./MockFOXToken.sol"; + +contract FOXStakingTestUpgrades is Test { + FoxStaking public foxStaking; + MockFOXToken public foxToken; + address user = address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045); + + function setUp() public { + foxToken = new MockFOXToken(); + address foxStakingProxy = Upgrades.deployUUPSProxy( + "FoxStaking.sol", + abi.encodeCall(FoxStaking.initialize, (address(foxToken))) + ); + foxStaking = FoxStaking(foxStakingProxy); + } + + function testCanDeploy() public view { + uint256 expectedVersion = 1; + assertEq(foxStaking.version(), expectedVersion); + } +} diff --git a/foundry/test/MockFOXToken.sol b/foundry/test/MockFOXToken.sol new file mode 100644 index 0000000..dfa7110 --- /dev/null +++ b/foundry/test/MockFOXToken.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import "forge-std/Test.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockFOXToken is ERC20 { + constructor() ERC20("Mock FOX Token", "FOX") { + // 1M FOX for testing, only in local chain can't use this as voting power soz + _mint(address(this), 1e24); + } + + function makeItRain(address to, uint256 amount) public { + _transfer(address(this), to, amount); + } +}