diff --git a/.gitmodules b/.gitmodules index a0e0ceb5..7d79667c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/foundry-rs/forge-std [submodule "lib/openzeppelin-foundry-upgrades"] path = lib/openzeppelin-foundry-upgrades - url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades \ No newline at end of file + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades diff --git a/contracts/SDRewardManager.sol b/contracts/SDRewardManager.sol new file mode 100644 index 00000000..733e7755 --- /dev/null +++ b/contracts/SDRewardManager.sol @@ -0,0 +1,126 @@ +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 { ISocializingPool } from "./interfaces/ISocializingPool.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; + bool approved; + } + + ///@notice Address of the Stader Config contract + IStaderConfig public staderConfig; + + ///@notice Cycle number of the last added entry + uint256 public lastEntryCycleNumber; + + // 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 EntryAlreadyApproved(uint256 cycleNumber); + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _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 the current cycle (fetched from socializing pool) + * @param _amount The amount of SD to be rewarded + */ + function addRewardEntry(uint256 _amount) external { + if (!staderConfig.onlySDRewardEntryRole(msg.sender)) { + revert AccessDenied(msg.sender); + } + uint256 cycleNumber = getCurrentCycleNumber(); + SDRewardEntry memory rewardEntry = rewardEntries[cycleNumber]; + + if (rewardEntry.approved) { + revert EntryAlreadyApproved(cycleNumber); + } + + rewardEntry.cycleNumber = cycleNumber; + rewardEntry.amount = _amount; + lastEntryCycleNumber = cycleNumber; + rewardEntries[cycleNumber] = rewardEntry; + + emit NewRewardEntry(cycleNumber, _amount); + } + + /** + * @notice Approves a reward entry for the current cycle (fetched from socializing pool) and transfers the reward amount. + */ + function approveEntry() external { + if (!staderConfig.onlySDRewardApproverRole(msg.sender)) { + revert AccessDenied(msg.sender); + } + + uint256 cycleNumber = getCurrentCycleNumber(); + + SDRewardEntry storage rewardEntry = rewardEntries[cycleNumber]; + + if (rewardEntry.cycleNumber == 0) { + revert EntryNotFound(cycleNumber); + } + + if (rewardEntry.approved) { + 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); + } + } + + /** + * @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[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/contracts/StaderConfig.sol b/contracts/StaderConfig.sol index 24af1f35..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"); @@ -537,6 +539,14 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { return hasRole(OPERATOR, 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 { if ( !(variablesMap[MIN_DEPOSIT_AMOUNT] != 0 && diff --git a/contracts/StaderOracle.sol b/contracts/StaderOracle.sol index bec4b6e2..60f9f0c2 100644 --- a/contracts/StaderOracle.sol +++ b/contracts/StaderOracle.sol @@ -29,7 +29,7 @@ contract StaderOracle is IStaderOracle, AccessControlUpgradeable, PausableUpgrad uint256 public constant MAX_ER_UPDATE_FREQUENCY = 7200 * 7; // 7 days uint256 public constant ER_CHANGE_MAX_BPS = 10_000; uint256 public override erChangeLimit; - uint256 public constant MIN_TRUSTED_NODES = 5; + uint256 public constant MIN_TRUSTED_NODES = 3; uint256 public override trustedNodeChangeCoolingPeriod; /// @inheritdoc IStaderOracle @@ -119,6 +119,9 @@ contract StaderOracle is IStaderOracle, AccessControlUpgradeable, PausableUpgrad if (block.number < lastTrustedNodeCountChangeBlock + trustedNodeChangeCoolingPeriod) { revert CooldownNotComplete(); } + if (trustedNodesCount <= MIN_TRUSTED_NODES) { + revert InsufficientTrustedNodes(); + } lastTrustedNodeCountChangeBlock = block.number; isTrustedNode[_nodeAddress] = false; @@ -304,8 +307,7 @@ contract StaderOracle is IStaderOracle, AccessControlUpgradeable, PausableUpgrad // Emit SD Price submitted event emit SDPriceSubmitted(msg.sender, _sdPriceData.sdPriceInETH, _sdPriceData.reportingBlockNumber, block.number); - // price can be derived once more than 66% percent oracles have submitted price - if ((submissionCount >= (2 * trustedNodesCount) / 3 + 1)) { + if ((submissionCount >= trustedNodesCount / 2 + 1)) { lastReportedSDPriceData = _sdPriceData; lastReportedSDPriceData.sdPriceInETH = getMedianValue(sdPrices); diff --git a/contracts/interfaces/IStaderConfig.sol b/contracts/interfaces/IStaderConfig.sol index f8f9854a..42973808 100644 --- a/contracts/interfaces/IStaderConfig.sol +++ b/contracts/interfaces/IStaderConfig.sol @@ -74,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); @@ -171,4 +175,8 @@ interface IStaderConfig { function onlyManagerRole(address account) external view returns (bool); function onlyOperatorRole(address account) external view returns (bool); + + function onlySDRewardEntryRole(address account) external view returns (bool); + + function onlySDRewardApproverRole(address account) external view returns (bool); } 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/SDRewardManager.t.sol b/test/foundry_tests/SDRewardManager.t.sol new file mode 100644 index 00000000..25edc58a --- /dev/null +++ b/test/foundry_tests/SDRewardManager.t.sol @@ -0,0 +1,256 @@ +// 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 staderManager; + 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); + staderManager = vm.addr(105); + + 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.grantRole(staderConfig.MANAGER(), staderManager); + staderConfig.updatePermissionlessSocializingPool(address(permissionlessSP)); + staderConfig.grantRole(staderConfig.ROLE_SD_REWARD_APPROVER(), user1); + staderConfig.grantRole(staderConfig.ROLE_SD_REWARD_ENTRY(), 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 = 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 + vm.prank(user1); + + // Should emit event for adding entry + vm.expectEmit(true, false, false, true); + emit NewRewardEntry(cycleNumber, amount); + rewardManager.addRewardEntry(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_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(amount1); + + // Adding entry second time + rewardManager.addRewardEntry(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; + + // anyone cannot call the addRewardEntry method + vm.prank(user2); + vm.expectRevert(abi.encodeWithSignature("AccessDenied(address)", user2)); + rewardManager.addRewardEntry(amount); + } + + function test_addRewardEntry_EntryAlreadyApproved() public { + uint256 cycleNumber = 1; + uint256 amount1 = 10 ether; + uint256 amount2 = 20 ether; + + // Only allowed user's can call the addRewardEntry function + vm.startPrank(user1); + + // Adding entry first time + rewardManager.addRewardEntry(amount1); + + // Approving Entry + rewardManager.approveEntry(); + + // Adding entry second time + vm.expectRevert(abi.encodeWithSignature("EntryAlreadyApproved(uint256)", cycleNumber)); + rewardManager.addRewardEntry(amount2); + + vm.stopPrank(); + } + + function test_approveEntry() public { + 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(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(); + 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(); + } + + function test_approveEntry_EntryAlreadyApproved() public { + uint256 cycleNumber = 1; + uint256 amount = 10 ether; + + // Add the entry + vm.startPrank(user1); + rewardManager.addRewardEntry(amount); + rewardManager.approveEntry(); + + // Attempt to approve the same entry again + vm.expectRevert(abi.encodeWithSignature("EntryAlreadyApproved(uint256)", 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(30 ether); + vm.stopPrank(); + + SDRewardManager.SDRewardEntry memory lastestEntry = rewardManager.viewLatestEntry(); + 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); + } +} diff --git a/test/foundry_tests/StaderOracle.t.sol b/test/foundry_tests/StaderOracle.t.sol index b818690d..bb5f2c30 100644 --- a/test/foundry_tests/StaderOracle.t.sol +++ b/test/foundry_tests/StaderOracle.t.sol @@ -125,6 +125,7 @@ contract StaderOracleTest is Test { } function test_add_remove_trustedNode() public { + // Tests for adding nodes address trustedNode = vm.addr(123); assertEq(staderOracle.trustedNodesCount(), 0); assertFalse(staderOracle.isTrustedNode(trustedNode)); @@ -139,16 +140,6 @@ contract StaderOracleTest is Test { assertEq(staderOracle.trustedNodesCount(), 1); assertTrue(staderOracle.isTrustedNode(trustedNode)); - vm.expectRevert(IStaderOracle.NodeNotTrusted.selector); - vm.prank(staderManager); - staderOracle.removeTrustedNode(vm.addr(567)); - - vm.prank(staderManager); - staderOracle.removeTrustedNode(trustedNode); - - assertEq(staderOracle.trustedNodesCount(), 0); - assertFalse(staderOracle.isTrustedNode(trustedNode)); - // lets update trustedNode cooling period vm.expectRevert(UtilLib.CallerNotManager.selector); staderOracle.updateTrustedNodeChangeCoolingPeriod(100); @@ -157,22 +148,36 @@ contract StaderOracleTest is Test { staderOracle.updateTrustedNodeChangeCoolingPeriod(100); vm.expectRevert(IStaderOracle.CooldownNotComplete.selector); - staderOracle.addTrustedNode(vm.addr(78)); + staderOracle.addTrustedNode(vm.addr(77)); - // wait for 100 blocks + // wait for 100 blocks each time to add node + vm.roll(block.number + 100); + staderOracle.addTrustedNode(vm.addr(77)); vm.roll(block.number + 100); staderOracle.addTrustedNode(vm.addr(78)); - assertEq(staderOracle.trustedNodesCount(), 1); + vm.roll(block.number + 100); + staderOracle.addTrustedNode(vm.addr(79)); + assertEq(staderOracle.trustedNodesCount(), 4); + assertTrue(staderOracle.isTrustedNode(vm.addr(77))); assertTrue(staderOracle.isTrustedNode(vm.addr(78))); + assertTrue(staderOracle.isTrustedNode(vm.addr(79))); + + // Tests for removing nodes + vm.expectRevert(IStaderOracle.NodeNotTrusted.selector); + staderOracle.removeTrustedNode(vm.addr(567)); vm.expectRevert(IStaderOracle.CooldownNotComplete.selector); - staderOracle.removeTrustedNode(vm.addr(78)); + staderOracle.removeTrustedNode(vm.addr(77)); // wait for 100 blocks vm.roll(block.number + 100); + staderOracle.removeTrustedNode(vm.addr(77)); + assertEq(staderOracle.trustedNodesCount(), 3); + assertFalse(staderOracle.isTrustedNode(vm.addr(77))); + + vm.roll(block.number + 100); + vm.expectRevert(IStaderOracle.InsufficientTrustedNodes.selector); staderOracle.removeTrustedNode(vm.addr(78)); - assertEq(staderOracle.trustedNodesCount(), 0); - assertFalse(staderOracle.isTrustedNode(vm.addr(78))); vm.stopPrank(); } @@ -189,7 +194,7 @@ contract StaderOracleTest is Test { vm.expectRevert(IStaderOracle.InsufficientTrustedNodes.selector); staderOracle.submitSDPrice(sdPriceData); - assertEq(staderOracle.MIN_TRUSTED_NODES(), 5); + assertEq(staderOracle.MIN_TRUSTED_NODES(), 3); address trustedNode2 = vm.addr(702); address trustedNode3 = vm.addr(703); address trustedNode4 = vm.addr(704); @@ -307,17 +312,12 @@ contract StaderOracleTest is Test { vm.prank(trustedNode3); staderOracle.submitSDPrice(sdPriceData); - sdPriceData.reportingBlockNumber = 5 * 7200; - sdPriceData.sdPriceInETH = 4; - vm.prank(trustedNode4); - staderOracle.submitSDPrice(sdPriceData); - // now consensus is met for reporting block num 5 * 7200 // trustedNode1 manipulated the sd price if other oracles are not wrking properly - // sdPrice submited were [1,6,2,4] => hence median = (2+4)/2 = 3 + // sdPrice submited were [1,6,2] => hence median = 2 (lastSDReportingBlockNumber, lastSDPrice) = staderOracle.lastReportedSDPriceData(); assertEq(lastSDReportingBlockNumber, 5 * 7200); - assertEq(lastSDPrice, 3); + assertEq(lastSDPrice, 2); // trusted node 5 tries to submit at reportable block 5 * 7200 sdPriceData.reportingBlockNumber = 5 * 7200; @@ -330,7 +330,7 @@ contract StaderOracleTest is Test { function test_submitSDPrice_manipulation_not_possible_by_minority_malicious_oracles() public { SDPriceData memory sdPriceData = SDPriceData({ reportingBlockNumber: 1212, sdPriceInETH: 1 }); - assertEq(staderOracle.MIN_TRUSTED_NODES(), 5); + assertEq(staderOracle.MIN_TRUSTED_NODES(), 3); address trustedNode1 = vm.addr(701); address trustedNode2 = vm.addr(702); address trustedNode3 = vm.addr(703); @@ -355,9 +355,6 @@ contract StaderOracleTest is Test { vm.prank(trustedNode2); staderOracle.submitSDPrice(sdPriceData); - vm.prank(trustedNode3); - staderOracle.submitSDPrice(sdPriceData); - // cycle 2 vm.roll(2 * 7200 + 1); sdPriceData.reportingBlockNumber = 2 * 7200; @@ -369,9 +366,6 @@ contract StaderOracleTest is Test { vm.prank(trustedNode2); staderOracle.submitSDPrice(sdPriceData); - vm.prank(trustedNode3); - staderOracle.submitSDPrice(sdPriceData); - // trustedNode4 submits for cycle 1 sdPriceData.reportingBlockNumber = 1 * 7200; sdPriceData.sdPriceInETH = 1; @@ -800,7 +794,7 @@ contract StaderOracleTest is Test { totalETHXSupply: 100 }); - assertEq(staderOracle.MIN_TRUSTED_NODES(), 5); + assertEq(staderOracle.MIN_TRUSTED_NODES(), 3); address trustedNode1 = vm.addr(701); address trustedNode2 = vm.addr(702); address trustedNode3 = vm.addr(703); @@ -1007,7 +1001,7 @@ contract StaderOracleTest is Test { sortedInvalidSignaturePubkeys: invalidSignaturePubkeys }); - assertEq(staderOracle.MIN_TRUSTED_NODES(), 5); + assertEq(staderOracle.MIN_TRUSTED_NODES(), 3); address trustedNode1 = vm.addr(701); address trustedNode2 = vm.addr(702); address trustedNode3 = vm.addr(703); @@ -1082,7 +1076,7 @@ contract StaderOracleTest is Test { sortedPubkeys: sortedPubkeys }); - assertEq(staderOracle.MIN_TRUSTED_NODES(), 5); + assertEq(staderOracle.MIN_TRUSTED_NODES(), 3); address trustedNode1 = vm.addr(701); address trustedNode2 = vm.addr(702); address trustedNode3 = vm.addr(703); @@ -1162,7 +1156,7 @@ contract StaderOracleTest is Test { sortedPubkeys: sortedPubkeys }); - assertEq(staderOracle.MIN_TRUSTED_NODES(), 5); + assertEq(staderOracle.MIN_TRUSTED_NODES(), 3); address trustedNode1 = vm.addr(701); address trustedNode2 = vm.addr(702); address trustedNode3 = vm.addr(703); @@ -1232,7 +1226,7 @@ contract StaderOracleTest is Test { slashedValidatorsCount: 4 }); - assertEq(staderOracle.MIN_TRUSTED_NODES(), 5); + assertEq(staderOracle.MIN_TRUSTED_NODES(), 3); address trustedNode1 = vm.addr(701); address trustedNode2 = vm.addr(702); address trustedNode3 = vm.addr(703);