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

feat: new unbonded mech #54

Merged
merged 3 commits into from
Jan 15, 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
61 changes: 48 additions & 13 deletions src/CSAccounting.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.21;

Check warning on line 4 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Found more than One contract per file. 2 contracts found!

import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";

Expand Down Expand Up @@ -57,9 +57,13 @@
keccak256("RESET_BOND_CURVE_ROLE");
bytes32 public constant BOND_BURNER_ROLE = keccak256("BOND_BURNER_ROLE");

uint256 public constant TOTAL_BASIS_POINTS = 10_000; // 100%
// TODO: should be reconsidered. is it should be absolute value or percent?
uint256 public constant BONDED_KEY_THRESHOLD_PERCENT_BP = 2000; // 20%

ICSModule private immutable CSM;

address public FEE_DISTRIBUTOR;

Check warning on line 66 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Variable name must be in mixedCase

/// @param bondCurve initial bond curve
/// @param admin admin role member address
Expand All @@ -80,8 +84,8 @@
CSBondLock(bondLockRetentionPeriod)
{
// check zero addresses
require(admin != address(0), "admin is zero address");

Check warning on line 87 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Use Custom Errors instead of require statements
require(

Check warning on line 88 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Use Custom Errors instead of require statements
communityStakingModule != address(0),
"community staking module is zero address"
);
Expand Down Expand Up @@ -142,12 +146,12 @@
}

/// @notice Pauses accounting by DAO decision.
function pauseAccounting() external onlyRole(PAUSE_ROLE) {

Check warning on line 149 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Code contains empty blocks
// TODO: implement me
}

/// @notice Unpauses accounting by DAO decision.
function resumeAccounting() external onlyRole(RESUME_ROLE) {

Check warning on line 154 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Code contains empty blocks
// TODO: implement me
}

Expand Down Expand Up @@ -206,35 +210,66 @@
}

/// @notice Returns the number of unbonded keys
/// @dev unbonded meaning amount of keys with no bond at all
/// @param nodeOperatorId id of the node operator to get keys count for.
/// @return unbonded keys count.
function getUnbondedKeysCount(
uint256 nodeOperatorId
) public view returns (uint256) {
// TODO: rework with threshold ???
return
_getUnbondedKeysCount({
nodeOperatorId: nodeOperatorId,
accountLockedBond: true
});
}

/// @notice Returns the number of unbonded keys to eject from validator set
/// @param nodeOperatorId id of the node operator to get keys count for.
/// @return unbonded keys count.
function getUnbondedKeysCountToEject(
uint256 nodeOperatorId
) public view returns (uint256) {
return
_getUnbondedKeysCount({
nodeOperatorId: nodeOperatorId,
accountLockedBond: false
});
}

/// @dev unbonded meaning amount of keys with bond less than threshold
function _getUnbondedKeysCount(
uint256 nodeOperatorId,
bool accountLockedBond
) internal view returns (uint256) {
uint256 activeKeys = _getActiveKeys(nodeOperatorId);
uint256 currentBond = CSBondCore._ethByShares(
_bondShares[nodeOperatorId]
);
// TODO: should the lock be included in the calculation to eject validators?
uint256 lockedBond = CSBondLock.getActualLockedBond(nodeOperatorId);
if (currentBond <= lockedBond) return activeKeys;
currentBond -= lockedBond;
if (accountLockedBond) {
uint256 lockedBond = CSBondLock.getActualLockedBond(nodeOperatorId);
if (currentBond <= lockedBond) return activeKeys;
currentBond -= lockedBond;
}
CSBondCurve.BondCurve memory bondCurve = CSBondCurve.getBondCurve(
nodeOperatorId
);
uint256 bondedKeys = CSBondCurve.getKeysCountByBondAmount(
currentBond,
bondCurve
);
if (
currentBond >
CSBondCurve.getBondAmountByKeysCount(bondedKeys, bondCurve)
) {
bondedKeys += 1;
}
return activeKeys > bondedKeys ? activeKeys - bondedKeys : 0;
if (bondedKeys >= activeKeys) return 0;
uint256 amountForBondedKeys = CSBondCurve.getBondAmountByKeysCount(
bondedKeys,
bondCurve
);
uint256 bondForNextKey = CSBondCurve.getBondAmountByKeysCount(
bondedKeys + 1,
bondCurve
) - amountForBondedKeys;
uint256 keyBondPercent = ((TOTAL_BASIS_POINTS *
(currentBond - amountForBondedKeys)) / bondForNextKey);
if (keyBondPercent < BONDED_KEY_THRESHOLD_PERCENT_BP)
return activeKeys - bondedKeys;
return activeKeys - bondedKeys - 1;
}

