Skip to content

Commit

Permalink
feat: Add distribution data history and rebate on distribution (#382)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgusakov authored Jan 17, 2025
1 parent b338668 commit 4994ecf
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 56 deletions.
3 changes: 2 additions & 1 deletion script/DeployBase.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ abstract contract DeployBase is Script {
CSFeeDistributor feeDistributorImpl = new CSFeeDistributor({
stETH: locator.lido(),
accounting: address(accounting),
oracle: address(oracle)
oracle: address(oracle),
rebateRecipient: config.aragonAgent
});
feeDistributor = CSFeeDistributor(
_deployProxy(config.proxyAdmin, address(feeDistributorImpl))
Expand Down
3 changes: 2 additions & 1 deletion script/DeployImplementationsBase.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ abstract contract DeployImplementationsBase is DeployBase {
CSFeeDistributor feeDistributorImpl = new CSFeeDistributor({
stETH: locator.lido(),
accounting: address(accounting),
oracle: address(oracle)
oracle: address(oracle),
rebateRecipient: config.aragonAgent
});

verifier = new CSVerifier({
Expand Down
56 changes: 50 additions & 6 deletions src/CSFeeDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ contract CSFeeDistributor is
IStETH public immutable STETH;
address public immutable ACCOUNTING;
address public immutable ORACLE;
address public immutable REBATE_RECIPIENT;

/// @notice Merkle Tree root
/// @notice The latest Merkle Tree root
bytes32 public treeRoot;

/// @notice CID of the published Merkle tree
/// @notice CID of the last published Merkle tree
string public treeCid;

/// @notice CID of the file with log of the last frame reported
/// @notice CID of the file with log for the last frame reported
string public logCid;

/// @notice Amount of stETH shares sent to the Accounting in favor of the NO
Expand All @@ -40,14 +41,27 @@ contract CSFeeDistributor is
/// @notice Total Amount of stETH shares available for claiming by NOs
uint256 public totalClaimableShares;

constructor(address stETH, address accounting, address oracle) {
/// @notice Array of the distribution data history
mapping(uint256 => DistributionData) internal _distributionDataHistory;

/// @notice The number of _distributionDataHistory records
uint256 public distributionDataHistoryCount;

constructor(
address stETH,
address accounting,
address oracle,
address rebateRecipient
) {
if (accounting == address(0)) revert ZeroAccountingAddress();
if (oracle == address(0)) revert ZeroOracleAddress();
if (stETH == address(0)) revert ZeroStEthAddress();
if (rebateRecipient == address(0)) revert ZeroRebateRecipientAddress();

ACCOUNTING = accounting;
STETH = IStETH(stETH);
ORACLE = oracle;
REBATE_RECIPIENT = rebateRecipient;

_disableInitializers();
}
Expand Down Expand Up @@ -90,11 +104,14 @@ contract CSFeeDistributor is
bytes32 _treeRoot,
string calldata _treeCid,
string calldata _logCid,
uint256 distributed
uint256 distributed,
uint256 rebate,
uint256 refSlot
) external {
if (msg.sender != ORACLE) revert NotOracle();
if (
totalClaimableShares + distributed > STETH.sharesOf(address(this))
totalClaimableShares + distributed + rebate >
STETH.sharesOf(address(this))
) {
revert InvalidShares();
}
Expand Down Expand Up @@ -123,6 +140,11 @@ contract CSFeeDistributor is

emit ModuleFeeDistributed(distributed);

if (rebate > 0) {
STETH.transferShares(REBATE_RECIPIENT, rebate);
emit RebateTransferred(rebate);
}

// NOTE: Make sure off-chain tooling provides a distinct CID of a log even for empty reports, e.g. by mixing
// in a frame identifier such as reference slot to a file.
if (bytes(_logCid).length == 0) revert InvalidLogCID();
Expand All @@ -131,6 +153,21 @@ contract CSFeeDistributor is

logCid = _logCid;
emit DistributionLogUpdated(_logCid);

_distributionDataHistory[
distributionDataHistoryCount
] = DistributionData({
refSlot: refSlot,
treeRoot: treeRoot,
treeCid: treeCid,
logCid: _logCid,
distributed: distributed,
rebate: rebate
});

unchecked {
++distributionDataHistoryCount;
}
}

/// @inheritdoc AssetRecoverer
Expand All @@ -147,6 +184,13 @@ contract CSFeeDistributor is
return STETH.sharesOf(address(this)) - totalClaimableShares;
}

/// @inheritdoc ICSFeeDistributor
function getHistoricalDistributionData(
uint256 index
) external view returns (DistributionData memory) {
return _distributionDataHistory[index];
}

/// @inheritdoc ICSFeeDistributor
function getFeesToDistribute(
uint256 nodeOperatorId,
Expand Down
14 changes: 8 additions & 6 deletions src/CSFeeOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,14 @@ contract CSFeeOracle is
}

function _handleConsensusReportData(ReportData calldata data) internal {
feeDistributor.processOracleReport(
data.treeRoot,
data.treeCid,
data.logCid,
data.distributed
);
feeDistributor.processOracleReport({
_treeRoot: data.treeRoot,
_treeCid: data.treeCid,
_logCid: data.logCid,
distributed: data.distributed,
rebate: data.rebate,
refSlot: data.refSlot
});
}

function _checkMsgSenderIsAllowedToSubmitData() internal view {
Expand Down
37 changes: 36 additions & 1 deletion src/interfaces/ICSFeeDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ import { IStETH } from "./IStETH.sol";
pragma solidity 0.8.24;

interface ICSFeeDistributor is IAssetRecovererLib {
struct DistributionData {
/// @dev Reference slot for which the report was calculated. If the slot
/// contains a block, the state being reported should include all state
/// changes resulting from that block. The epoch containing the slot
/// should be finalized prior to calculating the report.
uint256 refSlot;
/// @notice Merkle Tree root.
bytes32 treeRoot;
/// @notice CID of the published Merkle tree.
string treeCid;
/// @notice CID of the file with log of the frame reported.
string logCid;
/// @notice Total amount of fees distributed in the report.
uint256 distributed;
/// @notice Amount of the rebate shares in the report
uint256 rebate;
}

/// @dev Emitted when fees are distributed
event OperatorFeeDistributed(
uint256 indexed nodeOperatorId,
Expand All @@ -26,10 +44,14 @@ interface ICSFeeDistributor is IAssetRecovererLib {
/// @dev It logs how many shares were distributed in the latest report
event ModuleFeeDistributed(uint256 shares);

/// @dev Emitted when rebate is transferred
event RebateTransferred(uint256 shares);

error ZeroAccountingAddress();
error ZeroStEthAddress();
error ZeroAdminAddress();
error ZeroOracleAddress();
error ZeroRebateRecipientAddress();
error NotAccounting();
error NotOracle();

Expand All @@ -49,6 +71,8 @@ interface ICSFeeDistributor is IAssetRecovererLib {

function ORACLE() external view returns (address);

function REBATE_RECIPIENT() external view returns (address);

function treeRoot() external view returns (bytes32);

function treeCid() external view returns (string calldata);
Expand Down Expand Up @@ -85,17 +109,28 @@ interface ICSFeeDistributor is IAssetRecovererLib {
/// @param _treeRoot Root of the Merkle tree
/// @param _treeCid an IPFS CID of the tree
/// @param _logCid an IPFS CID of the log
/// @param distributed an amount of the distributed shares
/// @param rebate an amount of the rebate shares
/// @param refSlot refSlot of the report
function processOracleReport(
bytes32 _treeRoot,
string calldata _treeCid,
string calldata _logCid,
uint256 _distributedShares
uint256 distributed,
uint256 rebate,
uint256 refSlot
) external;

/// @notice Get the Amount of stETH shares that are pending to be distributed
/// @return pendingShares Amount shares that are pending to distribute
function pendingSharesToDistribute() external view returns (uint256);

/// @notice Get the historical record of distribution data
/// @return index Historical entry index
function getHistoricalDistributionData(
uint256 index
) external view returns (DistributionData memory);

/// @notice Get a hash of a leaf
/// @param nodeOperatorId ID of the Node Operator
/// @param shares Amount of stETH shares
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces/ICSFeeOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ interface ICSFeeOracle is IAssetRecovererLib {
bytes32 treeRoot;
/// @notice CID of the published Merkle tree.
string treeCid;
/// @notice CID of the file with log of the last frame reported.
/// @notice CID of the file with log of the frame reported.
string logCid;
/// @notice Total amount of fees distributed in the report.
uint256 distributed;
/// @notice Amount of the rebate shares in the report
uint256 rebate;
}

/// @dev Emitted when a new fee distributor contract is set
Expand Down
Loading

0 comments on commit 4994ecf

Please sign in to comment.