Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
763ceca
feat: RewardsManagerV2 and StipendDistributor contracts
owen-eth Sep 6, 2025
774ae42
Merge branch 'main' into rewards-manager-v2
owen-eth Sep 6, 2025
b330bbd
various design updates and addressing comments
owen-eth Sep 8, 2025
4d73776
updated abis
owen-eth Sep 8, 2025
2baebef
fix solhint errors
owen-eth Sep 9, 2025
13f480f
updates from comments
owen-eth Sep 9, 2025
da35e59
additional minor changes
owen-eth Sep 9, 2025
540c977
solhint err fix
owen-eth Sep 9, 2025
504345f
fix abi
owen-eth Sep 9, 2025
59cf9bd
added initial deployment scripts
owen-eth Sep 10, 2025
923913a
add constructor exception line to new contracts + minor fixes
owen-eth Sep 10, 2025
e96c0e5
renamed RewardsManagerV2 -> BlockRewardManager
owen-eth Sep 11, 2025
5eab8a0
updated README
owen-eth Sep 11, 2025
08d554a
Sanity check grantStipends and minor README update
owen-eth Sep 11, 2025
c7d613b
cleaner migrateRewards(), more sanity checks
owen-eth Sep 11, 2025
e22e708
Minor Readme changes, comment in BlockRewardsManager, removed mainnet…
owen-eth Sep 12, 2025
fce65c1
updated abis
owen-eth Sep 12, 2025
9cf4381
update BlockRewardManager mainnet script
owen-eth Sep 12, 2025
624af7b
added owner migration to StipendDistributor
owen-eth Sep 12, 2025
378bb95
Merge branch 'main' into rewards-manager-v2
owen-eth Sep 12, 2025
1bc51e4
Merge branch 'main' into rewards-manager-v2
shaspitz Sep 12, 2025
2f2f4a1
renamed stipenddistributor to rewarddistributor, added erc20 distribu…
owen-eth Sep 13, 2025
bd2e6df
Merge branch 'rewards-manager-v2' of https://github.com/primev/mev-co…
owen-eth Sep 13, 2025
b98b72f
added SafeERC20, some minor adjustments and checks
owen-eth Sep 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
495 changes: 495 additions & 0 deletions contracts-abi/abi/BlockRewardManager.abi

Large diffs are not rendered by default.

1,184 changes: 1,184 additions & 0 deletions contracts-abi/abi/RewardDistributor.abi

Large diffs are not rendered by default.

479 changes: 479 additions & 0 deletions contracts-abi/abi/RewardsManagerV2.abi

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions contracts-abi/script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ extract_and_save_abi "$BASE_DIR/out/RewardManager.sol/RewardManager.json" "$ABI_

extract_and_save_abi "$BASE_DIR/out/DepositManager.sol/DepositManager.json" "$ABI_DIR/DepositManager.abi"

extract_and_save_abi "$BASE_DIR/out/RewardDistributor.sol/RewardDistributor.json" "$ABI_DIR/RewardDistributor.abi"

extract_and_save_abi "$BASE_DIR/out/BlockRewardManager.sol/BlockRewardManager.json" "$ABI_DIR/BlockRewardManager.abi"

echo "ABI files extracted successfully."

GO_CODE_BASE_DIR="./clients"
Expand Down Expand Up @@ -115,6 +119,10 @@ generate_go_code "$ABI_DIR/RewardManager.abi" "RewardManager" "rewardmanager"

generate_go_code "$ABI_DIR/DepositManager.abi" "DepositManager" "depositmanager"

generate_go_code "$ABI_DIR/RewardDistributor.abi" "RewardDistributor" "rewarddistributor"

generate_go_code "$ABI_DIR/BlockRewardManager.abi" "BlockRewardManager" "blockrewardmanager"

echo "External ABI downloaded and processed successfully."

echo "Go code generated successfully in separate folders."
42 changes: 42 additions & 0 deletions contracts/contracts/interfaces/IBlockRewardManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: BSL 1.1
pragma solidity 0.8.26;


