Skip to content

Commit

Permalink
Merge pull request #22 from yieldnest/feature/extra-stakingnode-tests
Browse files Browse the repository at this point in the history
Feature/extra stakingnode tests
  • Loading branch information
danoctavian authored Mar 5, 2024
2 parents affab6b + fff2d28 commit cde04ee
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 17 deletions.
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");
}
}

0 comments on commit cde04ee

Please sign in to comment.