Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/extra stakingnode tests #22

Merged
merged 2 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions src/StakingNode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea
error ETHDepositorNotDelayedWithdrawalRouter();
error WithdrawalAmountTooLow(uint256 sentAmount, uint256 pendingWithdrawnValidatorPrincipal);
error WithdrawalPrincipalAmountTooHigh(uint256 withdrawnValidatorPrincipal, uint256 allocatedETH);
error ClaimableAnmountExceedsPrincipal(uint256 withdrawnValidatorPrincipal, uint256 claimableAmount);
error ValidatorPrincipalExceedsTotalClaimable(uint256 withdrawnValidatorPrincipal, uint256 claimableAmount);
error ClaimAmountTooLow(uint256 expected, uint256 actual);
error ZeroAddress();
error NotStakingNodesManager();
Expand Down Expand Up @@ -143,8 +143,9 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea
for (uint256 i = 0; i < claimableWithdrawals.length; i++) {
totalClaimable += claimableWithdrawals[i].amount;
}

if (totalClaimable < withdrawnValidatorPrincipal) {
revert ClaimableAnmountExceedsPrincipal(withdrawnValidatorPrincipal, totalClaimable);
revert ValidatorPrincipalExceedsTotalClaimable(withdrawnValidatorPrincipal, totalClaimable);
}

// only claim if we have active unclaimed withdrawals
Expand All @@ -156,6 +157,7 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea
uint256 balanceAfter = address(this).balance;

uint256 claimedAmount = balanceAfter - balanceBefore;

if (totalClaimable > claimedAmount) {
revert ClaimAmountTooLow(totalClaimable, claimedAmount);
}
Expand Down
102 changes: 90 additions & 12 deletions test/foundry/integration/StakingNode.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,11 @@ import {IEigenPod} from "../../../src/external/eigenlayer/v0.1.0/interfaces/IEig
import {IDelayedWithdrawalRouter} from "../../../src/external/eigenlayer/v0.1.0/interfaces/IDelayedWithdrawalRouter.sol";
import {BeaconChainProofs} from "../../../src/external/eigenlayer/v0.1.0/BeaconChainProofs.sol";
import {MainnetEigenPodMock} from "../mocks/mainnet/MainnetEigenPodMock.sol";
import "../../../src/StakingNode.sol";
import {stdStorage, StdStorage} from "forge-std/Test.sol";