interface IBlockRewardManager {
// -------- Events --------
/// @notice Emitted for each proposer payment routed by this contract
event ProposerPaid(
address indexed feeRecipient,
uint256 indexed proposerAmt,
uint256 indexed rewardAmt
);
/// @notice Emitted when the treasury is withdrawn
event TreasuryWithdrawn(uint256 indexed treasuryAmt);
/// @notice Emitted when the rewards pct is set
event RewardsPctBpsSet(uint256 indexed rewardsPctBps);
/// @notice Emitted when the treasury is set
event TreasurySet(address indexed treasury);

// -------- Errors --------
error OnlyOwnerOrTreasury();
error RewardsPctTooHigh();
error TreasuryIsZero();
error NoFundsToWithdraw();
error ProposerTransferFailed(address feeRecipient, uint256 amount);
error TreasuryTransferFailed(address treasury, uint256 amount);

/// @notice Builders/relays call this to route EL rewards *through* this contract.
function payProposer(address payable feeRecipient) external payable;

function withdrawToTreasury() external;

function setRewardsPctBps(uint256 rewardsPctBps) external;

function setTreasury(address treasury) external;

// -------- Admin --------
function initialize(address initialOwner, uint256 rewardsPctBps, address treasury) external;



}
101 changes: 101 additions & 0 deletions contracts/contracts/interfaces/IRewardDistributor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: BSL 1.1
pragma solidity 0.8.26;

