Skip to content

Commit

Permalink
feat: factory and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vgorkavenko committed May 28, 2024
1 parent 449a9bb commit 21ef7fa
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 0 deletions.
109 changes: 109 additions & 0 deletions contracts/EVMScriptFactories/CSMSettleELStealingPenalty.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.6;

import "../TrustedCaller.sol";
import "../libraries/EVMScriptCreator.sol";
import "../interfaces/IEVMScriptFactory.sol";
import "../interfaces/ICSModule.sol";

/// @author vgorkavenko
/// @notice Creates EVMScript to settle EL stealing penalty for a specific node operators on CSM
contract CSMSettleElStealingPenalty is TrustedCaller, IEVMScriptFactory {

// -------------
// ERRORS
// -------------

string private constant ERROR_EMPTY_NODE_OPERATORS_IDS =
"EMPTY_NODE_OPERATORS_IDS";
string private constant ERROR_OUT_OF_RANGE_NODE_OPERATOR_ID =
"OUT_OF_RANGE_NODE_OPERATOR_ID";
string private constant ERROR_NOT_SORTED_NODE_OPERATORS_IDS =
"ERROR_NOT_SORTED_NODE_OPERATORS_IDS";

// -------------
// VARIABLES
// -------------

/// @notice Address of CSModule
ICSModule public immutable csm;

// -------------
// CONSTRUCTOR
// -------------

constructor(address _trustedCaller, address _csm)
TrustedCaller(_trustedCaller)
{
csm = ICSModule(_csm);
}

// -------------
// EXTERNAL METHODS
// -------------

/// @notice Creates EVMScript to settle EL stealing penalty for a specific node operators on CSM
/// @param _creator Address who creates EVMScript
/// @param _evmScriptCallData Encoded: uint256[] memory nodeOperatorIds
function createEVMScript(address _creator, bytes memory _evmScriptCallData)
external
view
override
onlyTrustedCaller(_creator)
returns (bytes memory)
{
uint256[] memory nodeOperatorIds = _decodeEVMScriptCallData(_evmScriptCallData);

_validateInputData(nodeOperatorIds);

return
EVMScriptCreator.createEVMScript(
address(csm),
ICSModule.settleELRewardsStealingPenalty.selector,
_evmScriptCallData
);
}

/// @notice Decodes call data used by createEVMScript method
/// @param _evmScriptCallData Encoded: uint256[] memory nodeOperatorIds
/// @return Node operator IDs to settle EL stealing penalty
function decodeEVMScriptCallData(bytes memory _evmScriptCallData)
external
pure
returns (uint256[] memory)
{
return _decodeEVMScriptCallData(_evmScriptCallData);
}

// ------------------
// PRIVATE METHODS
// ------------------

function _decodeEVMScriptCallData(bytes memory _evmScriptCallData)
private
pure
returns (uint256[] memory)
{
return abi.decode(_evmScriptCallData, (uint256[]));
}

function _validateInputData(
uint256[] memory _decodedCallData
) private view {
uint256 nodeOperatorsCount = csm.getNodeOperatorsCount();
require(_decodedCallData.length > 0, ERROR_EMPTY_NODE_OPERATORS_IDS);
require(
_decodedCallData[_decodedCallData.length - 1] < nodeOperatorsCount,
ERROR_OUT_OF_RANGE_NODE_OPERATOR_ID
);
for (uint256 i = 0; i < _decodedCallData.length; ++i) {
require(
i == 0 ||
_decodedCallData[i] > _decodedCallData[i - 1],
ERROR_NOT_SORTED_NODE_OPERATORS_IDS
);
}
}
}
16 changes: 16 additions & 0 deletions contracts/interfaces/ICSModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.6;