contract StakingNodeTest is IntegrationBaseTest {
using stdStorage for StdStorage;

function testCreateNodeAndAssertETHBalanceWithoutRegisteredValidators() public {

vm.prank(actors.STAKING_NODE_CREATOR);
IStakingNode stakingNodeInstance = stakingNodesManager.createStakingNode();

uint256 actualETHBalance = stakingNodeInstance.getETHBalance();
assertEq(actualETHBalance, 0, "ETH balance does not match expected value");
}
contract StakingNodeTestBase is IntegrationBaseTest {

function setupStakingNode(uint depositAmount) public returns (IStakingNode, IEigenPod) {

Expand Down Expand Up @@ -77,6 +68,10 @@ contract StakingNodeTest is IntegrationBaseTest {

return (stakingNodeInstance, eigenPodInstance);
}
}


contract StakingNodeEigenPod is StakingNodeTestBase {

function testCreateNodeAndVerifyPodStateIsValid() public {

Expand Down Expand Up @@ -114,6 +109,12 @@ contract StakingNodeTest is IntegrationBaseTest {

assertEq(rewardsAmount, rewardsSweeped, "Rewards amount does not match expected value");
}
}


contract StakingNodeWithdrawWithoutRestaking is StakingNodeTestBase {
using stdStorage for StdStorage;


function testWithdrawBeforeRestakingAndClaimDelayedWithdrawals() public {

Expand Down Expand Up @@ -206,7 +207,72 @@ contract StakingNodeTest is IntegrationBaseTest {
uint expectedRewards = rewardsSweeped - validatorPrincipal;
assertEq(rewardsAmount, expectedRewards, "Rewards amount does not match expected value");
}


function testValidatorPrincipalExceedsTotalClaimable() public {

uint activeValidators = 5;

uint depositAmount = activeValidators * 32 ether;
uint validatorPrincipal = depositAmount; // Total principal for all validators

(IStakingNode stakingNodeInstance, IEigenPod eigenPodInstance) = setupStakingNode(depositAmount);

// Simulate rewards being sweeped into the StakingNode's balance
uint rewardsSweeped = 3 * 32 ether;
address payable eigenPodAddress = payable(address(eigenPodInstance));
vm.deal(eigenPodAddress, rewardsSweeped);

// Trigger withdraw before restaking successfully
vm.prank(actors.STAKING_NODES_ADMIN);
stakingNodeInstance.withdrawBeforeRestaking();

// Simulate time passing for withdrawal delay
IDelayedWithdrawalRouter delayedWithdrawalRouter = stakingNodesManager.delayedWithdrawalRouter();
vm.roll(block.number + delayedWithdrawalRouter.withdrawalDelayBlocks() + 1);

uint256 tooLargeValidatorPrincipal = validatorPrincipal;

// Attempt to claim withdrawals with a validator principal that exceeds total claimable amount
vm.prank(actors.STAKING_NODES_ADMIN);
vm.expectRevert(abi.encodeWithSelector(StakingNode.ValidatorPrincipalExceedsTotalClaimable.selector, tooLargeValidatorPrincipal, rewardsSweeped));
stakingNodeInstance.claimDelayedWithdrawals(type(uint256).max, tooLargeValidatorPrincipal); // Exceeding by 1 ether
}


function testWithdrawalPrincipalAmountTooHigh() public {

uint activeValidators = 5;

uint depositAmount = activeValidators * 32 ether;
uint validatorPrincipal = depositAmount; // Total principal for all validators

(IStakingNode stakingNodeInstance, IEigenPod eigenPodInstance) = setupStakingNode(depositAmount);

// Simulate rewards being sweeped into the StakingNode's balance
uint rewardsSweeped = 5 * 32 ether;
address payable eigenPodAddress = payable(address(eigenPodInstance));
vm.deal(eigenPodAddress, rewardsSweeped);

// Trigger withdraw before restaking successfully
vm.prank(actors.STAKING_NODES_ADMIN);
stakingNodeInstance.withdrawBeforeRestaking();

// Simulate time passing for withdrawal delay
IDelayedWithdrawalRouter delayedWithdrawalRouter = stakingNodesManager.delayedWithdrawalRouter();
vm.roll(block.number + delayedWithdrawalRouter.withdrawalDelayBlocks() + 1);

uint256 tooLargeValidatorPrincipal = validatorPrincipal + 1 ether;
uint256 expectedAllocatedETH = depositAmount;

// Attempt to claim withdrawals with a validator principal that exceeds total claimable amount
vm.prank(actors.STAKING_NODES_ADMIN);
vm.expectRevert(abi.encodeWithSelector(StakingNode.WithdrawalPrincipalAmountTooHigh.selector, tooLargeValidatorPrincipal, expectedAllocatedETH));
stakingNodeInstance.claimDelayedWithdrawals(type(uint256).max, tooLargeValidatorPrincipal); // Exceeding by 1 ether
}
}

contract StakingNodeVerifyWithdrawalCredentials is StakingNodeTestBase {
using stdStorage for StdStorage;

function testVerifyWithdrawalCredentialsRevertingWhenPaused() public {

Expand Down Expand Up @@ -376,3 +442,15 @@ contract StakingNodeTest is IntegrationBaseTest {
}
}

contract StakingNodeMiscTests is StakingNodeTestBase {

function testSendingETHToStakingNodeShouldRevert() public {
(IStakingNode stakingNodeInstance, IEigenPod eigenPodInstance) = setupStakingNode(32 ether);
uint256 initialBalance = address(stakingNodeInstance).balance;
uint256 amountToSend = 1 ether;

// Attempt to send ETH to the StakingNode contract
(bool sent, ) = address(stakingNodeInstance).call{value: amountToSend}("");
assertFalse(sent, "Sending ETH should fail");
}
}
78 changes: 75 additions & 3 deletions test/foundry/integration/StakingNodesManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {IEigenPod} from "../../../src/external/eigenlayer/v0.1.0/interfaces/IEig
import {IStakingNodesManager} from "../../../src/interfaces/IStakingNodesManager.sol";
import "forge-std/console.sol";
import "../../../src/StakingNodesManager.sol";
import "../../../src/ynETH.sol";
import "../mocks/TestStakingNodeV2.sol";
import "../mocks/TestStakingNodesManagerV2.sol";

Expand Down Expand Up @@ -89,8 +90,6 @@ contract StakingNodesManagerStakingNodeCreation is IntegrationBaseTest {
vm.expectRevert("AccessControlUnauthorizedAccount");
stakingNodesManager.createStakingNode();
}


}

contract StakingNodesManagerStakingNodeImplementation is IntegrationBaseTest {
Expand Down Expand Up @@ -313,7 +312,68 @@ contract StakingNodesManagerRegisterValidators is IntegrationBaseTest {
vm.expectRevert(abi.encodeWithSelector(StakingNodesManager.ValidatorAlreadyUsed.selector, ONE_PUBLIC_KEY) );
stakingNodesManager.registerValidators(depositRoot, validatorData);
}


function testRegisterValidatorsWithInsufficientDeposit() public {
address addr1 = vm.addr(100);
vm.deal(addr1, 100 ether);
uint validatorCount = 1;
uint depositAmount = 16 ether; // Insufficient deposit amount
vm.prank(addr1);
yneth.depositETH{value: depositAmount}(addr1);

vm.prank(actors.STAKING_NODE_CREATOR);
stakingNodesManager.createStakingNode();
// Attempt to register a validator with insufficient deposit
IStakingNodesManager.ValidatorData[] memory validatorData = new IStakingNodesManager.ValidatorData[](1);
validatorData[0] = IStakingNodesManager.ValidatorData({
publicKey: ONE_PUBLIC_KEY,
signature: ZERO_SIGNATURE,
nodeId: 0,
depositDataRoot: bytes32(0)
});

for (uint i = 0; i < validatorData.length; i++) {
bytes memory withdrawalCredentials = stakingNodesManager.getWithdrawalCredentials(validatorData[i].nodeId);
uint amount = depositAmount / validatorData.length;
bytes32 depositDataRoot = stakingNodesManager.generateDepositRoot(validatorData[i].publicKey, validatorData[i].signature, withdrawalCredentials, amount);
validatorData[i].depositDataRoot = depositDataRoot;
}

bytes32 depositRoot = depositContractEth2.get_deposit_root();
vm.prank(actors.VALIDATOR_MANAGER);
vm.expectRevert(abi.encodeWithSelector(ynETH.InsufficientBalance.selector));
stakingNodesManager.registerValidators(depositRoot, validatorData);
}

function testRegisterValidatorsWithExceedingMaxNodeCount() public {
address addr1 = vm.addr(100);
vm.deal(addr1, 100 ether);
uint validatorCount = 1;
uint depositAmount = 32 ether * validatorCount;
vm.prank(addr1);
yneth.depositETH{value: depositAmount}(addr1);

// Create nodes up to the max node count
uint256 maxNodeCount = stakingNodesManager.maxNodeCount();
for (uint256 i = 0; i < maxNodeCount; i++) {
vm.prank(actors.STAKING_NODE_CREATOR);
stakingNodesManager.createStakingNode();
}

// Attempt to register a validator when max node count is reached
IStakingNodesManager.ValidatorData[] memory validatorData = new IStakingNodesManager.ValidatorData[](1);
validatorData[0] = IStakingNodesManager.ValidatorData({
publicKey: ONE_PUBLIC_KEY,
signature: ZERO_SIGNATURE,
nodeId: uint256(maxNodeCount), // Node ID equal to max node count
depositDataRoot: bytes32(0)
});

bytes32 depositRoot = depositContractEth2.get_deposit_root();
vm.prank(actors.VALIDATOR_MANAGER);
vm.expectRevert(abi.encodeWithSelector(StakingNodesManager.InvalidNodeId.selector, maxNodeCount));
stakingNodesManager.registerValidators(depositRoot, validatorData);
}
}

contract StakingNodesManagerViews is IntegrationBaseTest {
Expand Down Expand Up @@ -358,4 +418,16 @@ contract StakingNodesManagerViews is IntegrationBaseTest {
isAdmin = stakingNodesManager.isStakingNodesAdmin(nonAdminAddress);
assertFalse(isAdmin, "Address should not be an admin");
}
}

contract StakingNodesManagerMisc is IntegrationBaseTest {

function testSendingETHToStakingNodesManagerShouldRevert() public {
uint256 initialBalance = address(stakingNodesManager).balance;
uint256 amountToSend = 1 ether;

// Send ETH to the StakingNodesManager contract
(bool sent, ) = address(stakingNodesManager).call{value: amountToSend}("");
assertFalse(sent, "Sending ETH should fail");
}
}