/// @title IStipendDistributor
/// @notice Interface for stipend distribution and claims.
interface IRewardDistributor {

struct Distribution {
address operator;
address recipient;
uint128 amount;
}

/// @dev Pack both counters into a single slot for each asset.
struct RewardData {
uint128 accrued;
uint128 claimed;
}

// -------- Events --------
/// @dev Emitted when the oracle address is updated.
event RewardManagerSet(address indexed rewardManager);

/// @dev Emitted when stipends are granted.
event ETHGranted(address indexed operator, address indexed recipient, uint256 indexed amount);
event TokensGranted(address indexed operator, address indexed recipient, uint256 indexed amount);
event RewardsBatchGranted(uint256 indexed tokenID, uint256 indexed amount);
/// @dev Emitted when rewards are claimed by a recipient for an operator.
event ETHRewardsClaimed(address indexed operator, address indexed recipient, uint256 indexed amount);
event TokenRewardsClaimed(address indexed operator, address indexed recipient, uint256 indexed amount);

/// @dev Emitted when a recipient mapping is overridden for a specific pubkey.
event RecipientSet(address indexed operator, bytes pubkey, address indexed recipient);

/// @dev Emitted when an operator sets/updates their global override recipient.
event OperatorGlobalOverrideSet(address indexed operator, address indexed recipient);

/// @dev Emitted when an operator sets/updates a claim delegate for a given recipient.
event ClaimDelegateSet(address indexed operator, address indexed recipient, address indexed delegate, bool status);

/// @dev Emitted when accrued rewards are migrated from one recipient to another for an operator.
event RewardsMigrated(uint256 tokenID, address indexed operator, address indexed from, address indexed to, uint128 amount);

/// @dev Emitted when accrued rewards are reclaimed by the owner.
event RewardsReclaimed(uint256 indexed tokenID, address indexed operator, address indexed recipient, uint256 amount);

/// @dev Emitted when the reward token address is updated.
event RewardTokenSet(address indexed rewardToken, uint256 indexed tokenID);

// -------- Errors --------
error NotOwnerOrRewardManager();
error InvalidRewardToken();
error ZeroAddress();
error InvalidTokenID();
error InvalidBLSPubKeyLength();
error InvalidRecipient();
error InvalidOperator();
error InvalidClaimDelegate();
error LengthMismatch();
error NoClaimableRewards(address operator, address recipient);
error RewardsTransferFailed(address recipient);
error IncorrectPaymentAmount(uint256 received, uint256 expected);

// -------- Externals --------
/// @notice Initialize the proxy.
function initialize(address owner, address rewardManager) external;

/// @notice Grant ETH rewards to multiple (operator, recipient) pairs.
function grantETHRewards(Distribution[] calldata rewardList) external payable;

/// @notice Grant token rewards to multiple (operator, recipient) pairs.
function grantTokenRewards(Distribution[] calldata rewardList, uint256 tokenID) external payable;

/// @notice Claim rewards for the caller (as operator) to specific recipients.
function claimRewards(address[] calldata recipients, uint256 tokenID) external;

/// @notice Claim rewards on behalf of an operator to specific recipients (must be delegated).
function claimOnbehalfOfOperator(address operator, address[] calldata recipients, uint256 tokenID) external;

/// @notice Override recipient for a list of BLS pubkeys in a registry.
function overrideRecipientByPubkey(bytes[] calldata pubkeys, address recipient) external;

/// @notice Set the caller's global override recipient for any non-overridden keys.
function setOperatorGlobalOverride(address recipient) external;

/// @notice Allow or revoke a delegate to claim for a given recipient of the caller (operator).
function setClaimDelegate(address delegate, address recipient, bool status) external;

/// @notice Migrate unclaimed rewards from one recipient to another for the caller (operator).
function migrateExistingRewards(address from, address to, uint256 tokenID) external;

/// @notice Pause / Unpause admin controls.
function reclaimStipendsToOwner(address[] calldata operators, address[] calldata recipients, uint256 tokenID) external;
function pause() external;
function unpause() external;
function setRewardManager(address _rewardManager) external;
function setRewardToken(address _rewardToken, uint256 _id) external;
function getKeyRecipient(address operator, bytes calldata pubkey) external view returns (address);
function getPendingRewards(address operator, address recipient, uint256 tokenID) external view returns (uint128);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: BSL 1.1
pragma solidity 0.8.26;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";

import {IBlockRewardManager} from "../../interfaces/IBlockRewardManager.sol";
import {BlockRewardManagerStorage} from "./BlockRewardManagerStorage.sol";
import {Errors} from "../../utils/Errors.sol";

contract BlockRewardManager is
Initializable,
Ownable2StepUpgradeable,
ReentrancyGuardUpgradeable,
BlockRewardManagerStorage,
IBlockRewardManager,
UUPSUpgradeable
{
uint256 constant _BPS_DENOMINATOR = 10_000;

modifier onlyOwnerOrTreasury() {
require(msg.sender == owner() || msg.sender == treasury, OnlyOwnerOrTreasury());
_;
}

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

// -------- Receive/Fallback (explicitly disabled) --------
receive() external payable { revert Errors.InvalidReceive(); }
fallback() external payable { revert Errors.InvalidFallback(); }

// -------- Initializer --------
function initialize(address initialOwner, uint256 rewardsPctBps, address treasury) external initializer override {
__Ownable_init(initialOwner);
__ReentrancyGuard_init();
__UUPSUpgradeable_init();
_setRewardsPctBps(rewardsPctBps);
_setTreasury(treasury);
}

// -------- Proposer payment (EL rewards routed through this contract) --------
function payProposer(address payable feeRecipient) external payable {
uint256 totalAmt = msg.value;
uint256 bps = rewardsPctBps;
//two paths here for gas savings
if (bps == 0) {
(bool success, ) = feeRecipient.call{value: totalAmt}("");
require(success, ProposerTransferFailed(feeRecipient, totalAmt)); //revert if transfer fails
emit ProposerPaid(feeRecipient, totalAmt, 0);
} else {
uint256 amtForRewards = totalAmt * bps / _BPS_DENOMINATOR;
uint256 proposerAmt = totalAmt - amtForRewards;
toTreasury += amtForRewards;
(bool success, ) = feeRecipient.call{value: proposerAmt}("");
require(success, ProposerTransferFailed(feeRecipient, proposerAmt)); //revert if transfer fails
emit ProposerPaid(feeRecipient, proposerAmt, amtForRewards);
}
}

function withdrawToTreasury() external onlyOwnerOrTreasury {
require(toTreasury > 0, NoFundsToWithdraw());
uint256 treasuryAmt = toTreasury;
toTreasury = 0;
(bool success, ) = treasury.call{value: treasuryAmt}(""); //Treasury will not revert
require(success, TreasuryTransferFailed(treasury, treasuryAmt)); //revert if transfer fails
emit TreasuryWithdrawn(treasuryAmt);
}

function setRewardsPctBps(uint256 rewardsPctBps) external onlyOwner {
_setRewardsPctBps(rewardsPctBps);
}

function setTreasury(address treasury) external onlyOwner {
_setTreasury(treasury);
}

function _setTreasury(address _treasury) internal {
require(_treasury != address(0), TreasuryIsZero());
treasury = payable(_treasury);
emit TreasurySet(_treasury);
}

function _setRewardsPctBps(uint256 _rewardsPctBps) internal {
require (_rewardsPctBps <= 2500, RewardsPctTooHigh());
rewardsPctBps = _rewardsPctBps;
emit RewardsPctBpsSet(_rewardsPctBps);
}

// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address) internal override onlyOwner {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

abstract contract BlockRewardManagerStorage {

uint256 public toTreasury;
uint256 public rewardsPctBps;
address payable public treasury;

uint256[42] private __gap; // reserve slots for upgrades

}
Loading
Loading