Skip to content

Commit

Permalink
Merge branch 'main' into feat/remove-NT-key-checks
Browse files Browse the repository at this point in the history
  • Loading branch information
blockgroot committed Nov 28, 2024
2 parents 896ae0c + 781525e commit f0983b3
Show file tree
Hide file tree
Showing 8 changed files with 448 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
126 changes: 126 additions & 0 deletions contracts/SDRewardManager.sol
Original file line number Diff line number Diff line change
@@ -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();
}
}
10 changes: 10 additions & 0 deletions contracts/StaderConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Check warning on line 74 in contracts/StaderConfig.sol

View workflow job for this annotation

GitHub Actions / Run linters

Constant name must be in capitalized SNAKE_CASE
Expand Down Expand Up @@ -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 &&
Expand Down
8 changes: 5 additions & 3 deletions contracts/StaderOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
8 changes: 8 additions & 0 deletions contracts/interfaces/IStaderConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
12 changes: 12 additions & 0 deletions scripts/deploy/SDRewardManager.ts
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit f0983b3

Please sign in to comment.