Skip to content
108 changes: 108 additions & 0 deletions contracts/EVMScriptFactories/CSMSetVettedGateTree.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2025 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/IVettedGate.sol";

/// @author vgorkavenko
/// @notice Creates EVMScript to set tree for CSM's VettedGate
contract CSMSetVettedGateTree is TrustedCaller, IEVMScriptFactory {

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

string private constant ERROR_EMPTY_TREE_ROOT =
"EMPTY_TREE_ROOT";
string private constant ERROR_EMPTY_TREE_CID =
"EMPTY_TREE_CID";
string private constant ERROR_SAME_TREE_CID =
"SAME_TREE_CID";
string private constant ERROR_SAME_TREE_ROOT =
"SAME_TREE_ROOT";

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

/// @notice Alias for factory (e.g. "IdentifiedCommunityStakerSetTreeParams")
string public name;

/// @notice Address of VettedGate
IVettedGate public immutable vettedGate;

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

constructor(address _trustedCaller, string memory _name, address _vettedGate)
TrustedCaller(_trustedCaller)
{
name = _name;
vettedGate = IVettedGate(_vettedGate);
}

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

/// @notice Creates EVMScript to set treeRoot and treeCid for CSM's VettedGate
/// @param _creator Address who creates EVMScript
/// @param _evmScriptCallData Encoded: bytes32 treeRoot and string treeCid
function createEVMScript(address _creator, bytes calldata _evmScriptCallData)
external
view
override
onlyTrustedCaller(_creator)
returns (bytes memory)
{
(bytes32 treeRoot, string memory treeCid) = _decodeEVMScriptCallData(_evmScriptCallData);

_validateInputData(treeRoot, treeCid);

return
EVMScriptCreator.createEVMScript(
address(vettedGate),
IVettedGate.setTreeParams.selector,
_evmScriptCallData
);
}

/// @notice Decodes call data used by createEVMScript method
/// @param _evmScriptCallData Encoded: bytes32 treeRoot and string treeCid
/// @return treeRoot The root of the tree
/// @return treeCid The CID of the tree
function decodeEVMScriptCallData(bytes calldata _evmScriptCallData)
external
pure
returns (bytes32, string memory)
{
return _decodeEVMScriptCallData(_evmScriptCallData);
}

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

function _decodeEVMScriptCallData(bytes calldata _evmScriptCallData)
private
pure
returns (bytes32, string memory)
{
return abi.decode(_evmScriptCallData, (bytes32, string));
}

function _validateInputData(
bytes32 treeRoot,
string memory treeCid
) private view {
require(treeRoot != bytes32(0), ERROR_EMPTY_TREE_ROOT);
require(bytes(treeCid).length > 0, ERROR_EMPTY_TREE_CID);
require(treeRoot != vettedGate.treeRoot(), ERROR_SAME_TREE_ROOT);
require(keccak256(bytes(treeCid)) != keccak256(bytes(vettedGate.treeCid())), ERROR_SAME_TREE_CID);
}
}
19 changes: 19 additions & 0 deletions contracts/interfaces/IVettedGate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2025 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.6;

/// @title Lido's Community Staking Module Vetted Gate interface
interface IVettedGate {

function treeRoot() external view returns (bytes32);
function treeCid() external view returns (string memory);

/// @notice Set the root of the eligible members Merkle Tree
/// @param _treeRoot New root of the Merkle Tree
/// @param _treeCid New CID of the Merkle Tree
function setTreeParams(
bytes32 _treeRoot,
string calldata _treeCid
) external;
}
35 changes: 35 additions & 0 deletions contracts/test/VettedGateStub.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.4;

import "OpenZeppelin/[email protected]/contracts/access/AccessControl.sol";

