diff --git a/contracts/OperatorRewardsCollector.sol b/contracts/OperatorRewardsCollector.sol index 7a318eb2..cb08a2b6 100644 --- a/contracts/OperatorRewardsCollector.sol +++ b/contracts/OperatorRewardsCollector.sol @@ -16,6 +16,7 @@ import { ISDUtilityPool, UserData, OperatorLiquidation } from "./interfaces/ISDU import { ISDCollateral } from "./interfaces/SDCollateral/ISDCollateral.sol"; import { IWETH } from "./interfaces/IWETH.sol"; import { IStaderOracle } from "../contracts/interfaces/IStaderOracle.sol"; +import { IPoolUtils } from "../contracts/interfaces/IPoolUtils.sol"; contract OperatorRewardsCollector is IOperatorRewardsCollector, AccessControlUpgradeable { IStaderConfig public staderConfig; @@ -52,10 +53,15 @@ contract OperatorRewardsCollector is IOperatorRewardsCollector, AccessControlUpg * @dev This function first checks for any unpaid liquidations for the operator and repays them if necessary. Then, it transfers any remaining balance to the operator's reward address. */ function claim() external { - claimLiquidation(msg.sender); - uint256 amount = balances[msg.sender] > withdrawableInEth(msg.sender) - ? withdrawableInEth(msg.sender) - : balances[msg.sender]; + uint256 amount; + if (_isPermissionlessCaller(msg.sender)) { + claimLiquidation(msg.sender); + amount = balances[msg.sender] > withdrawableInEth(msg.sender) + ? withdrawableInEth(msg.sender) + : balances[msg.sender]; + } else { + amount = balances[msg.sender]; + } _claim(msg.sender, amount); } @@ -66,7 +72,12 @@ contract OperatorRewardsCollector is IOperatorRewardsCollector, AccessControlUpg * @param _amount amount of ETH to claim */ function claimWithAmount(uint256 _amount) external { - claimLiquidation(msg.sender); + if (_isPermissionlessCaller(msg.sender)) { + claimLiquidation(msg.sender); + uint256 maxWithdrawableInEth = withdrawableInEth(msg.sender); + if (_amount > maxWithdrawableInEth) revert InsufficientBalance(); + } + if (_amount > balances[msg.sender]) revert InsufficientBalance(); _claim(msg.sender, _amount); } @@ -113,6 +124,14 @@ contract OperatorRewardsCollector is IOperatorRewardsCollector, AccessControlUpg return balances[operator]; } + function _isPermissionlessCaller(address caller) internal returns (bool) { + IPoolUtils poolUtils = IPoolUtils(staderConfig.getPoolUtils()); + uint8 poolId = poolUtils.getOperatorPoolId(caller); + address permissionlessNodeRegistry = staderConfig.getPermissionlessNodeRegistry(); + + return INodeRegistry(permissionlessNodeRegistry).POOL_ID() == poolId; + } + /** * @notice Completes any pending liquidation for an operator if exists. * @dev Internal function to handle liquidation completion. @@ -181,9 +200,6 @@ contract OperatorRewardsCollector is IOperatorRewardsCollector, AccessControlUpg * @param amount The amount to be claimed. */ function _claim(address operator, uint256 amount) internal { - uint256 maxWithdrawableInEth = withdrawableInEth(operator); - if (amount > maxWithdrawableInEth || amount > balances[operator]) revert InsufficientBalance(); - balances[operator] -= amount; // If there's an amount to send, transfer it to the operator's rewards address diff --git a/contracts/PermissionedNodeRegistry.sol b/contracts/PermissionedNodeRegistry.sol index 5b2f9b56..a24ae3d6 100644 --- a/contracts/PermissionedNodeRegistry.sol +++ b/contracts/PermissionedNodeRegistry.sol @@ -432,17 +432,6 @@ contract PermissionedNodeRegistry is emit UpdatedOperatorName(msg.sender, _operatorName); } - /** - * @notice update the maximum non terminal key limit per operator - * @dev only `MANAGER` role can call - * @param _maxNonTerminalKeyPerOperator updated maximum non terminal key per operator limit - */ - function updateMaxNonTerminalKeyPerOperator(uint64 _maxNonTerminalKeyPerOperator) external override { - UtilLib.onlyManagerRole(msg.sender, staderConfig); - maxNonTerminalKeyPerOperator = _maxNonTerminalKeyPerOperator; - emit UpdatedMaxNonTerminalKeyPerOperator(maxNonTerminalKeyPerOperator); - } - /** * @notice update number of validator keys that can be added in a single tx by the operator * @dev only `OPERATOR` role can call @@ -720,22 +709,6 @@ contract PermissionedNodeRegistry is revert InvalidKeyCount(); } totalKeys = getOperatorTotalKeys(_operatorId); - uint256 totalNonTerminalKeys = getOperatorTotalNonTerminalKeys(msg.sender, 0, totalKeys); - if ((totalNonTerminalKeys + keyCount) > maxNonTerminalKeyPerOperator) { - revert MaxKeyLimitReached(); - } - - //checks if operator has enough SD collateral for adding `keyCount` keys - //SD threshold for permissioned NOs is 0 for phase1 - if ( - !ISDCollateral(staderConfig.getSDCollateral()).hasEnoughSDCollateral( - msg.sender, - POOL_ID, - totalNonTerminalKeys + keyCount - ) - ) { - revert NotEnoughSDCollateral(); - } } // operator in active state diff --git a/contracts/PermissionlessNodeRegistry.sol b/contracts/PermissionlessNodeRegistry.sol index e1605eea..2f7b0ba5 100644 --- a/contracts/PermissionlessNodeRegistry.sol +++ b/contracts/PermissionlessNodeRegistry.sol @@ -59,6 +59,7 @@ contract PermissionlessNodeRegistry is //mapping of operator address with nodeELReward vault address mapping(uint256 => address) public override nodeELRewardVaultByOperatorId; mapping(uint256 => address) public proposedRewardAddressByOperatorId; + uint256 public maxKeysPerOperator; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -150,6 +151,10 @@ contract PermissionlessNodeRegistry is ) public payable override nonReentrant whenNotPaused { uint256 operatorId = onlyActiveOperator(msg.sender); uint256 keyCount = _pubkey.length; + uint256 totalKeys = getOperatorTotalKeys(operatorId); + if (totalKeys + keyCount > maxKeysPerOperator) { + revert MaxKeyLimitExceed(); + } if (keyCount != _preDepositSignature.length || keyCount != _depositSignature.length) { revert MisMatchingInputKeysSize(); } @@ -444,6 +449,16 @@ contract PermissionlessNodeRegistry is emit TransferredCollateralToPool(_amount); } + /** + * @notice update the max validator per operator value + * @dev only `MANAGER` role can update + */ + function updateMaxKeysPerOperator(uint256 _maxKeysPerOperator) external { + UtilLib.onlyManagerRole(msg.sender, staderConfig); + maxKeysPerOperator = _maxKeysPerOperator; + emit UpdateMaxKeysPerOperator(_maxKeysPerOperator); + } + /** * @param _nodeOperator @notice operator total non terminal keys within a specified validator list * @param _startIndex start index in validator queue to start with diff --git a/contracts/interfaces/IPermissionedNodeRegistry.sol b/contracts/interfaces/IPermissionedNodeRegistry.sol index f0f76ee9..d7ae04d0 100644 --- a/contracts/interfaces/IPermissionedNodeRegistry.sol +++ b/contracts/interfaces/IPermissionedNodeRegistry.sol @@ -62,8 +62,6 @@ interface IPermissionedNodeRegistry { function markValidatorStatusAsPreDeposit(bytes calldata _pubkey) external; - function updateMaxNonTerminalKeyPerOperator(uint64 _maxNonTerminalKeyPerOperator) external; - function updateInputKeyCountLimit(uint16 _inputKeyCountLimit) external; function proposeRewardAddress(address _operatorAddress, address _newRewardAddress) external; diff --git a/contracts/interfaces/IPermissionlessNodeRegistry.sol b/contracts/interfaces/IPermissionlessNodeRegistry.sol index 89b9e3ea..722acb14 100644 --- a/contracts/interfaces/IPermissionlessNodeRegistry.sol +++ b/contracts/interfaces/IPermissionlessNodeRegistry.sol @@ -8,6 +8,7 @@ interface IPermissionlessNodeRegistry { error InSufficientBalance(); error CooldownNotComplete(); error NoChangeInState(); + error MaxKeyLimitExceed(); // Events event OnboardedOperator( @@ -21,6 +22,7 @@ interface IPermissionlessNodeRegistry { event UpdatedSocializingPoolState(uint256 operatorId, bool optedForSocializingPool, uint256 block); event TransferredCollateralToPool(uint256 amount); event ValidatorAddedViaReferral(uint256 amount, string referralId); + event UpdateMaxKeysPerOperator(uint256 maxKeysPerOperator); //Getters diff --git a/test/fork/reward-claim-optimisation.ts b/test/fork/reward-claim-optimisation.ts new file mode 100644 index 00000000..d1cfb73e --- /dev/null +++ b/test/fork/reward-claim-optimisation.ts @@ -0,0 +1,56 @@ +import { ethers, network } from "hardhat"; +import "dotenv/config"; +import { impersonateAccount, setBalance } from "@nomicfoundation/hardhat-network-helpers"; + +const PROXY_OWNER = "0x1112D5C55670Cb5144BF36114C20a122908068B9" +const PROXY_ADMIN = "0x67B12264Ca3e0037Fc7E22F2457b42643a04C86e"; +const OPERATOR_REWARDS_COLLECTOR_ADDRESS = "0x84ffDC9De310144D889540A49052F6d1AdB2C335"; +const OPERATOR = "0xb851788Fa34B0d9215F54531061D4e2e06A74AEE" + +async function setForkBlock(blockNumber: number) { + await network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + jsonRpcUrl: process.env.PROVIDER_URL_MAINNET, + blockNumber: blockNumber, + }, + }, + ], + }); +} + +async function configureNewContract (contractName: String, contractAddress: String) { + await setBalance(PROXY_OWNER, ethers.parseEther("1")) + await impersonateAccount(PROXY_OWNER) + + const impersonatedProxyOwner = await ethers.getSigner(PROXY_OWNER); + + const contractFactory = await ethers.getContractFactory(contractName); + const contractImpl = await contractFactory.deploy(); + console.log(`${contractName} Implementation deployed to:`, await contractImpl.getAddress()); + + const proxyAdminContract = await ethers.getContractAt("ProxyAdmin", PROXY_ADMIN); + await proxyAdminContract.connect(impersonatedProxyOwner).upgrade(contractAddress, await contractImpl.getAddress()); + + const contract = await ethers.getContractAt(contractName, contractAddress) + + return contract; +} + +describe("Gas Coverage", function () { + it("should consume less gas after upgrade", async () => { + await setForkBlock(21270988); + await setBalance(OPERATOR, ethers.parseEther("100")) + await impersonateAccount(OPERATOR); + const impersonatedOperator = await ethers.getSigner(OPERATOR); + + const newOperatorRewardsCollector = await configureNewContract("OperatorRewardsCollector", OPERATOR_REWARDS_COLLECTOR_ADDRESS); + + // Firing a txn with updated contracts + const claimTxn = await newOperatorRewardsCollector.connect(impersonatedOperator).claim(); + const claimTxnReceipt = await claimTxn.wait(); + console.log("Claim Txn Gas Estimate:", claimTxnReceipt.gasUsed); + }); +}); diff --git a/test/foundry_tests/NodeELRewardVault.t.sol b/test/foundry_tests/NodeELRewardVault.t.sol index 5eb65f30..5dd64da1 100644 --- a/test/foundry_tests/NodeELRewardVault.t.sol +++ b/test/foundry_tests/NodeELRewardVault.t.sol @@ -19,6 +19,7 @@ import { PoolUtilsMock } from "../mocks/PoolUtilsMock.sol"; import { StakePoolManagerMock } from "../mocks/StakePoolManagerMock.sol"; import { StaderOracleMock } from "../mocks/StaderOracleMock.sol"; import { SDUtilityPoolMock } from "../mocks/SDUtilityPoolMock.sol"; +import { PermissionlessNodeRegistryMock } from "../mocks/PermissionlessNodeRegistryMock.sol"; contract NodeELRewardVaultTest is Test { address private constant OPERATOR_ADDRESSS = address(500); @@ -59,6 +60,7 @@ contract NodeELRewardVaultTest is Test { address operator = OPERATOR_ADDRESSS; mockStaderOracle(staderOracleMock); mockSdUtilityPool(sdUtilityPoolMock, operator); + mockPermissionlessNodeRegistry(vm.addr(105)); OperatorRewardsCollector operatorRCImpl = new OperatorRewardsCollector(); TransparentUpgradeableProxy operatorRCProxy = new TransparentUpgradeableProxy( @@ -95,6 +97,7 @@ contract NodeELRewardVaultTest is Test { staderConfig.updateSDCollateral(address(sdCollateral)); staderConfig.updateSDUtilityPool(sdUtilityPoolMock); staderConfig.updateStaderOracle(staderOracleMock); + staderConfig.updatePermissionlessNodeRegistry(vm.addr(105)); staderConfig.grantRole(staderConfig.MANAGER(), staderManager); vaultFactory.grantRole(vaultFactory.NODE_REGISTRY_CONTRACT(), address(poolUtils.nodeRegistry())); vm.stopPrank(); @@ -265,4 +268,11 @@ contract NodeELRewardVaultTest is Test { bytes memory mockCode = address(implementation).code; vm.etch(staderOracleMock, mockCode); } + + function mockPermissionlessNodeRegistry(address _permissionlessNodeRegistry) private { + emit log_named_address("permissionlessNodeRegistry", _permissionlessNodeRegistry); + PermissionlessNodeRegistryMock nodeRegistryMock = new PermissionlessNodeRegistryMock(); + bytes memory mockCode = address(nodeRegistryMock).code; + vm.etch(_permissionlessNodeRegistry, mockCode); + } } diff --git a/test/foundry_tests/OperatorRewardsCollector.t.sol b/test/foundry_tests/OperatorRewardsCollector.t.sol index 47c20a35..d14b5555 100644 --- a/test/foundry_tests/OperatorRewardsCollector.t.sol +++ b/test/foundry_tests/OperatorRewardsCollector.t.sol @@ -30,6 +30,8 @@ contract OperatorRewardsCollectorTest is Test { event SDWithdrawn(address indexed operator, uint256 sdAmount); event SDRepaid(address operator, uint256 repayAmount); + error InsufficientBalance(); + address staderAdmin; address staderManager; address staderTreasury; @@ -173,6 +175,112 @@ contract OperatorRewardsCollectorTest is Test { vm.stopPrank(); } + function test_Claim_PermissionedPoolOperator(uint256 amount) public { + vm.assume(amount < 100000 ether); + + operatorRewardsCollector.depositFor{ value: amount }(staderManager); + assertEq(operatorRewardsCollector.balances(staderManager), amount); + vm.mockCall( + address(staderOracle), + abi.encodeWithSelector(IStaderOracle.getSDPriceInETH.selector), + abi.encode(1e14) + ); + + vm.mockCall( + address(poolUtils), + abi.encodeWithSelector(IPoolUtils.getOperatorPoolId.selector, staderManager), + abi.encode(uint8(2)) // Assuming `POOL_ID()` for PermissionedNodeRegistry is `2` + ); + + vm.mockCall( + address(permissionlessNodeRegistryMock), + abi.encodeWithSelector(INodeRegistry.POOL_ID.selector), + abi.encode(uint8(1)) + ); + + vm.startPrank(staderManager); + operatorRewardsCollector.claim(); + + assertEq(operatorRewardsCollector.balances(staderManager), 0); + vm.stopPrank(); + } + + function test_ClaimWithAmount_PermissionlessNodeRegistry() public { + uint256 amount = 10 ether; // Deposit amount + + // Deposit funds into the contract + operatorRewardsCollector.depositFor{ value: amount }(staderManager); + assertEq(operatorRewardsCollector.balances(staderManager), amount); + + uint256 withdrawableAmount = operatorRewardsCollector.withdrawableInEth(staderManager); // Withdrawable amount + + // Mock the operator's pool ID to match the PermissionlessPool + vm.mockCall( + address(poolUtils), + abi.encodeWithSelector(IPoolUtils.getOperatorPoolId.selector, staderManager), + abi.encode(uint8(1)) // PermissionlessPool + ); + + // Mock Permissionless Node Registry POOL_ID + vm.mockCall( + address(permissionlessNodeRegistryMock), + abi.encodeWithSelector(INodeRegistry.POOL_ID.selector), + abi.encode(uint8(1)) // PermissionlessNodeRegistry + ); + + vm.startPrank(staderManager); + + // Case 1: Claiming more than `withdrawableInEth` + vm.expectRevert(InsufficientBalance.selector); + operatorRewardsCollector.claimWithAmount(withdrawableAmount + 1 ether); + + // Case 2: Claiming more than `balances[msg.sender]` + vm.expectRevert(InsufficientBalance.selector); + operatorRewardsCollector.claimWithAmount(amount + 1 ether); + + // Case 3: Valid claim within limits + uint256 validClaim = 3 ether; + operatorRewardsCollector.claimWithAmount(validClaim); + assertEq(operatorRewardsCollector.balances(staderManager), amount - validClaim); + + vm.stopPrank(); + } + + function test_ClaimWithAmount_PermissionedNodeRegistry(uint256 amount, uint256 claimAmount) public { + // Assume reasonable values for deposits and claims + vm.assume(amount > 0 && amount < 100000 ether); + + operatorRewardsCollector.depositFor{ value: amount }(staderManager); + assertEq(operatorRewardsCollector.balances(staderManager), amount); + + // Mock the operator's pool ID to match the PermissionedPool + vm.mockCall( + address(poolUtils), + abi.encodeWithSelector(IPoolUtils.getOperatorPoolId.selector, staderManager), + abi.encode(uint8(2)) + ); + + vm.mockCall( + address(permissionlessNodeRegistryMock), + abi.encodeWithSelector(INodeRegistry.POOL_ID.selector), + abi.encode(uint8(1)) + ); + + vm.startPrank(staderManager); + + if (claimAmount > amount) { + // Case: Attempting to claim more than balance, expect revert + vm.expectRevert(InsufficientBalance.selector); + operatorRewardsCollector.claimWithAmount(claimAmount); + } else { + // Case: Claiming valid amount + operatorRewardsCollector.claimWithAmount(claimAmount); + assertEq(operatorRewardsCollector.balances(staderManager), amount - claimAmount); + } + + vm.stopPrank(); + } + function test_claimLiquidationZeroAmount(uint256 amount) public { vm.assume(amount < 100000 ether); diff --git a/test/foundry_tests/PermissionedNodeRegistry.t.sol b/test/foundry_tests/PermissionedNodeRegistry.t.sol index a0da00f0..559ab445 100644 --- a/test/foundry_tests/PermissionedNodeRegistry.t.sol +++ b/test/foundry_tests/PermissionedNodeRegistry.t.sol @@ -227,24 +227,6 @@ contract PermissionedNodeRegistryTest is Test { assertEq(nodeRegistry.isExistingPubkey(pubkeys[0]), true); } - function testAddValidatorKeysNotEnoughSDCollateral() public { - ( - bytes[] memory pubkeys, - bytes[] memory preDepositSignature, - bytes[] memory depositSignature - ) = getValidatorKeys(); - vm.startPrank(permissionedNO); - nodeRegistry.onboardNodeOperator("testOP", payable(address(this))); - vm.mockCall( - address(sdCollateral), - abi.encodeWithSelector(ISDCollateral.hasEnoughSDCollateral.selector), - abi.encode(false) - ); - vm.expectRevert(INodeRegistry.NotEnoughSDCollateral.selector); - nodeRegistry.addValidatorKeys(pubkeys, preDepositSignature, depositSignature); - vm.stopPrank(); - } - function test_addValidatorKeysWithMisMatchingInputs() public { bytes[] memory pubkeys = new bytes[](1); bytes[] memory preDepositSignature = new bytes[](1); @@ -271,40 +253,6 @@ contract PermissionedNodeRegistryTest is Test { vm.stopPrank(); } - function test_addValidatorKeysOPCrossingMaxNonTerminalKeys() public { - ( - bytes[] memory pubkeys, - bytes[] memory preDepositSignature, - bytes[] memory depositSignature - ) = getValidatorKeys(); - vm.prank(staderManager); - nodeRegistry.updateMaxNonTerminalKeyPerOperator(2); - vm.startPrank(permissionedNO); - nodeRegistry.onboardNodeOperator("testOP", payable(address(this))); - vm.expectRevert(INodeRegistry.MaxKeyLimitReached.selector); - nodeRegistry.addValidatorKeys(pubkeys, preDepositSignature, depositSignature); - vm.stopPrank(); - } - - function test_addValidatorKeysWithInsufficientSDCollateral() public { - ( - bytes[] memory pubkeys, - bytes[] memory preDepositSignature, - bytes[] memory depositSignature - ) = getValidatorKeys(); - - vm.startPrank(permissionedNO); - nodeRegistry.onboardNodeOperator("testOP", payable(address(this))); - vm.mockCall( - address(sdCollateral), - abi.encodeWithSelector(ISDCollateral.hasEnoughSDCollateral.selector), - abi.encode(false) - ); - vm.expectRevert(INodeRegistry.NotEnoughSDCollateral.selector); - nodeRegistry.addValidatorKeys(pubkeys, preDepositSignature, depositSignature); - vm.stopPrank(); - } - function test_markReadyToDepositValidator() public { ( bytes[] memory pubkeys, @@ -427,17 +375,6 @@ contract PermissionedNodeRegistryTest is Test { assertEq(nodeRegistry.inputKeyCountLimit(), _keyCountLimit); } - function test_updateMaxNonTerminalKeyPerOperator(uint64 _maxNonTerminalKeyPerOperator) public { - vm.prank(staderManager); - nodeRegistry.updateMaxNonTerminalKeyPerOperator(_maxNonTerminalKeyPerOperator); - assertEq(nodeRegistry.maxNonTerminalKeyPerOperator(), _maxNonTerminalKeyPerOperator); - } - - function testFail_updateMaxNonTerminalKeyPerOperator(uint64 _maxNonTerminalKeyPerOperator) public { - nodeRegistry.updateMaxNonTerminalKeyPerOperator(_maxNonTerminalKeyPerOperator); - assertEq(nodeRegistry.maxNonTerminalKeyPerOperator(), _maxNonTerminalKeyPerOperator); - } - function test_updateVerifiedKeysBatchSize(uint256 _verifiedKeysBatchSize) public { vm.prank(operator); nodeRegistry.updateVerifiedKeysBatchSize(_verifiedKeysBatchSize); diff --git a/test/foundry_tests/PermissionlessNodeRegistry.t.sol b/test/foundry_tests/PermissionlessNodeRegistry.t.sol index 3ba936e1..dcc4bbd8 100644 --- a/test/foundry_tests/PermissionlessNodeRegistry.t.sol +++ b/test/foundry_tests/PermissionlessNodeRegistry.t.sol @@ -97,6 +97,8 @@ contract PermissionlessNodeRegistryTest is Test { staderConfig.grantRole(staderConfig.OPERATOR(), operator); vaultFactory.grantRole(vaultFactory.NODE_REGISTRY_CONTRACT(), address(nodeRegistry)); vm.stopPrank(); + vm.prank(staderManager); + nodeRegistry.updateMaxKeysPerOperator(500); } function test_JustToIncreaseCoverage() public { @@ -254,6 +256,21 @@ contract PermissionlessNodeRegistryTest is Test { assertEq(nodeRegistry.isExistingPubkey(pubkeys[0]), true); } + function test_addValidatorKeysWithLLimitExceed() public { + ( + bytes[] memory pubkeys, + bytes[] memory preDepositSignature, + bytes[] memory depositSignature + ) = getValidatorKeys(); + vm.prank(staderManager); + nodeRegistry.updateMaxKeysPerOperator(2); // Limit is 2 and trying to add 3 keys + startHoax(address(this)); + nodeRegistry.onboardNodeOperator(true, "testOP", payable(address(this))); + vm.expectRevert(IPermissionlessNodeRegistry.MaxKeyLimitExceed.selector); + nodeRegistry.addValidatorKeys{ value: 12 ether }(pubkeys, preDepositSignature, depositSignature); + vm.stopPrank(); + } + function testAddValidatorKeysNotEnoughSDCollateral() public { ( bytes[] memory pubkeys, diff --git a/test/foundry_tests/ValidatorWithdrawalVault.t.sol b/test/foundry_tests/ValidatorWithdrawalVault.t.sol index 8b15e979..dabb95e6 100644 --- a/test/foundry_tests/ValidatorWithdrawalVault.t.sol +++ b/test/foundry_tests/ValidatorWithdrawalVault.t.sol @@ -21,6 +21,7 @@ import "../mocks/StakePoolManagerMock.sol"; import { SDUtilityPoolMock } from "../mocks/SDUtilityPoolMock.sol"; import { StaderOracleMock } from "../mocks/StaderOracleMock.sol"; +import { PermissionlessNodeRegistryMock } from "../mocks/PermissionlessNodeRegistryMock.sol"; contract ValidatorWithdrawalVaultTest is Test { address private constant OPERATOR_ADDRESS = address(500); @@ -52,6 +53,7 @@ contract ValidatorWithdrawalVaultTest is Test { mockStaderOracle(staderOracleMock); mockSDUtilityPool(sdUtilityPoolMock, operator); + mockPermissionlessNodeRegistry(vm.addr(105)); ProxyAdmin proxyAdmin = new ProxyAdmin(); @@ -93,6 +95,7 @@ contract ValidatorWithdrawalVaultTest is Test { staderConfig.updateOperatorRewardsCollector(address(operatorRC)); staderConfig.updateValidatorWithdrawalVaultImplementation(address(withdrawVaultImpl)); staderConfig.updateStaderOracle(staderOracleMock); + staderConfig.updatePermissionlessNodeRegistry(vm.addr(105)); staderConfig.grantRole(staderConfig.MANAGER(), staderManager); vaultFactory.grantRole(vaultFactory.NODE_REGISTRY_CONTRACT(), address(poolUtils.nodeRegistry())); vm.stopPrank(); @@ -384,4 +387,11 @@ contract ValidatorWithdrawalVaultTest is Test { abi.encode(UserData(0 ether, 4 ether, 1, 0)) ); } + + function mockPermissionlessNodeRegistry(address _permissionlessNodeRegistry) private { + emit log_named_address("permissionlessNodeRegistry", _permissionlessNodeRegistry); + PermissionlessNodeRegistryMock nodeRegistryMock = new PermissionlessNodeRegistryMock(); + bytes memory mockCode = address(nodeRegistryMock).code; + vm.etch(_permissionlessNodeRegistry, mockCode); + } }