From 534f7c5ad4ed242672e7b4b9e1b2699bc343bb4c Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:55:00 +0000 Subject: [PATCH 01/10] feat: add sd reward manager contract --- contracts/SDRewardManager.sol | 92 ++++++++++++++++++++++++++ contracts/StaderConfig.sol | 26 ++++++++ contracts/interfaces/IStaderConfig.sol | 12 ++++ 3 files changed, 130 insertions(+) create mode 100644 contracts/SDRewardManager.sol diff --git a/contracts/SDRewardManager.sol b/contracts/SDRewardManager.sol new file mode 100644 index 00000000..9387aa60 --- /dev/null +++ b/contracts/SDRewardManager.sol @@ -0,0 +1,92 @@ +pragma solidity 0.8.16; + +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import { IStaderConfig } from "./interfaces/IStaderConfig.sol"; +import { UtilLib } from "./library/UtilLib.sol"; + +contract SDRewardManager is Initializable { + using SafeERC20Upgradeable for IERC20Upgradeable; + + struct SDRewardEntry { + uint256 cycleNumber; + uint256 amount; // in exact SD value, not in gwei or wei + bool approved; + } + + IStaderConfig public staderConfig; + + uint256 public latestCycleNumber; + + // Mapping of cycle numbers to reward entries + mapping(uint256 => SDRewardEntry) public rewardEntries; + + // Event emitted when a new reward entry is created + event NewRewardEntry(uint256 indexed cycleNumber, uint256 amount); + + // Event emitted when a reward entry is approved + event RewardEntryApproved(uint256 indexed cycleNumber, uint256 amount); + + error AccessDenied(address account); + error EntryNotFound(uint256 cycleNumber); + error EntryAlreadyRegistered(uint256 cycleNumber); + error EntryAlreadApproved(uint256 cycleNumber); + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address _staderConfig) external initializer { + UtilLib.checkNonZeroAddress(_staderConfig); + staderConfig = IStaderConfig(_staderConfig); + } + + function addRewardEntry(uint256 _cycleNumber, uint256 _amount) external { + if (!staderConfig.isAllowedToCall(msg.sender, "addRewardEntry(uint256,uint256)")) { + revert AccessDenied(msg.sender); + } + + if (_cycleNumber <= latestCycleNumber) { + revert EntryAlreadyRegistered(_cycleNumber); + } + + SDRewardEntry storage rewardEntry = rewardEntries[_cycleNumber]; + rewardEntry.cycleNumber = _cycleNumber; + rewardEntry.amount = _amount; + latestCycleNumber = _cycleNumber; + + emit NewRewardEntry(_cycleNumber, _amount); + } + + function approveEntry(uint256 _cycleNumber, uint256 _amount) external { + if (!staderConfig.isAllowedToCall(msg.sender, "approveEntry(uint256,uint256)")) { + revert AccessDenied(msg.sender); + } + + SDRewardEntry storage rewardEntry = rewardEntries[_cycleNumber]; + + if (rewardEntry.cycleNumber == 0) { + revert EntryNotFound(_cycleNumber); + } + + if (rewardEntry.approved) { + revert EntryAlreadApproved(_cycleNumber); + } + + rewardEntry.approved = true; + if (rewardEntry.amount > 0) { + IERC20Upgradeable(staderConfig.getStaderToken()).safeTransferFrom( + msg.sender, + staderConfig.getPermissionlessSocializingPool(), + _amount + ); + emit RewardEntryApproved(_cycleNumber, _amount); + } + } + + function viewLatestEntry() external view returns (SDRewardEntry memory) { + return rewardEntries[latestCycleNumber]; + } +} diff --git a/contracts/StaderConfig.sol b/contracts/StaderConfig.sol index 24af1f35..d9f9b920 100644 --- a/contracts/StaderConfig.sol +++ b/contracts/StaderConfig.sol @@ -297,6 +297,27 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { setContract(SD_INCENTIVE_CONTROLLER, _sdIncentiveController); } + // Access Control + function giveCallPermission( + address contractAddress, + string calldata functionSig, + address accountToPermit + ) external onlyRole(DEFAULT_ADMIN_ROLE) { + bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); + grantRole(role, accountToPermit); + emit PermissionGranted(accountToPermit, contractAddress, functionSig); + } + + function revokeCallPermission( + address contractAddress, + string calldata functionSig, + address accountToRevoke + ) external onlyRole(DEFAULT_ADMIN_ROLE) { + bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); + revokeRole(role, accountToRevoke); + emit PermissionRevoked(accountToRevoke, contractAddress, functionSig); + } + //Constants Getters function getStakedEthPerNode() external view override returns (uint256) { @@ -537,6 +558,11 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { return hasRole(OPERATOR, account); } + function isAllowedToCall(address account, string calldata functionSig) external view returns (bool) { + bytes32 role = keccak256(abi.encodePacked(msg.sender, functionSig)); + return hasRole(role, account); + } + function verifyDepositAndWithdrawLimits() internal view { if ( !(variablesMap[MIN_DEPOSIT_AMOUNT] != 0 && diff --git a/contracts/interfaces/IStaderConfig.sol b/contracts/interfaces/IStaderConfig.sol index f8f9854a..cf49f219 100644 --- a/contracts/interfaces/IStaderConfig.sol +++ b/contracts/interfaces/IStaderConfig.sol @@ -16,6 +16,8 @@ interface IStaderConfig { event SetAccount(bytes32 key, address newAddress); event SetContract(bytes32 key, address newAddress); event SetToken(bytes32 key, address newAddress); + event PermissionGranted(address indexed accountToPermit, address indexed contractAddress, string functionSig); + event PermissionRevoked(address indexed accountToRevoke, address indexed contractAddress, string functionSig); //Contracts function POOL_UTILS() external view returns (bytes32); @@ -171,4 +173,14 @@ interface IStaderConfig { function onlyManagerRole(address account) external view returns (bool); function onlyOperatorRole(address account) external view returns (bool); + + function isAllowedToCall(address account, string calldata functionSig) external view returns (bool); + + function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external; + + function revokeCallPermission( + address contractAddress, + string calldata functionSig, + address accountToRevoke + ) external; } From bdf19c440698f21bf99bcef21743e05f79d5b99f Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:23:09 +0000 Subject: [PATCH 02/10] test: add tests and scripts --- contracts/SDRewardManager.sol | 29 ++- scripts/deploy/SDRewardManager.ts | 12 ++ test/foundry_tests/SDRewardsManager.t.sol | 206 ++++++++++++++++++++++ 3 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 scripts/deploy/SDRewardManager.ts create mode 100644 test/foundry_tests/SDRewardsManager.t.sol diff --git a/contracts/SDRewardManager.sol b/contracts/SDRewardManager.sol index 9387aa60..73775eff 100644 --- a/contracts/SDRewardManager.sol +++ b/contracts/SDRewardManager.sol @@ -6,12 +6,16 @@ import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC import { IStaderConfig } from "./interfaces/IStaderConfig.sol"; import { UtilLib } from "./library/UtilLib.sol"; +/** + * @title SDRewardManager + * @notice This contract is responsible to add SD rewards to the socializing pool + */ contract SDRewardManager is Initializable { using SafeERC20Upgradeable for IERC20Upgradeable; struct SDRewardEntry { uint256 cycleNumber; - uint256 amount; // in exact SD value, not in gwei or wei + uint256 amount; bool approved; } @@ -38,11 +42,20 @@ contract SDRewardManager is Initializable { _disableInitializers(); } + /** + * @notice Initializes the contract with a Stader configuration address + * @param _staderConfig Address of the StaderConfig contract + */ function initialize(address _staderConfig) external initializer { UtilLib.checkNonZeroAddress(_staderConfig); staderConfig = IStaderConfig(_staderConfig); } + /** + * @notice Adds a new reward entry for a specified cycle + * @param _cycleNumber The cycle number for the reward entry + * @param _amount The amount of SD to be rewarded + */ function addRewardEntry(uint256 _cycleNumber, uint256 _amount) external { if (!staderConfig.isAllowedToCall(msg.sender, "addRewardEntry(uint256,uint256)")) { revert AccessDenied(msg.sender); @@ -60,7 +73,11 @@ contract SDRewardManager is Initializable { emit NewRewardEntry(_cycleNumber, _amount); } - function approveEntry(uint256 _cycleNumber, uint256 _amount) external { + /** + * @notice Approves a reward entry for a specified cycle and transfers the reward amount. + * @param _cycleNumber The cycle number for the reward entry + */ + function approveEntry(uint256 _cycleNumber) external { if (!staderConfig.isAllowedToCall(msg.sender, "approveEntry(uint256,uint256)")) { revert AccessDenied(msg.sender); } @@ -80,12 +97,16 @@ contract SDRewardManager is Initializable { IERC20Upgradeable(staderConfig.getStaderToken()).safeTransferFrom( msg.sender, staderConfig.getPermissionlessSocializingPool(), - _amount + rewardEntry.amount ); - emit RewardEntryApproved(_cycleNumber, _amount); + emit RewardEntryApproved(_cycleNumber, rewardEntry.amount); } } + /** + * @notice Returns the latest reward entry + * @return The latest SDRewardEntry struct for the most recent cycle + */ function viewLatestEntry() external view returns (SDRewardEntry memory) { return rewardEntries[latestCycleNumber]; } diff --git a/scripts/deploy/SDRewardManager.ts b/scripts/deploy/SDRewardManager.ts new file mode 100644 index 00000000..650efbd4 --- /dev/null +++ b/scripts/deploy/SDRewardManager.ts @@ -0,0 +1,12 @@ +import { ethers, upgrades } from 'hardhat' + +async function main() { + const [owner] = await ethers.getSigners() + const staderConfigAddr = process.env.STADER_CONFIG ?? '' + + const sdRewardManagerFactory = await ethers.getContractFactory('SDRewardManager') + const sdRewardManager = await upgrades.deployProxy(sdRewardManagerFactory, [staderConfigAddr]) + console.log('SDRewardManager deployed to: ', sdRewardManager.address) +} + +main() diff --git a/test/foundry_tests/SDRewardsManager.t.sol b/test/foundry_tests/SDRewardsManager.t.sol new file mode 100644 index 00000000..2610106a --- /dev/null +++ b/test/foundry_tests/SDRewardsManager.t.sol @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +import "../../contracts/library/UtilLib.sol"; + +import "../../contracts/SDRewardManager.sol"; +import "../../contracts/StaderConfig.sol"; +import "../../contracts/SocializingPool.sol"; + +import "../mocks/StaderTokenMock.sol"; + +import "forge-std/Test.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +contract SDRewardManagerTest is Test { + address staderAdmin; + address user1; + address user2; + address staderTokenDeployer; + uint256 latestCycleNumber; + + StaderConfig staderConfig; + SDRewardManager rewardManager; + StaderTokenMock staderToken; + SocializingPool permissionlessSP; + + event NewRewardEntry(uint256 indexed cycleNumber, uint256 amount); + event RewardEntryApproved(uint256 indexed cycleNumber, uint256 amount); + + function setUp() public { + staderAdmin = vm.addr(100); + user1 = vm.addr(101); + user2 = vm.addr(102); + staderTokenDeployer = vm.addr(103); + address ethDepositAddr = vm.addr(104); + + vm.prank(staderTokenDeployer); + staderToken = new StaderTokenMock(); + ProxyAdmin proxyAdmin = new ProxyAdmin(); + + StaderConfig configImpl = new StaderConfig(); + TransparentUpgradeableProxy configProxy = new TransparentUpgradeableProxy( + address(configImpl), + address(proxyAdmin), + "" + ); + staderConfig = StaderConfig(address(configProxy)); + staderConfig.initialize(staderAdmin, ethDepositAddr); + + SDRewardManager rewardManagerImpl = new SDRewardManager(); + TransparentUpgradeableProxy rewardManagerProxy = new TransparentUpgradeableProxy( + address(rewardManagerImpl), + address(proxyAdmin), + "" + ); + rewardManager = SDRewardManager(address(rewardManagerProxy)); + rewardManager.initialize(address(staderConfig)); + + SocializingPool spImpl = new SocializingPool(); + + TransparentUpgradeableProxy permissionlessSPProxy = new TransparentUpgradeableProxy( + address(spImpl), + address(proxyAdmin), + "" + ); + permissionlessSP = SocializingPool(payable(permissionlessSPProxy)); + permissionlessSP.initialize(staderAdmin, address(staderConfig)); + + vm.startPrank(staderAdmin); + staderConfig.updateStaderToken(address(staderToken)); + staderConfig.updatePermissionlessSocializingPool(address(permissionlessSP)); + staderConfig.giveCallPermission(address(rewardManager), "addRewardEntry(uint256,uint256)", user1); + staderConfig.giveCallPermission(address(rewardManager), "approveEntry(uint256,uint256)", user1); + vm.stopPrank(); + + vm.startPrank(staderTokenDeployer); + IERC20Upgradeable(staderConfig.getStaderToken()).transfer(user1, 100 ether); + vm.stopPrank(); + + vm.startPrank(user1); + IERC20Upgradeable(staderConfig.getStaderToken()).approve(address(rewardManager), 100 ether); + vm.stopPrank(); + } + + function test_initialize() public { + assertEq(address(rewardManager.staderConfig()), address(staderConfig)); + assertEq(staderConfig.getStaderToken(), address(staderToken)); + assertEq(address(permissionlessSP.staderConfig()), address(staderConfig)); + + assertTrue(permissionlessSP.hasRole(permissionlessSP.DEFAULT_ADMIN_ROLE(), staderAdmin)); + + assertEq(staderConfig.getPermissionlessSocializingPool(), address(permissionlessSP)); + } + + function test_addRewardEntry() public { + uint256 cycleNumber = 1; + uint256 amount = 10 ether; + + // Only allowed contract can call the addRewardEntry function + vm.prank(user1); + + // Should emit event for adding entry + vm.expectEmit(true, false, false, true); + emit NewRewardEntry(cycleNumber, amount); + rewardManager.addRewardEntry(cycleNumber, amount); + + // Checking if the entry is correct + (uint256 storedCycleNumber, uint256 storedAmount, bool isApproved) = rewardManager.rewardEntries(cycleNumber); + assertEq(storedCycleNumber, cycleNumber); + assertEq(storedAmount, amount); + assertEq(isApproved, false); + } + + function test_addRewardEntry_AccessDenied() public { + uint256 cycleNumber = 1; + uint256 amount = 10 ether; + + // anyone cannot call the addRewardEntry method + vm.prank(user2); + vm.expectRevert(abi.encodeWithSignature("AccessDenied(address)", user2)); + rewardManager.addRewardEntry(cycleNumber, amount); + } + + function test_addRewardEntry_EntryAlreadyRegistered() public { + uint256 cycleNumber = 1; + uint256 amount = 10 ether; + + // Adding the first entry + vm.startPrank(user1); + rewardManager.addRewardEntry(cycleNumber, amount); + + // Attempt to add the same entry again + vm.expectRevert(abi.encodeWithSignature("EntryAlreadyRegistered(uint256)", cycleNumber)); + rewardManager.addRewardEntry(cycleNumber, amount); + vm.stopPrank(); + } + + function test_approveEntry() public { + uint256 cycleNumber = 1; + uint256 amount = 10 ether; + + // Add the entry + vm.startPrank(user1); + rewardManager.addRewardEntry(cycleNumber, amount); + + (, uint256 storedAmount, ) = rewardManager.rewardEntries(cycleNumber); + // Expect the RewardEntryApproved event + vm.expectEmit(true, false, false, true); + emit RewardEntryApproved(cycleNumber, storedAmount); + // Approve the entry + rewardManager.approveEntry(cycleNumber); + vm.stopPrank(); + + // Check if the entry is approved + (, , bool isApproved) = rewardManager.rewardEntries(cycleNumber); + assertTrue(isApproved); + } + + function test_approveEntry_AccessDenied() public { + uint256 cycleNumber = 1; + + // anyone cannot call the approveEntry method + vm.prank(user2); + vm.expectRevert(abi.encodeWithSignature("AccessDenied(address)", user2)); + rewardManager.approveEntry(cycleNumber); + } + + function test_approveEntry_EntryNotFound() public { + uint256 cycleNumber = 1; + + // Attempt to approve an entry that doesn't exist + vm.expectRevert(abi.encodeWithSignature("EntryNotFound(uint256)", cycleNumber + 100)); + vm.prank(user1); + rewardManager.approveEntry(cycleNumber + 100); + } + + function test_approveEntry_EntryAlreadApproved() public { + uint256 cycleNumber = 1; + uint256 amount = 10 ether; + + // Add the entry + vm.startPrank(user1); + rewardManager.addRewardEntry(cycleNumber, amount); + rewardManager.approveEntry(cycleNumber); + + // Attempt to approve the same entry again + vm.expectRevert(abi.encodeWithSignature("EntryAlreadApproved(uint256)", cycleNumber)); + rewardManager.approveEntry(cycleNumber); + vm.stopPrank(); + } + + function test_viewLatestEntry() public { + // Add the entries + vm.startPrank(user1); + rewardManager.addRewardEntry(1, 10 ether); + rewardManager.addRewardEntry(2, 20 ether); + rewardManager.addRewardEntry(3, 30 ether); + vm.stopPrank(); + + SDRewardManager.SDRewardEntry memory lastestEntry = rewardManager.viewLatestEntry(); + assertEq(lastestEntry.cycleNumber, 3); + assertEq(lastestEntry.amount, 30 ether); + } +} From cacf569a19fbd59ad75faec051a45a8f89f27e72 Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:14:03 +0000 Subject: [PATCH 03/10] fix: permissions --- contracts/SDRewardManager.sol | 2 +- test/foundry_tests/SDRewardsManager.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/SDRewardManager.sol b/contracts/SDRewardManager.sol index 73775eff..99345c7e 100644 --- a/contracts/SDRewardManager.sol +++ b/contracts/SDRewardManager.sol @@ -78,7 +78,7 @@ contract SDRewardManager is Initializable { * @param _cycleNumber The cycle number for the reward entry */ function approveEntry(uint256 _cycleNumber) external { - if (!staderConfig.isAllowedToCall(msg.sender, "approveEntry(uint256,uint256)")) { + if (!staderConfig.isAllowedToCall(msg.sender, "approveEntry(uint256)")) { revert AccessDenied(msg.sender); } diff --git a/test/foundry_tests/SDRewardsManager.t.sol b/test/foundry_tests/SDRewardsManager.t.sol index 2610106a..9de9525d 100644 --- a/test/foundry_tests/SDRewardsManager.t.sol +++ b/test/foundry_tests/SDRewardsManager.t.sol @@ -72,7 +72,7 @@ contract SDRewardManagerTest is Test { staderConfig.updateStaderToken(address(staderToken)); staderConfig.updatePermissionlessSocializingPool(address(permissionlessSP)); staderConfig.giveCallPermission(address(rewardManager), "addRewardEntry(uint256,uint256)", user1); - staderConfig.giveCallPermission(address(rewardManager), "approveEntry(uint256,uint256)", user1); + staderConfig.giveCallPermission(address(rewardManager), "approveEntry(uint256)", user1); vm.stopPrank(); vm.startPrank(staderTokenDeployer); From b82cdf21010705fb1a4381f587b861b5cf1fb7d5 Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:40:17 +0000 Subject: [PATCH 04/10] chore: allow manager to grant and revoke permissions --- contracts/StaderConfig.sol | 4 ++-- test/foundry_tests/SDRewardsManager.t.sol | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/StaderConfig.sol b/contracts/StaderConfig.sol index d9f9b920..b8a5a59a 100644 --- a/contracts/StaderConfig.sol +++ b/contracts/StaderConfig.sol @@ -302,7 +302,7 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { address contractAddress, string calldata functionSig, address accountToPermit - ) external onlyRole(DEFAULT_ADMIN_ROLE) { + ) external onlyRole(MANAGER) { bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); grantRole(role, accountToPermit); emit PermissionGranted(accountToPermit, contractAddress, functionSig); @@ -312,7 +312,7 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { address contractAddress, string calldata functionSig, address accountToRevoke - ) external onlyRole(DEFAULT_ADMIN_ROLE) { + ) external onlyRole(MANAGER) { bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); revokeRole(role, accountToRevoke); emit PermissionRevoked(accountToRevoke, contractAddress, functionSig); diff --git a/test/foundry_tests/SDRewardsManager.t.sol b/test/foundry_tests/SDRewardsManager.t.sol index 9de9525d..347149d4 100644 --- a/test/foundry_tests/SDRewardsManager.t.sol +++ b/test/foundry_tests/SDRewardsManager.t.sol @@ -69,6 +69,7 @@ contract SDRewardManagerTest is Test { permissionlessSP.initialize(staderAdmin, address(staderConfig)); vm.startPrank(staderAdmin); + staderConfig.grantRole(staderConfig.MANAGER(), staderAdmin); staderConfig.updateStaderToken(address(staderToken)); staderConfig.updatePermissionlessSocializingPool(address(permissionlessSP)); staderConfig.giveCallPermission(address(rewardManager), "addRewardEntry(uint256,uint256)", user1); From 73a1415eb20141cf2e8a97cf835416f4e91f29c3 Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:56:01 +0000 Subject: [PATCH 05/10] Revert "chore: allow manager to grant and revoke permissions" This reverts commit b82cdf21010705fb1a4381f587b861b5cf1fb7d5. --- contracts/StaderConfig.sol | 4 ++-- test/foundry_tests/SDRewardsManager.t.sol | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/StaderConfig.sol b/contracts/StaderConfig.sol index b8a5a59a..d9f9b920 100644 --- a/contracts/StaderConfig.sol +++ b/contracts/StaderConfig.sol @@ -302,7 +302,7 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { address contractAddress, string calldata functionSig, address accountToPermit - ) external onlyRole(MANAGER) { + ) external onlyRole(DEFAULT_ADMIN_ROLE) { bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); grantRole(role, accountToPermit); emit PermissionGranted(accountToPermit, contractAddress, functionSig); @@ -312,7 +312,7 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { address contractAddress, string calldata functionSig, address accountToRevoke - ) external onlyRole(MANAGER) { + ) external onlyRole(DEFAULT_ADMIN_ROLE) { bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); revokeRole(role, accountToRevoke); emit PermissionRevoked(accountToRevoke, contractAddress, functionSig); diff --git a/test/foundry_tests/SDRewardsManager.t.sol b/test/foundry_tests/SDRewardsManager.t.sol index 347149d4..9de9525d 100644 --- a/test/foundry_tests/SDRewardsManager.t.sol +++ b/test/foundry_tests/SDRewardsManager.t.sol @@ -69,7 +69,6 @@ contract SDRewardManagerTest is Test { permissionlessSP.initialize(staderAdmin, address(staderConfig)); vm.startPrank(staderAdmin); - staderConfig.grantRole(staderConfig.MANAGER(), staderAdmin); staderConfig.updateStaderToken(address(staderToken)); staderConfig.updatePermissionlessSocializingPool(address(permissionlessSP)); staderConfig.giveCallPermission(address(rewardManager), "addRewardEntry(uint256,uint256)", user1); From 0dbfe22a86ed8066e9484544cd5c6e80b741c4e8 Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:05:03 +0000 Subject: [PATCH 06/10] fix: call internal function and allow manager to grant and revoke permissions --- contracts/StaderConfig.sol | 8 ++++---- test/foundry_tests/SDRewardsManager.t.sol | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/contracts/StaderConfig.sol b/contracts/StaderConfig.sol index d9f9b920..dad1dfcc 100644 --- a/contracts/StaderConfig.sol +++ b/contracts/StaderConfig.sol @@ -302,9 +302,9 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { address contractAddress, string calldata functionSig, address accountToPermit - ) external onlyRole(DEFAULT_ADMIN_ROLE) { + ) external onlyRole(MANAGER) { bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); - grantRole(role, accountToPermit); + _grantRole(role, accountToPermit); emit PermissionGranted(accountToPermit, contractAddress, functionSig); } @@ -312,9 +312,9 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { address contractAddress, string calldata functionSig, address accountToRevoke - ) external onlyRole(DEFAULT_ADMIN_ROLE) { + ) external onlyRole(MANAGER) { bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); - revokeRole(role, accountToRevoke); + _revokeRole(role, accountToRevoke); emit PermissionRevoked(accountToRevoke, contractAddress, functionSig); } diff --git a/test/foundry_tests/SDRewardsManager.t.sol b/test/foundry_tests/SDRewardsManager.t.sol index 9de9525d..0cc56b4d 100644 --- a/test/foundry_tests/SDRewardsManager.t.sol +++ b/test/foundry_tests/SDRewardsManager.t.sol @@ -16,6 +16,7 @@ import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC contract SDRewardManagerTest is Test { address staderAdmin; + address staderManager; address user1; address user2; address staderTokenDeployer; @@ -35,6 +36,7 @@ contract SDRewardManagerTest is Test { user2 = vm.addr(102); staderTokenDeployer = vm.addr(103); address ethDepositAddr = vm.addr(104); + staderManager = vm.addr(105); vm.prank(staderTokenDeployer); staderToken = new StaderTokenMock(); @@ -70,7 +72,11 @@ contract SDRewardManagerTest is Test { vm.startPrank(staderAdmin); staderConfig.updateStaderToken(address(staderToken)); + staderConfig.grantRole(staderConfig.MANAGER(), staderManager); staderConfig.updatePermissionlessSocializingPool(address(permissionlessSP)); + vm.stopPrank(); + + vm.startPrank(staderManager); staderConfig.giveCallPermission(address(rewardManager), "addRewardEntry(uint256,uint256)", user1); staderConfig.giveCallPermission(address(rewardManager), "approveEntry(uint256)", user1); vm.stopPrank(); From 64a43a8d408fef0045dd3156c2f96271aaab223b Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:12:16 +0000 Subject: [PATCH 07/10] feat: allow overriding last non approved entry --- contracts/SDRewardManager.sol | 19 +++++-- test/foundry_tests/SDRewardsManager.t.sol | 67 +++++++++++++++++++++-- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/contracts/SDRewardManager.sol b/contracts/SDRewardManager.sol index 99345c7e..13657007 100644 --- a/contracts/SDRewardManager.sol +++ b/contracts/SDRewardManager.sol @@ -35,7 +35,8 @@ contract SDRewardManager is Initializable { error AccessDenied(address account); error EntryNotFound(uint256 cycleNumber); error EntryAlreadyRegistered(uint256 cycleNumber); - error EntryAlreadApproved(uint256 cycleNumber); + error EntryAlreadyApproved(uint256 cycleNumber); + error InvalidCycleNumber(uint256 cycleNumber); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -61,14 +62,24 @@ contract SDRewardManager is Initializable { revert AccessDenied(msg.sender); } - if (_cycleNumber <= latestCycleNumber) { + SDRewardEntry memory rewardEntry = rewardEntries[_cycleNumber]; + + if (_cycleNumber < latestCycleNumber) { revert EntryAlreadyRegistered(_cycleNumber); } - SDRewardEntry storage rewardEntry = rewardEntries[_cycleNumber]; + if (_cycleNumber > latestCycleNumber + 1) { + revert InvalidCycleNumber(_cycleNumber); + } + + if (rewardEntry.approved) { + revert EntryAlreadyApproved(_cycleNumber); + } + rewardEntry.cycleNumber = _cycleNumber; rewardEntry.amount = _amount; latestCycleNumber = _cycleNumber; + rewardEntries[_cycleNumber] = rewardEntry; emit NewRewardEntry(_cycleNumber, _amount); } @@ -89,7 +100,7 @@ contract SDRewardManager is Initializable { } if (rewardEntry.approved) { - revert EntryAlreadApproved(_cycleNumber); + revert EntryAlreadyApproved(_cycleNumber); } rewardEntry.approved = true; diff --git a/test/foundry_tests/SDRewardsManager.t.sol b/test/foundry_tests/SDRewardsManager.t.sol index 0cc56b4d..ca457681 100644 --- a/test/foundry_tests/SDRewardsManager.t.sol +++ b/test/foundry_tests/SDRewardsManager.t.sol @@ -119,6 +119,29 @@ contract SDRewardManagerTest is Test { assertEq(isApproved, false); } + function test_addRewardEntry_multipleTimes() public { + uint256 cycleNumber = 1; + uint256 amount1 = 10 ether; + uint256 amount2 = 20 ether; + + // Only allowed contract can call the addRewardEntry function + vm.startPrank(user1); + + // Adding entry first time + rewardManager.addRewardEntry(cycleNumber, amount1); + + // Adding entry second time + rewardManager.addRewardEntry(cycleNumber, amount2); + + vm.stopPrank(); + + // Checking if the entry is correct + (uint256 storedCycleNumber, uint256 storedAmount, bool isApproved) = rewardManager.rewardEntries(cycleNumber); + assertEq(storedCycleNumber, cycleNumber); + assertEq(storedAmount, amount2); + assertEq(isApproved, false); + } + function test_addRewardEntry_AccessDenied() public { uint256 cycleNumber = 1; uint256 amount = 10 ether; @@ -138,8 +161,44 @@ contract SDRewardManagerTest is Test { rewardManager.addRewardEntry(cycleNumber, amount); // Attempt to add the same entry again - vm.expectRevert(abi.encodeWithSignature("EntryAlreadyRegistered(uint256)", cycleNumber)); - rewardManager.addRewardEntry(cycleNumber, amount); + vm.expectRevert(abi.encodeWithSignature("EntryAlreadyRegistered(uint256)", cycleNumber - 1)); + rewardManager.addRewardEntry(cycleNumber - 1, amount); + vm.stopPrank(); + } + + function test_addRewardEntry_InvalidCycleNumber() public { + uint256 cycleNumber1 = 1; + uint256 cycleNumber2 = 5; + uint256 amount = 10 ether; + + // Adding the first entry + vm.startPrank(user1); + rewardManager.addRewardEntry(cycleNumber1, amount); + + // Attempt to add the same entry again + vm.expectRevert(abi.encodeWithSignature("InvalidCycleNumber(uint256)", cycleNumber2)); + rewardManager.addRewardEntry(cycleNumber2, amount); + vm.stopPrank(); + } + + function test_addRewardEntry_EntryAlreadyApproved() public { + uint256 cycleNumber = 1; + uint256 amount1 = 10 ether; + uint256 amount2 = 20 ether; + + // Only allowed contract can call the addRewardEntry function + vm.startPrank(user1); + + // Adding entry first time + rewardManager.addRewardEntry(cycleNumber, amount1); + + // Approving Entry + rewardManager.approveEntry(cycleNumber); + + // Adding entry second time + vm.expectRevert(abi.encodeWithSignature("EntryAlreadyApproved(uint256)", cycleNumber)); + rewardManager.addRewardEntry(cycleNumber, amount2); + vm.stopPrank(); } @@ -182,7 +241,7 @@ contract SDRewardManagerTest is Test { rewardManager.approveEntry(cycleNumber + 100); } - function test_approveEntry_EntryAlreadApproved() public { + function test_approveEntry_EntryAlreadyApproved() public { uint256 cycleNumber = 1; uint256 amount = 10 ether; @@ -192,7 +251,7 @@ contract SDRewardManagerTest is Test { rewardManager.approveEntry(cycleNumber); // Attempt to approve the same entry again - vm.expectRevert(abi.encodeWithSignature("EntryAlreadApproved(uint256)", cycleNumber)); + vm.expectRevert(abi.encodeWithSignature("EntryAlreadyApproved(uint256)", cycleNumber)); rewardManager.approveEntry(cycleNumber); vm.stopPrank(); } From 49d65130aaa1e19d52b13ddedc13e0a88212a0b8 Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Mon, 4 Nov 2024 07:04:38 +0000 Subject: [PATCH 08/10] refactor: test file name --- .../{SDRewardsManager.t.sol => SDRewardManager.t.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/foundry_tests/{SDRewardsManager.t.sol => SDRewardManager.t.sol} (100%) diff --git a/test/foundry_tests/SDRewardsManager.t.sol b/test/foundry_tests/SDRewardManager.t.sol similarity index 100% rename from test/foundry_tests/SDRewardsManager.t.sol rename to test/foundry_tests/SDRewardManager.t.sol From 27fc0bc3dbf0b42bb598422518b4e73ea9702530 Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:09:27 +0000 Subject: [PATCH 09/10] refactor: use legacy mechanism to check access control --- contracts/SDRewardManager.sol | 4 +-- contracts/StaderConfig.sol | 32 ++++++------------------ contracts/interfaces/IStaderConfig.sol | 16 +++++------- test/foundry_tests/SDRewardManager.t.sol | 7 ++---- 4 files changed, 18 insertions(+), 41 deletions(-) diff --git a/contracts/SDRewardManager.sol b/contracts/SDRewardManager.sol index 13657007..603b4425 100644 --- a/contracts/SDRewardManager.sol +++ b/contracts/SDRewardManager.sol @@ -58,7 +58,7 @@ contract SDRewardManager is Initializable { * @param _amount The amount of SD to be rewarded */ function addRewardEntry(uint256 _cycleNumber, uint256 _amount) external { - if (!staderConfig.isAllowedToCall(msg.sender, "addRewardEntry(uint256,uint256)")) { + if (!staderConfig.onlySDRewardEntryRole(msg.sender)) { revert AccessDenied(msg.sender); } @@ -89,7 +89,7 @@ contract SDRewardManager is Initializable { * @param _cycleNumber The cycle number for the reward entry */ function approveEntry(uint256 _cycleNumber) external { - if (!staderConfig.isAllowedToCall(msg.sender, "approveEntry(uint256)")) { + if (!staderConfig.onlySDRewardApproverRole(msg.sender)) { revert AccessDenied(msg.sender); } diff --git a/contracts/StaderConfig.sol b/contracts/StaderConfig.sol index dad1dfcc..1fb1b78a 100644 --- a/contracts/StaderConfig.sol +++ b/contracts/StaderConfig.sol @@ -67,6 +67,8 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { //Roles bytes32 public constant override MANAGER = keccak256("MANAGER"); bytes32 public constant override OPERATOR = keccak256("OPERATOR"); + bytes32 public constant override ROLE_SD_REWARD_ENTRY = keccak256("ROLE_SD_REWARD_ENTRY"); + bytes32 public constant override ROLE_SD_REWARD_APPROVER = keccak256("ROLE_SD_REWARD_APPROVER"); bytes32 public constant SD = keccak256("SD"); bytes32 public constant ETHx = keccak256("ETHx"); @@ -297,27 +299,6 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { setContract(SD_INCENTIVE_CONTROLLER, _sdIncentiveController); } - // Access Control - function giveCallPermission( - address contractAddress, - string calldata functionSig, - address accountToPermit - ) external onlyRole(MANAGER) { - bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); - _grantRole(role, accountToPermit); - emit PermissionGranted(accountToPermit, contractAddress, functionSig); - } - - function revokeCallPermission( - address contractAddress, - string calldata functionSig, - address accountToRevoke - ) external onlyRole(MANAGER) { - bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); - _revokeRole(role, accountToRevoke); - emit PermissionRevoked(accountToRevoke, contractAddress, functionSig); - } - //Constants Getters function getStakedEthPerNode() external view override returns (uint256) { @@ -558,9 +539,12 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { return hasRole(OPERATOR, account); } - function isAllowedToCall(address account, string calldata functionSig) external view returns (bool) { - bytes32 role = keccak256(abi.encodePacked(msg.sender, functionSig)); - return hasRole(role, account); + function onlySDRewardEntryRole(address account) external view override returns (bool) { + return hasRole(ROLE_SD_REWARD_ENTRY, account); + } + + function onlySDRewardApproverRole(address account) external view override returns (bool) { + return hasRole(ROLE_SD_REWARD_APPROVER, account); } function verifyDepositAndWithdrawLimits() internal view { diff --git a/contracts/interfaces/IStaderConfig.sol b/contracts/interfaces/IStaderConfig.sol index cf49f219..42973808 100644 --- a/contracts/interfaces/IStaderConfig.sol +++ b/contracts/interfaces/IStaderConfig.sol @@ -16,8 +16,6 @@ interface IStaderConfig { event SetAccount(bytes32 key, address newAddress); event SetContract(bytes32 key, address newAddress); event SetToken(bytes32 key, address newAddress); - event PermissionGranted(address indexed accountToPermit, address indexed contractAddress, string functionSig); - event PermissionRevoked(address indexed accountToRevoke, address indexed contractAddress, string functionSig); //Contracts function POOL_UTILS() external view returns (bytes32); @@ -76,6 +74,10 @@ interface IStaderConfig { function OPERATOR() external view returns (bytes32); + function ROLE_SD_REWARD_ENTRY() external view returns (bytes32); + + function ROLE_SD_REWARD_APPROVER() external view returns (bytes32); + // Constants function getStakedEthPerNode() external view returns (uint256); @@ -174,13 +176,7 @@ interface IStaderConfig { function onlyOperatorRole(address account) external view returns (bool); - function isAllowedToCall(address account, string calldata functionSig) external view returns (bool); - - function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external; + function onlySDRewardEntryRole(address account) external view returns (bool); - function revokeCallPermission( - address contractAddress, - string calldata functionSig, - address accountToRevoke - ) external; + function onlySDRewardApproverRole(address account) external view returns (bool); } diff --git a/test/foundry_tests/SDRewardManager.t.sol b/test/foundry_tests/SDRewardManager.t.sol index ca457681..27de92d9 100644 --- a/test/foundry_tests/SDRewardManager.t.sol +++ b/test/foundry_tests/SDRewardManager.t.sol @@ -74,11 +74,8 @@ contract SDRewardManagerTest is Test { staderConfig.updateStaderToken(address(staderToken)); staderConfig.grantRole(staderConfig.MANAGER(), staderManager); staderConfig.updatePermissionlessSocializingPool(address(permissionlessSP)); - vm.stopPrank(); - - vm.startPrank(staderManager); - staderConfig.giveCallPermission(address(rewardManager), "addRewardEntry(uint256,uint256)", user1); - staderConfig.giveCallPermission(address(rewardManager), "approveEntry(uint256)", user1); + staderConfig.grantRole(staderConfig.ROLE_SD_REWARD_APPROVER(), user1); + staderConfig.grantRole(staderConfig.ROLE_SD_REWARD_ENTRY(), user1); vm.stopPrank(); vm.startPrank(staderTokenDeployer); From 837cf25cc2c7604af369769a6963c61ffee61323 Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:11:29 +0000 Subject: [PATCH 10/10] feat: fetch cycle number from pool --- contracts/SDRewardManager.sol | 60 ++++++------- test/foundry_tests/SDRewardManager.t.sol | 104 ++++++++++------------- 2 files changed, 77 insertions(+), 87 deletions(-) diff --git a/contracts/SDRewardManager.sol b/contracts/SDRewardManager.sol index 603b4425..733e7755 100644 --- a/contracts/SDRewardManager.sol +++ b/contracts/SDRewardManager.sol @@ -4,6 +4,7 @@ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/I import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import { IStaderConfig } from "./interfaces/IStaderConfig.sol"; +import { ISocializingPool } from "./interfaces/ISocializingPool.sol"; import { UtilLib } from "./library/UtilLib.sol"; /** @@ -19,9 +20,11 @@ contract SDRewardManager is Initializable { bool approved; } + ///@notice Address of the Stader Config contract IStaderConfig public staderConfig; - uint256 public latestCycleNumber; + ///@notice Cycle number of the last added entry + uint256 public lastEntryCycleNumber; // Mapping of cycle numbers to reward entries mapping(uint256 => SDRewardEntry) public rewardEntries; @@ -34,9 +37,7 @@ contract SDRewardManager is Initializable { error AccessDenied(address account); error EntryNotFound(uint256 cycleNumber); - error EntryAlreadyRegistered(uint256 cycleNumber); error EntryAlreadyApproved(uint256 cycleNumber); - error InvalidCycleNumber(uint256 cycleNumber); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -53,64 +54,57 @@ contract SDRewardManager is Initializable { } /** - * @notice Adds a new reward entry for a specified cycle - * @param _cycleNumber The cycle number for the reward entry + * @notice Adds a new reward entry for the current cycle (fetched from socializing pool) * @param _amount The amount of SD to be rewarded */ - function addRewardEntry(uint256 _cycleNumber, uint256 _amount) external { + function addRewardEntry(uint256 _amount) external { if (!staderConfig.onlySDRewardEntryRole(msg.sender)) { revert AccessDenied(msg.sender); } - - SDRewardEntry memory rewardEntry = rewardEntries[_cycleNumber]; - - if (_cycleNumber < latestCycleNumber) { - revert EntryAlreadyRegistered(_cycleNumber); - } - - if (_cycleNumber > latestCycleNumber + 1) { - revert InvalidCycleNumber(_cycleNumber); - } + uint256 cycleNumber = getCurrentCycleNumber(); + SDRewardEntry memory rewardEntry = rewardEntries[cycleNumber]; if (rewardEntry.approved) { - revert EntryAlreadyApproved(_cycleNumber); + revert EntryAlreadyApproved(cycleNumber); } - rewardEntry.cycleNumber = _cycleNumber; + rewardEntry.cycleNumber = cycleNumber; rewardEntry.amount = _amount; - latestCycleNumber = _cycleNumber; - rewardEntries[_cycleNumber] = rewardEntry; + lastEntryCycleNumber = cycleNumber; + rewardEntries[cycleNumber] = rewardEntry; - emit NewRewardEntry(_cycleNumber, _amount); + emit NewRewardEntry(cycleNumber, _amount); } /** - * @notice Approves a reward entry for a specified cycle and transfers the reward amount. - * @param _cycleNumber The cycle number for the reward entry + * @notice Approves a reward entry for the current cycle (fetched from socializing pool) and transfers the reward amount. */ - function approveEntry(uint256 _cycleNumber) external { + function approveEntry() external { if (!staderConfig.onlySDRewardApproverRole(msg.sender)) { revert AccessDenied(msg.sender); } - SDRewardEntry storage rewardEntry = rewardEntries[_cycleNumber]; + uint256 cycleNumber = getCurrentCycleNumber(); + + SDRewardEntry storage rewardEntry = rewardEntries[cycleNumber]; if (rewardEntry.cycleNumber == 0) { - revert EntryNotFound(_cycleNumber); + revert EntryNotFound(cycleNumber); } if (rewardEntry.approved) { - revert EntryAlreadyApproved(_cycleNumber); + revert EntryAlreadyApproved(cycleNumber); } rewardEntry.approved = true; + if (rewardEntry.amount > 0) { IERC20Upgradeable(staderConfig.getStaderToken()).safeTransferFrom( msg.sender, staderConfig.getPermissionlessSocializingPool(), rewardEntry.amount ); - emit RewardEntryApproved(_cycleNumber, rewardEntry.amount); + emit RewardEntryApproved(cycleNumber, rewardEntry.amount); } } @@ -119,6 +113,14 @@ contract SDRewardManager is Initializable { * @return The latest SDRewardEntry struct for the most recent cycle */ function viewLatestEntry() external view returns (SDRewardEntry memory) { - return rewardEntries[latestCycleNumber]; + return rewardEntries[lastEntryCycleNumber]; + } + + /** + * @notice Fetch the current cycle number from permissionless socializing pool + * @return Current cycle number + */ + function getCurrentCycleNumber() public view returns (uint256) { + return ISocializingPool(staderConfig.getPermissionlessSocializingPool()).getCurrentRewardsIndex(); } } diff --git a/test/foundry_tests/SDRewardManager.t.sol b/test/foundry_tests/SDRewardManager.t.sol index 27de92d9..25edc58a 100644 --- a/test/foundry_tests/SDRewardManager.t.sol +++ b/test/foundry_tests/SDRewardManager.t.sol @@ -98,7 +98,12 @@ contract SDRewardManagerTest is Test { } function test_addRewardEntry() public { - uint256 cycleNumber = 1; + uint256 cycleNumber = 3; + vm.mockCall( + address(permissionlessSP), + abi.encodeWithSelector(ISocializingPool.getCurrentRewardsIndex.selector), + abi.encode(cycleNumber) + ); uint256 amount = 10 ether; // Only allowed contract can call the addRewardEntry function @@ -107,7 +112,7 @@ contract SDRewardManagerTest is Test { // Should emit event for adding entry vm.expectEmit(true, false, false, true); emit NewRewardEntry(cycleNumber, amount); - rewardManager.addRewardEntry(cycleNumber, amount); + rewardManager.addRewardEntry(amount); // Checking if the entry is correct (uint256 storedCycleNumber, uint256 storedAmount, bool isApproved) = rewardManager.rewardEntries(cycleNumber); @@ -125,10 +130,10 @@ contract SDRewardManagerTest is Test { vm.startPrank(user1); // Adding entry first time - rewardManager.addRewardEntry(cycleNumber, amount1); + rewardManager.addRewardEntry(amount1); // Adding entry second time - rewardManager.addRewardEntry(cycleNumber, amount2); + rewardManager.addRewardEntry(amount2); vm.stopPrank(); @@ -146,36 +151,7 @@ contract SDRewardManagerTest is Test { // anyone cannot call the addRewardEntry method vm.prank(user2); vm.expectRevert(abi.encodeWithSignature("AccessDenied(address)", user2)); - rewardManager.addRewardEntry(cycleNumber, amount); - } - - function test_addRewardEntry_EntryAlreadyRegistered() public { - uint256 cycleNumber = 1; - uint256 amount = 10 ether; - - // Adding the first entry - vm.startPrank(user1); - rewardManager.addRewardEntry(cycleNumber, amount); - - // Attempt to add the same entry again - vm.expectRevert(abi.encodeWithSignature("EntryAlreadyRegistered(uint256)", cycleNumber - 1)); - rewardManager.addRewardEntry(cycleNumber - 1, amount); - vm.stopPrank(); - } - - function test_addRewardEntry_InvalidCycleNumber() public { - uint256 cycleNumber1 = 1; - uint256 cycleNumber2 = 5; - uint256 amount = 10 ether; - - // Adding the first entry - vm.startPrank(user1); - rewardManager.addRewardEntry(cycleNumber1, amount); - - // Attempt to add the same entry again - vm.expectRevert(abi.encodeWithSignature("InvalidCycleNumber(uint256)", cycleNumber2)); - rewardManager.addRewardEntry(cycleNumber2, amount); - vm.stopPrank(); + rewardManager.addRewardEntry(amount); } function test_addRewardEntry_EntryAlreadyApproved() public { @@ -183,36 +159,41 @@ contract SDRewardManagerTest is Test { uint256 amount1 = 10 ether; uint256 amount2 = 20 ether; - // Only allowed contract can call the addRewardEntry function + // Only allowed user's can call the addRewardEntry function vm.startPrank(user1); // Adding entry first time - rewardManager.addRewardEntry(cycleNumber, amount1); + rewardManager.addRewardEntry(amount1); // Approving Entry - rewardManager.approveEntry(cycleNumber); + rewardManager.approveEntry(); // Adding entry second time vm.expectRevert(abi.encodeWithSignature("EntryAlreadyApproved(uint256)", cycleNumber)); - rewardManager.addRewardEntry(cycleNumber, amount2); + rewardManager.addRewardEntry(amount2); vm.stopPrank(); } function test_approveEntry() public { - uint256 cycleNumber = 1; + uint256 cycleNumber = 19; + vm.mockCall( + address(permissionlessSP), + abi.encodeWithSelector(ISocializingPool.getCurrentRewardsIndex.selector), + abi.encode(cycleNumber) + ); uint256 amount = 10 ether; // Add the entry vm.startPrank(user1); - rewardManager.addRewardEntry(cycleNumber, amount); + rewardManager.addRewardEntry(amount); (, uint256 storedAmount, ) = rewardManager.rewardEntries(cycleNumber); // Expect the RewardEntryApproved event vm.expectEmit(true, false, false, true); emit RewardEntryApproved(cycleNumber, storedAmount); // Approve the entry - rewardManager.approveEntry(cycleNumber); + rewardManager.approveEntry(); vm.stopPrank(); // Check if the entry is approved @@ -226,16 +207,7 @@ contract SDRewardManagerTest is Test { // anyone cannot call the approveEntry method vm.prank(user2); vm.expectRevert(abi.encodeWithSignature("AccessDenied(address)", user2)); - rewardManager.approveEntry(cycleNumber); - } - - function test_approveEntry_EntryNotFound() public { - uint256 cycleNumber = 1; - - // Attempt to approve an entry that doesn't exist - vm.expectRevert(abi.encodeWithSignature("EntryNotFound(uint256)", cycleNumber + 100)); - vm.prank(user1); - rewardManager.approveEntry(cycleNumber + 100); + rewardManager.approveEntry(); } function test_approveEntry_EntryAlreadyApproved() public { @@ -244,25 +216,41 @@ contract SDRewardManagerTest is Test { // Add the entry vm.startPrank(user1); - rewardManager.addRewardEntry(cycleNumber, amount); - rewardManager.approveEntry(cycleNumber); + rewardManager.addRewardEntry(amount); + rewardManager.approveEntry(); // Attempt to approve the same entry again vm.expectRevert(abi.encodeWithSignature("EntryAlreadyApproved(uint256)", cycleNumber)); - rewardManager.approveEntry(cycleNumber); + rewardManager.approveEntry(); vm.stopPrank(); } function test_viewLatestEntry() public { // Add the entries + uint256 cycleNumber = 100; + vm.mockCall( + address(permissionlessSP), + abi.encodeWithSelector(ISocializingPool.getCurrentRewardsIndex.selector), + abi.encode(cycleNumber) + ); vm.startPrank(user1); - rewardManager.addRewardEntry(1, 10 ether); - rewardManager.addRewardEntry(2, 20 ether); - rewardManager.addRewardEntry(3, 30 ether); + rewardManager.addRewardEntry(30 ether); vm.stopPrank(); SDRewardManager.SDRewardEntry memory lastestEntry = rewardManager.viewLatestEntry(); - assertEq(lastestEntry.cycleNumber, 3); + assertEq(lastestEntry.cycleNumber, cycleNumber); assertEq(lastestEntry.amount, 30 ether); } + + function getCurrentCycleNumber() public { + uint256 cycleNumber = 101; + vm.mockCall( + address(permissionlessSP), + abi.encodeWithSelector(ISocializingPool.getCurrentRewardsIndex.selector), + abi.encode(cycleNumber) + ); + + uint256 currentCycleStored = rewardManager.getCurrentCycleNumber(); + assertEq(currentCycleStored, cycleNumber); + } }