/// @author vgorkavenko
/// @notice Helper contract with stub implementation of VettedGate for testing
contract VettedGateStub is AccessControl {
bytes32 public constant SET_TREE_ROLE = keccak256("SET_TREE_ROLE");

bytes32 public treeRoot;
string public treeCid;

event TreeParamsSet(bytes32 indexed treeRoot, string treeCid);

constructor() {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(SET_TREE_ROLE, msg.sender);
}

/// @notice Set the root of the eligible members Merkle Tree
/// @param _treeRoot New root of the Merkle Tree
/// @param _treeCid New CID of the Merkle Tree
function setTreeParams(
bytes32 _treeRoot,
string calldata _treeCid
) external onlyRole(SET_TREE_ROLE) {
treeRoot = _treeRoot;
treeCid = _treeCid;

emit TreeParamsSet(_treeRoot, _treeCid);
}
}
13 changes: 12 additions & 1 deletion deployed-csm-holesky.json
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
{"CSMSettleElStealingPenalty": {"contract": "CSMSettleElStealingPenalty", "address": "0x07696EA8A5b53C3E35d9cce10cc62c6c79C4691D", "constructorArgs": ["0xc4DAB3a3ef68C6DFd8614a870D64D475bA44F164", "0x4562c3e63c2e586cD1651B958C22F88135aCAd4f"]}}
{
"CSMSettleElStealingPenalty": {
"contract": "CSMSettleElStealingPenalty",
"address": "0x07696EA8A5b53C3E35d9cce10cc62c6c79C4691D",
"constructorArgs": ["0xc4DAB3a3ef68C6DFd8614a870D64D475bA44F164", "0x4562c3e63c2e586cD1651B958C22F88135aCAd4f"]
},
"CSMSetVettedGateTree": {
"contract": "CSMSetVettedGateTree",
"address": "0x26CDa8f9D84956efC743c8432658Ae9a5B7939da",
"constructorArgs": ["0xc4DAB3a3ef68C6DFd8614a870D64D475bA44F164", "IdentifiedCommunityStakerSetTreeParams", "0x92A5aB5e4f98e67Fb7295fe439A652d0E51033bf"]
}
}
9 changes: 9 additions & 0 deletions deployed-csm-hoodi.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,14 @@
"0x4af43ee34a6fcd1feca1e1f832124c763561da53",
"0x79CEf36D84743222f37765204Bec41E92a93E59d"
]
},
"CSMSetVettedGateTree": {
"contract": "CSMSetVettedGateTree",
"address": "0xa890fc73e1b771Ee6073e2402E631c312FF92Cd9",
"constructorArgs": [
"0x4AF43Ee34a6fcD1fEcA1e1F832124C763561dA53",
"IdentifiedCommunityStakerSetTreeParams",
"0x10a254E724fe2b7f305F76f3F116a3969c53845f"
]
}
}
25 changes: 17 additions & 8 deletions deployed-csm-mainnet.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
{
"CSMSettleElStealingPenalty": {
"contract": "CSMSettleElStealingPenalty",
"address": "0xF6B6E7997338C48Ea3a8BCfa4BB64a315fDa76f4",
"constructorArgs": [
"0xC52fC3081123073078698F1EAc2f1Dc7Bd71880f",
"0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F"
]
}
"CSMSetVettedGateTree": {
"contract": "CSMSetVettedGateTree",
"address": "0xBc5642bDD6F2a54b01A75605aAe9143525D97308",
"constructorArgs": [
"0xC52fC3081123073078698F1EAc2f1Dc7Bd71880f",
"ics",
"0xB314D4A76C457c93150d308787939063F4Cc67E0"
]
},
"CSMSettleElStealingPenalty": {
"contract": "CSMSettleElStealingPenalty",
"address": "0xF6B6E7997338C48Ea3a8BCfa4BB64a315fDa76f4",
"constructorArgs": [
"0xC52fC3081123073078698F1EAc2f1Dc7Bd71880f",
"0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F"
]
}
}
55 changes: 55 additions & 0 deletions scripts/acceptance_test_csm_set_vetted_gate_tree_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from dataclasses import dataclass

from brownie import chain, CSMSetVettedGateTree

from utils import log


@dataclass
class DeployConfig:
trusted_caller: str
factory_name: str
vetted_gate_address: str


deploy_config = DeployConfig(
trusted_caller="",
factory_name="",
vetted_gate_address=""
)


deployment_tx_hash = ""


def main():

tx = chain.get_transaction(deployment_tx_hash)

log.br()

log.nb("tx of creation", deployment_tx_hash)

log.br()

log.nb("trusted_caller", deploy_config.trusted_caller)
log.nb("factory_name", deploy_config.factory_name)
log.nb("vetted_gate_address", deploy_config.vetted_gate_address)

log.br()

set_vetted_gate_tree_factory = CSMSetVettedGateTree.at(tx.contract_address)
log.nb('CSMSetVettedGateTree address (from tx)', set_vetted_gate_tree_factory)

log.br()

assert set_vetted_gate_tree_factory.vettedGate() == deploy_config.vetted_gate_address
log.nb('VettedGate address is correct')

assert set_vetted_gate_tree_factory.trustedCaller() == deploy_config.trusted_caller
log.nb('Trusted caller is correct')

assert set_vetted_gate_tree_factory.name() == deploy_config.factory_name
log.nb('Factory name is correct')

log.br()
Loading
Loading