/// @notice Returns the required bond in ETH (inc. missed and excess) for the given node operator to upload new keys.
Expand Down Expand Up @@ -531,7 +566,7 @@
/// @param ETHAmount amount of ETH to request.
function requestExcessBondETH(
uint256 nodeOperatorId,
uint256 ETHAmount

Check warning on line 569 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Variable name must be in mixedCase
) external onlyExistingNodeOperator(nodeOperatorId) {
ICSModule.NodeOperatorInfo memory nodeOperator = CSM.getNodeOperator(
nodeOperatorId
Expand All @@ -555,7 +590,7 @@
bytes32[] memory rewardsProof,
uint256 nodeOperatorId,
uint256 cumulativeFeeShares,
uint256 ETHAmount

Check warning on line 593 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Variable name must be in mixedCase
) external onlyExistingNodeOperator(nodeOperatorId) {
ICSModule.NodeOperatorInfo memory nodeOperator = CSM.getNodeOperator(
nodeOperatorId
Expand Down Expand Up @@ -644,7 +679,7 @@
/// @dev Called only by DAO. Have lifetime. Once expired can never be called.
function burnBond(
uint256 nodeOperatorId
) external onlyRole(BOND_BURNER_ROLE) {

Check warning on line 682 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Code contains empty blocks
// TODO: implement me and request exits for all validators from CSModule
}

Expand Down Expand Up @@ -693,7 +728,7 @@
}

modifier onlyExistingNodeOperator(uint256 nodeOperatorId) {
require(

Check warning on line 731 in src/CSAccounting.sol

View workflow job for this annotation

GitHub Actions / Linters

Use Custom Errors instead of require statements
nodeOperatorId < CSM.getNodeOperatorsCount(),
"node operator does not exist"
);
Expand Down
4 changes: 2 additions & 2 deletions src/CSBondCurve.sol
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ abstract contract CSBondCurve is CSBondCurveBase {

/// @notice Returns the required bond in ETH for the given number of keys for default bond curve.
/// @dev To calculate the amount for the new keys 2 calls are required:
/// getRequiredBondETHForKeys(newTotal) - getRequiredBondETHForKeys(currentTotal)
/// getBondAmountByKeysCount(newTotal) - getBondAmountByKeysCount(currentTotal)
/// @param keys number of keys to get required bond for.
/// @return required amount for particular keys count.
function getBondAmountByKeysCount(
Expand All @@ -180,7 +180,7 @@ abstract contract CSBondCurve is CSBondCurveBase {

/// @notice Returns the required bond in ETH for the given number of keys for particular bond curve.
/// @dev To calculate the amount for the new keys 2 calls are required:
/// getRequiredBondETHForKeys(newTotal, curve) - getRequiredBondETHForKeys(currentTotal, curve)
/// getBondAmountByKeysCount(newTotal, curve) - getBondAmountByKeysCount(currentTotal, curve)
/// @param keys number of keys to get required bond for.
/// @param curve bond curve to get required bond for.
/// @return required in amount for particular keys count.
Expand Down
81 changes: 81 additions & 0 deletions test/CSAccounting.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,87 @@ contract CSAccountingGetUnbondedKeysCountTest is CSAccountingBondStateBaseTest {
}
}

contract CSAccountingGetUnbondedKeysCountToEjectTest is
CSAccountingBondStateBaseTest
{
function test_default() public override {
_operator({ ongoing: 16, withdrawn: 0 });
_deposit({ bond: 30 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 1);
}

function test_WithCurve() public override {
_operator({ ongoing: 16, withdrawn: 0 });
_deposit({ bond: 11.5 ether });
_curve(defaultCurve);
assertEq(accounting.getUnbondedKeysCountToEject(0), 5);
}

function test_WithLocked() public override {
_operator({ ongoing: 16, withdrawn: 0 });
_deposit({ bond: 30 ether });
_lock({ id: 0, amount: 2 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 1);
dgusakov marked this conversation as resolved.
Show resolved Hide resolved
}

function test_WithLocked_MoreThanBond() public {
_operator({ ongoing: 16, withdrawn: 0 });
_deposit({ bond: 30 ether });
_lock({ id: 0, amount: 100500 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 1);
}

function test_WithCurveAndLocked() public override {
_operator({ ongoing: 16, withdrawn: 0 });
_deposit({ bond: 11.5 ether });
_curve(defaultCurve);
_lock({ id: 0, amount: 1 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 5);
}

function test_WithOneWithdrawnValidator() public override {
_operator({ ongoing: 16, withdrawn: 1 });
_deposit({ bond: 11.5 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 9);
}

function test_WithBond() public override {
_operator({ ongoing: 16, withdrawn: 0 });
_deposit({ bond: 11.5 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 10);
}

function test_WithBondAndOneWithdrawnValidator() public override {
_operator({ ongoing: 16, withdrawn: 1 });
_deposit({ bond: 11.5 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 9);
}

function test_WithExcessBond() public override {
_operator({ ongoing: 16, withdrawn: 0 });
_deposit({ bond: 33 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 0);
}

function test_WithExcessBondAndOneWithdrawnValidator() public override {
_operator({ ongoing: 16, withdrawn: 1 });
_deposit({ bond: 33 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 0);
}

function test_WithMissingBond() public override {
_operator({ ongoing: 16, withdrawn: 0 });
_deposit({ bond: 5.75 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 13);
}

function test_WithMissingBondAndOneWithdrawnValidator() public override {
_operator({ ongoing: 16, withdrawn: 1 });
_deposit({ bond: 5.75 ether });
assertEq(accounting.getUnbondedKeysCountToEject(0), 12);
}
}

abstract contract CSAccountingGetRequiredBondBaseTest is
CSAccountingBondStateBaseTest
{
Expand Down