/// @title Lido's Community Staking Module interface
interface ICSModule {
/// @notice Settles blocked bond for the given Node Operators
/// @dev Should be called by the Easy Track
/// @param nodeOperatorIds IDs of the Node Operators
function settleELRewardsStealingPenalty(
uint256[] memory nodeOperatorIds
) external;

function getNodeOperatorsCount() external view returns (uint256);
}
1 change: 1 addition & 0 deletions interfaces/CSAccounting.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions interfaces/CSEarlyAdoption.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[ { "type": "constructor", "inputs": [ { "name": "treeRoot", "type": "bytes32", "internalType": "bytes32" }, { "name": "curveId", "type": "uint256", "internalType": "uint256" }, { "name": "module", "type": "address", "internalType": "address" } ], "stateMutability": "nonpayable" }, { "type": "function", "name": "CURVE_ID", "inputs": [], "outputs": [ { "name": "", "type": "uint256", "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", "name": "MODULE", "inputs": [], "outputs": [ { "name": "", "type": "address", "internalType": "address" } ], "stateMutability": "view" }, { "type": "function", "name": "TREE_ROOT", "inputs": [], "outputs": [ { "name": "", "type": "bytes32", "internalType": "bytes32" } ], "stateMutability": "view" }, { "type": "function", "name": "consume", "inputs": [ { "name": "sender", "type": "address", "internalType": "address" }, { "name": "proof", "type": "bytes32[]", "internalType": "bytes32[]" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "consumed", "inputs": [ { "name": "sender", "type": "address", "internalType": "address" } ], "outputs": [ { "name": "", "type": "bool", "internalType": "bool" } ], "stateMutability": "view" }, { "type": "function", "name": "isEligible", "inputs": [ { "name": "sender", "type": "address", "internalType": "address" }, { "name": "proof", "type": "bytes32[]", "internalType": "bytes32[]" } ], "outputs": [ { "name": "", "type": "bool", "internalType": "bool" } ], "stateMutability": "view" }, { "type": "event", "name": "Consumed", "inputs": [ { "name": "sender", "type": "address", "indexed": true, "internalType": "address" } ], "anonymous": false }, { "type": "error", "name": "AlreadyConsumed", "inputs": [] }, { "type": "error", "name": "InvalidProof", "inputs": [] }, { "type": "error", "name": "InvalidValue", "inputs": [] }, { "type": "error", "name": "OnlyModule", "inputs": [] } ]
1 change: 1 addition & 0 deletions interfaces/CSFeeDistributor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[ { "type": "constructor", "inputs": [ { "name": "stETH", "type": "address", "internalType": "address" }, { "name": "accounting", "type": "address", "internalType": "address" } ], "stateMutability": "nonpayable" }, { "type": "function", "name": "ACCOUNTING", "inputs": [], "outputs": [ { "name": "", "type": "address", "internalType": "address" } ], "stateMutability": "view" }, { "type": "function", "name": "DEFAULT_ADMIN_ROLE", "inputs": [], "outputs": [ { "name": "", "type": "bytes32", "internalType": "bytes32" } ], "stateMutability": "view" }, { "type": "function", "name": "ORACLE_ROLE", "inputs": [], "outputs": [ { "name": "", "type": "bytes32", "internalType": "bytes32" } ], "stateMutability": "view" }, { "type": "function", "name": "RECOVERER_ROLE", "inputs": [], "outputs": [ { "name": "", "type": "bytes32", "internalType": "bytes32" } ], "stateMutability": "view" }, { "type": "function", "name": "STETH", "inputs": [], "outputs": [ { "name": "", "type": "address", "internalType": "contract IStETH" } ], "stateMutability": "view" }, { "type": "function", "name": "claimableShares", "inputs": [], "outputs": [ { "name": "", "type": "uint256", "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", "name": "distributeFees", "inputs": [ { "name": "nodeOperatorId", "type": "uint256", "internalType": "uint256" }, { "name": "shares", "type": "uint256", "internalType": "uint256" }, { "name": "proof", "type": "bytes32[]", "internalType": "bytes32[]" } ], "outputs": [ { "name": "", "type": "uint256", "internalType": "uint256" } ], "stateMutability": "nonpayable" }, { "type": "function", "name": "distributedShares", "inputs": [ { "name": "", "type": "uint256", "internalType": "uint256" } ], "outputs": [ { "name": "", "type": "uint256", "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", "name": "getFeesToDistribute", "inputs": [ { "name": "nodeOperatorId", "type": "uint256", "internalType": "uint256" }, { "name": "shares", "type": "uint256", "internalType": "uint256" }, { "name": "proof", "type": "bytes32[]", "internalType": "bytes32[]" } ], "outputs": [ { "name": "", "type": "uint256", "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", "name": "getRoleAdmin", "inputs": [ { "name": "role", "type": "bytes32", "internalType": "bytes32" } ], "outputs": [ { "name": "", "type": "bytes32", "internalType": "bytes32" } ], "stateMutability": "view" }, { "type": "function", "name": "getRoleMember", "inputs": [ { "name": "role", "type": "bytes32", "internalType": "bytes32" }, { "name": "index", "type": "uint256", "internalType": "uint256" } ], "outputs": [ { "name": "", "type": "address", "internalType": "address" } ], "stateMutability": "view" }, { "type": "function", "name": "getRoleMemberCount", "inputs": [ { "name": "role", "type": "bytes32", "internalType": "bytes32" } ], "outputs": [ { "name": "", "type": "uint256", "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", "name": "grantRole", "inputs": [ { "name": "role", "type": "bytes32", "internalType": "bytes32" }, { "name": "account", "type": "address", "internalType": "address" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "hasRole", "inputs": [ { "name": "role", "type": "bytes32", "internalType": "bytes32" }, { "name": "account", "type": "address", "internalType": "address" } ], "outputs": [ { "name": "", "type": "bool", "internalType": "bool" } ], "stateMutability": "view" }, { "type": "function", "name": "hashLeaf", "inputs": [ { "name": "nodeOperatorId", "type": "uint256", "internalType": "uint256" }, { "name": "shares", "type": "uint256", "internalType": "uint256" } ], "outputs": [ { "name": "", "type": "bytes32", "internalType": "bytes32" } ], "stateMutability": "pure" }, { "type": "function", "name": "initialize", "inputs": [ { "name": "admin", "type": "address", "internalType": "address" }, { "name": "oracle", "type": "address", "internalType": "address" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "pendingToDistribute", "inputs": [], "outputs": [ { "name": "", "type": "uint256", "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", "name": "processOracleReport", "inputs": [ { "name": "_treeRoot", "type": "bytes32", "internalType": "bytes32" }, { "name": "_treeCid", "type": "string", "internalType": "string" }, { "name": "distributed", "type": "uint256", "internalType": "uint256" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "recoverERC1155", "inputs": [ { "name": "token", "type": "address", "internalType": "address" }, { "name": "tokenId", "type": "uint256", "internalType": "uint256" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "recoverERC20", "inputs": [ { "name": "token", "type": "address", "internalType": "address" }, { "name": "amount", "type": "uint256", "internalType": "uint256" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "recoverERC721", "inputs": [ { "name": "token", "type": "address", "internalType": "address" }, { "name": "tokenId", "type": "uint256", "internalType": "uint256" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "recoverEther", "inputs": [], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "renounceRole", "inputs": [ { "name": "role", "type": "bytes32", "internalType": "bytes32" }, { "name": "callerConfirmation", "type": "address", "internalType": "address" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "revokeRole", "inputs": [ { "name": "role", "type": "bytes32", "internalType": "bytes32" }, { "name": "account", "type": "address", "internalType": "address" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "supportsInterface", "inputs": [ { "name": "interfaceId", "type": "bytes4", "internalType": "bytes4" } ], "outputs": [ { "name": "", "type": "bool", "internalType": "bool" } ], "stateMutability": "view" }, { "type": "function", "name": "treeCid", "inputs": [], "outputs": [ { "name": "", "type": "string", "internalType": "string" } ], "stateMutability": "view" }, { "type": "function", "name": "treeRoot", "inputs": [], "outputs": [ { "name": "", "type": "bytes32", "internalType": "bytes32" } ], "stateMutability": "view" }, { "type": "event", "name": "FeeDistributed", "inputs": [ { "name": "nodeOperatorId", "type": "uint256", "indexed": true, "internalType": "uint256" }, { "name": "shares", "type": "uint256", "indexed": false, "internalType": "uint256" } ], "anonymous": false }, { "type": "event", "name": "Initialized", "inputs": [ { "name": "version", "type": "uint64", "indexed": false, "internalType": "uint64" } ], "anonymous": false }, { "type": "event", "name": "RoleAdminChanged", "inputs": [ { "name": "role", "type": "bytes32", "indexed": true, "internalType": "bytes32" }, { "name": "previousAdminRole", "type": "bytes32", "indexed": true, "internalType": "bytes32" }, { "name": "newAdminRole", "type": "bytes32", "indexed": true, "internalType": "bytes32" } ], "anonymous": false }, { "type": "event", "name": "RoleGranted", "inputs": [ { "name": "role", "type": "bytes32", "indexed": true, "internalType": "bytes32" }, { "name": "account", "type": "address", "indexed": true, "internalType": "address" }, { "name": "sender", "type": "address", "indexed": true, "internalType": "address" } ], "anonymous": false }, { "type": "event", "name": "RoleRevoked", "inputs": [ { "name": "role", "type": "bytes32", "indexed": true, "internalType": "bytes32" }, { "name": "account", "type": "address", "indexed": true, "internalType": "address" }, { "name": "sender", "type": "address", "indexed": true, "internalType": "address" } ], "anonymous": false }, { "type": "error", "name": "AccessControlBadConfirmation", "inputs": [] }, { "type": "error", "name": "AccessControlUnauthorizedAccount", "inputs": [ { "name": "account", "type": "address", "internalType": "address" }, { "name": "neededRole", "type": "bytes32", "internalType": "bytes32" } ] }, { "type": "error", "name": "InvalidInitialization", "inputs": [] }, { "type": "error", "name": "InvalidProof", "inputs": [] }, { "type": "error", "name": "InvalidShares", "inputs": [] }, { "type": "error", "name": "InvalidTreeCID", "inputs": [] }, { "type": "error", "name": "InvalidTreeRoot", "inputs": [] }, { "type": "error", "name": "NotAccounting", "inputs": [] }, { "type": "error", "name": "NotAllowedToRecover", "inputs": [] }, { "type": "error", "name": "NotInitializing", "inputs": [] }, { "type": "error", "name": "ZeroAddress", "inputs": [ { "name": "field", "type": "string", "internalType": "string" } ] }, { "type": "event", "name": "ERC1155Recovered", "inputs": [ { "name": "token", "type": "address", "indexed": true, "internalType": "address" }, { "name": "tokenId", "type": "uint256", "indexed": false, "internalType": "uint256" }, { "name": "recipient", "type": "address", "indexed": true, "internalType": "address" }, { "name": "amount", "type": "uint256", "indexed": false, "internalType": "uint256" } ], "anonymous": false }, { "type": "event", "name": "ERC20Recovered", "inputs": [ { "name": "token", "type": "address", "indexed": true, "internalType": "address" }, { "name": "recipient", "type": "address", "indexed": true, "internalType": "address" }, { "name": "amount", "type": "uint256", "indexed": false, "internalType": "uint256" } ], "anonymous": false }, { "type": "event", "name": "ERC721Recovered", "inputs": [ { "name": "token", "type": "address", "indexed": true, "internalType": "address" }, { "name": "tokenId", "type": "uint256", "indexed": false, "internalType": "uint256" }, { "name": "recipient", "type": "address", "indexed": true, "internalType": "address" } ], "anonymous": false }, { "type": "event", "name": "EtherRecovered", "inputs": [ { "name": "recipient", "type": "address", "indexed": true, "internalType": "address" }, { "name": "amount", "type": "uint256", "indexed": false, "internalType": "uint256" } ], "anonymous": false }, { "type": "event", "name": "StETHSharesRecovered", "inputs": [ { "name": "recipient", "type": "address", "indexed": true, "internalType": "address" }, { "name": "shares", "type": "uint256", "indexed": false, "internalType": "uint256" } ], "anonymous": false }, { "type": "error", "name": "AddressEmptyCode", "inputs": [ { "name": "target", "type": "address", "internalType": "address" } ] }, { "type": "error", "name": "AddressInsufficientBalance", "inputs": [ { "name": "account", "type": "address", "internalType": "address" } ] }, { "type": "error", "name": "FailedInnerCall", "inputs": [] }, { "type": "error", "name": "FailedToSendEther", "inputs": [] }, { "type": "error", "name": "SafeERC20FailedOperation", "inputs": [ { "name": "token", "type": "address", "internalType": "address" } ] } ]
1 change: 1 addition & 0 deletions interfaces/CSFeeOracle.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions interfaces/CSModule.json

Large diffs are not rendered by default.

Loading

0 comments on commit 21ef7fa

Please sign in to comment.