diff --git a/bindings/legacy/v1.3.1/protocol/node.go b/bindings/legacy/v1.3.1/protocol/node.go new file mode 100644 index 000000000..f18fb01c7 --- /dev/null +++ b/bindings/legacy/v1.3.1/protocol/node.go @@ -0,0 +1,95 @@ +package protocol + +import ( + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/rocket-pool/smartnode/bindings/dao/protocol" + "github.com/rocket-pool/smartnode/bindings/rocketpool" + "github.com/rocket-pool/smartnode/bindings/types" + "github.com/rocket-pool/smartnode/bindings/utils/eth" +) + +// Config +const ( + NodeSettingsContractName string = "rocketDAOProtocolSettingsNode" + MinimumPerMinipoolStakeSettingPath string = "node.per.minipool.stake.minimum" + MaximumPerMinipoolStakeSettingPath string = "node.per.minipool.stake.maximum" +) + +// The minimum RPL stake per minipool as a fraction of assigned user ETH +func GetMinimumPerMinipoolStake(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) { + nodeSettingsContract, err := getNodeSettingsContract(rp, opts) + if err != nil { + return 0, err + } + value := new(*big.Int) + if err := nodeSettingsContract.Call(opts, value, "getMinimumPerMinipoolStake"); err != nil { + return 0, fmt.Errorf("error getting minimum RPL stake per minipool: %w", err) + } + return eth.WeiToEth(*value), nil +} +func ProposeMinimumPerMinipoolStake(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) { + return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MinimumPerMinipoolStakeSettingPath), NodeSettingsContractName, MinimumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts) +} +func EstimateProposeMinimumPerMinipoolStakeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) { + return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinimumPerMinipoolStakeSettingPath), NodeSettingsContractName, MinimumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts) +} + +// The minimum RPL stake per minipool as a fraction of assigned user ETH +func GetMinimumPerMinipoolStakeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) { + nodeSettingsContract, err := getNodeSettingsContract(rp, opts) + if err != nil { + return nil, err + } + value := new(*big.Int) + if err := nodeSettingsContract.Call(opts, value, "getMinimumPerMinipoolStake"); err != nil { + return nil, fmt.Errorf("error getting minimum RPL stake per minipool: %w", err) + } + return *value, nil +} + +// The maximum RPL stake per minipool as a fraction of assigned user ETH +func GetMaximumPerMinipoolStake(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) { + nodeSettingsContract, err := getNodeSettingsContract(rp, opts) + if err != nil { + return 0, err + } + value := new(*big.Int) + if err := nodeSettingsContract.Call(opts, value, "getMaximumPerMinipoolStake"); err != nil { + return 0, fmt.Errorf("error getting maximum RPL stake per minipool: %w", err) + } + return eth.WeiToEth(*value), nil +} +func ProposeMaximumPerMinipoolStake(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) { + return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MaximumPerMinipoolStakeSettingPath), NodeSettingsContractName, MaximumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts) +} +func EstimateProposeMaximumPerMinipoolStakeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) { + return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MaximumPerMinipoolStakeSettingPath), NodeSettingsContractName, MaximumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts) +} + +// The maximum RPL stake per minipool as a fraction of assigned user ETH +func GetMaximumPerMinipoolStakeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) { + nodeSettingsContract, err := getNodeSettingsContract(rp, opts) + if err != nil { + return nil, err + } + value := new(*big.Int) + if err := nodeSettingsContract.Call(opts, value, "getMaximumPerMinipoolStake"); err != nil { + return nil, fmt.Errorf("error getting maximum RPL stake per minipool: %w", err) + } + return *value, nil +} + +// Get contracts +var nodeSettingsContractLock sync.Mutex + +func getNodeSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) { + nodeSettingsContractLock.Lock() + defer nodeSettingsContractLock.Unlock() + return rp.GetContract(NodeSettingsContractName, opts) +} diff --git a/bindings/node/staking.go b/bindings/node/staking.go index be89d397c..85688b71c 100644 --- a/bindings/node/staking.go +++ b/bindings/node/staking.go @@ -111,19 +111,6 @@ func GetNodeUnstakingRPL(rp *rocketpool.RocketPool, nodeAddress common.Address, return *unstakingRpl, nil } -// Get a node's maximum RPL stake to collateralize their minipools -func GetNodeMaximumRPLStakeForMinipools(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) { - rocketNodeStaking, err := getRocketNodeStaking(rp, opts) - if err != nil { - return nil, err - } - nodeMaximumRplStake := new(*big.Int) - if err := rocketNodeStaking.Call(opts, nodeMaximumRplStake, "getNodeMaximumRPLStakeForMinipools", nodeAddress); err != nil { - return nil, fmt.Errorf("error getting maximum node RPL stake for minipools: %w", err) - } - return *nodeMaximumRplStake, nil -} - // Get the time a node last staked RPL func GetNodeRPLStakedTime(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (uint64, error) { rocketNodeStaking, err := getRocketNodeStaking(rp, opts) @@ -202,6 +189,19 @@ func GetNodeMinipoolETHBorrowed(rp *rocketpool.RocketPool, nodeAddress common.Ad return *nodeMinipoolETHBorrowed, nil } +// Get the minimum amount of legacy staked RPL a node must have after unstaking +func GetNodeMinimumLegacyRPLStake(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) { + rocketNodeStaking, err := getRocketNodeStaking(rp, opts) + if err != nil { + return nil, err + } + nodeMinimumLegacyRplStake := new(*big.Int) + if err := rocketNodeStaking.Call(opts, nodeMinimumLegacyRplStake, "getNodeMinimumLegacyRPLStake", nodeAddress); err != nil { + return nil, fmt.Errorf("error getting node minimum legacy rpl stake: %w", err) + } + return *nodeMinimumLegacyRplStake, nil +} + // Get the amount of ETH the node has bonded func GetNodeEthBonded(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) { rocketNodeStaking, err := getRocketNodeStaking(rp, opts) diff --git a/bindings/settings/protocol/node.go b/bindings/settings/protocol/node.go index eb24fbb41..6b63897dc 100644 --- a/bindings/settings/protocol/node.go +++ b/bindings/settings/protocol/node.go @@ -21,8 +21,7 @@ const ( SmoothingPoolRegistrationEnabledSettingPath string = "node.smoothing.pool.registration.enabled" NodeDepositEnabledSettingPath string = "node.deposit.enabled" VacantMinipoolsEnabledSettingPath string = "node.vacant.minipools.enabled" - MinimumPerMinipoolStakeSettingPath string = "node.per.minipool.stake.minimum" - MaximumPerMinipoolStakeSettingPath string = "node.per.minipool.stake.maximum" + MinimumLegacyRplStakePath string = "node.minimum.legacy.staked.rpl" ReducedBondSettingPath string = "reduced.bond" NodeUnstakingPeriodSettingPath string = "node.unstaking.period" ) @@ -103,66 +102,34 @@ func EstimateProposeVacantMinipoolsEnabledGas(rp *rocketpool.RocketPool, value b return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", VacantMinipoolsEnabledSettingPath), NodeSettingsContractName, VacantMinipoolsEnabledSettingPath, value, blockNumber, treeNodes, opts) } -// The minimum RPL stake per minipool as a fraction of assigned user ETH -func GetMinimumPerMinipoolStake(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) { +// The amount of legacy staked RPL required by a node after unstaking as percentage of their borrowed ETH +func GetMinimumLegacyRPLStake(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) { nodeSettingsContract, err := getNodeSettingsContract(rp, opts) if err != nil { return 0, err } value := new(*big.Int) - if err := nodeSettingsContract.Call(opts, value, "getMinimumPerMinipoolStake"); err != nil { - return 0, fmt.Errorf("error getting minimum RPL stake per minipool: %w", err) + if err := nodeSettingsContract.Call(opts, value, "getMinimumLegacyRPLStake"); err != nil { + return 0, fmt.Errorf("error getting minimum legacy RPL stake per node: %w", err) } return eth.WeiToEth(*value), nil } -func ProposeMinimumPerMinipoolStake(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) { - return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MinimumPerMinipoolStakeSettingPath), NodeSettingsContractName, MinimumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts) +func ProposeMinimumLecacyRPLStake(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) { + return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MinimumLegacyRplStakePath), NodeSettingsContractName, MinimumLegacyRplStakePath, value, blockNumber, treeNodes, opts) } -func EstimateProposeMinimumPerMinipoolStakeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) { - return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinimumPerMinipoolStakeSettingPath), NodeSettingsContractName, MinimumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts) +func EstimateProposeMinimumLecacyRPLStakeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) { + return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinimumLegacyRplStakePath), NodeSettingsContractName, MinimumLegacyRplStakePath, value, blockNumber, treeNodes, opts) } -// The minimum RPL stake per minipool as a fraction of assigned user ETH -func GetMinimumPerMinipoolStakeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) { +// The amount of legacy staked RPL required by a node after unstaking as percentage of their borrowed ETH +func GetMinimumLegacyRPLStakeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) { nodeSettingsContract, err := getNodeSettingsContract(rp, opts) if err != nil { return nil, err } value := new(*big.Int) - if err := nodeSettingsContract.Call(opts, value, "getMinimumPerMinipoolStake"); err != nil { - return nil, fmt.Errorf("error getting minimum RPL stake per minipool: %w", err) - } - return *value, nil -} - -// The maximum RPL stake per minipool as a fraction of assigned user ETH -func GetMaximumPerMinipoolStake(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) { - nodeSettingsContract, err := getNodeSettingsContract(rp, opts) - if err != nil { - return 0, err - } - value := new(*big.Int) - if err := nodeSettingsContract.Call(opts, value, "getMaximumPerMinipoolStake"); err != nil { - return 0, fmt.Errorf("error getting maximum RPL stake per minipool: %w", err) - } - return eth.WeiToEth(*value), nil -} -func ProposeMaximumPerMinipoolStake(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) { - return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MaximumPerMinipoolStakeSettingPath), NodeSettingsContractName, MaximumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts) -} -func EstimateProposeMaximumPerMinipoolStakeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) { - return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MaximumPerMinipoolStakeSettingPath), NodeSettingsContractName, MaximumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts) -} - -// The maximum RPL stake per minipool as a fraction of assigned user ETH -func GetMaximumPerMinipoolStakeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) { - nodeSettingsContract, err := getNodeSettingsContract(rp, opts) - if err != nil { - return nil, err - } - value := new(*big.Int) - if err := nodeSettingsContract.Call(opts, value, "getMaximumPerMinipoolStake"); err != nil { - return nil, fmt.Errorf("error getting maximum RPL stake per minipool: %w", err) + if err := nodeSettingsContract.Call(opts, value, "getMinimumLegacyRPLStake"); err != nil { + return nil, fmt.Errorf("error getting raw minimum legacy RPL stake per node: %w", err) } return *value, nil } diff --git a/bindings/utils/state/network.go b/bindings/utils/state/network.go index ac135317c..3f48fb835 100644 --- a/bindings/utils/state/network.go +++ b/bindings/utils/state/network.go @@ -35,6 +35,7 @@ type NetworkDetails struct { RplPrice *big.Int `json:"rpl_price"` MinCollateralFraction *big.Int `json:"min_collateral_fraction"` MaxCollateralFraction *big.Int `json:"max_collateral_fraction"` + MinimumLegacyRplStakeFraction *big.Int `json:"minimum_legacy_rpl_stake_fraction"` IntervalDuration time.Duration `json:"interval_duration"` IntervalStart time.Time `json:"interval_start"` NodeOperatorRewardsPercent *big.Int `json:"node_operator_rewards_percent"` @@ -114,8 +115,6 @@ func NewNetworkDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, i // Multicall getters contracts.Multicaller.AddCall(contracts.RocketNetworkPrices, &details.RplPrice, "getRPLPrice") - contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNode, &details.MinCollateralFraction, "getMinimumPerMinipoolStake") - contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNode, &details.MaxCollateralFraction, "getMaximumPerMinipoolStake") contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &rewardIndex, "getRewardIndex") contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &intervalStart, "getClaimIntervalTimeStart") contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &intervalDuration, "getClaimIntervalTime") @@ -155,13 +154,19 @@ func NewNetworkDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, i contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &pricesSubmissionFrequency, "getSubmitPricesFrequency") contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &balancesSubmissionFrequency, "getSubmitBalancesFrequency") + // Exists in Houston but to be removed in Saturn + if !isSaturnDeployed { + contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNode, &details.MinCollateralFraction, "getMinimumPerMinipoolStake") + contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNode, &details.MaxCollateralFraction, "getMaximumPerMinipoolStake") + } + // Saturn if isSaturnDeployed { contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &details.MegapoolRevenueSplitSettings.NodeOperatorCommissionShare, "getNodeShare") contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &details.MegapoolRevenueSplitSettings.NodeOperatorCommissionAdder, "getNodeShareSecurityCouncilAdder") contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &details.MegapoolRevenueSplitSettings.VoterCommissionShare, "getVoterShare") contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &details.MegapoolRevenueSplitSettings.PdaoCommissionShare, "getProtocolDAOShare") - + contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNode, &details.MinimumLegacyRplStakeFraction, "getMinimumLegacyRPLStake") contracts.Multicaller.AddCall(contracts.RocketNetworkRevenues, &details.MegapoolRevenueSplitTimeWeightedAverages.NodeShare, "getCurrentNodeShare") contracts.Multicaller.AddCall(contracts.RocketNetworkRevenues, &details.MegapoolRevenueSplitTimeWeightedAverages.VoterShare, "getCurrentVoterShare") contracts.Multicaller.AddCall(contracts.RocketNetworkRevenues, &details.MegapoolRevenueSplitTimeWeightedAverages.PdaoShare, "getCurrentProtocolDAOShare") diff --git a/bindings/utils/state/node.go b/bindings/utils/state/node.go index 73ff565b1..758b7634b 100644 --- a/bindings/utils/state/node.go +++ b/bindings/utils/state/node.go @@ -129,6 +129,7 @@ func GetNativeNodeDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts // Do some postprocessing on the node data details.DistributorBalance = distributorBalance + // TODO effectiveRPLStake and MinimumRPLStake are deprecated in Saturn // Fix the effective stake if details.EffectiveRPLStake.Cmp(details.MinimumRPLStake) == -1 { details.EffectiveRPLStake.SetUint64(0) @@ -211,6 +212,7 @@ func GetAllNativeNodeDetails(rp *rocketpool.RocketPool, contracts *NetworkContra details := &nodeDetails[i] details.DistributorBalance = balances[i] + // TODO effectiveRPLStake and MinimumRPLStake are deprecated in Saturn // Fix the effective stake if details.EffectiveRPLStake.Cmp(details.MinimumRPLStake) == -1 { details.EffectiveRPLStake.SetUint64(0) diff --git a/rocketpool-cli/node/stake-rpl.go b/rocketpool-cli/node/stake-rpl.go index cc8591d47..010c68527 100644 --- a/rocketpool-cli/node/stake-rpl.go +++ b/rocketpool-cli/node/stake-rpl.go @@ -330,8 +330,8 @@ func nodeStakeRpl(c *cli.Context) error { } else { if !(c.Bool("yes") || prompt.Confirm(fmt.Sprintf("Are you sure you want to stake %.6f RPL? You will not be able to unstake this RPL until you exit your validators and close your minipools, or reach %.6f staked RPL (%.0f%% of bonded eth)!", math.RoundDown(eth.WeiToEth(amountWei), 6), - math.RoundDown(eth.WeiToEth(status.MaximumRplStake), 6), - status.MaximumStakeFraction*100))) { + math.RoundDown(eth.WeiToEth(status.RplStakeThreshold), 6), + status.RplStakeThresholdFraction*100))) { fmt.Println("Cancelled.") return nil } diff --git a/rocketpool-cli/node/status.go b/rocketpool-cli/node/status.go index fce84a355..962a2471c 100644 --- a/rocketpool-cli/node/status.go +++ b/rocketpool-cli/node/status.go @@ -334,6 +334,10 @@ func getStatus(c *cli.Context) error { if status.RplStakeLegacy != nil && status.RplStakeLegacy.Cmp(big.NewInt(0)) != 0 { fmt.Printf("The node has %6f legacy staked RPL.\n", math.RoundDown(eth.WeiToEth(status.RplStakeLegacy), 6)) fmt.Printf("The node has a total stake (legacy minipool RPL plus megapool RPL) of %.6f RPL.\n", math.RoundDown(eth.WeiToEth(status.RplStake), 6)) + if status.RplStakeLegacy.Cmp(status.RplStakeThreshold) > 1 { + fmt.Printf( + "You can withdraw down to %.6f Legacy RPL (%.0f%% of borrowed eth)\n", math.RoundDown(eth.WeiToEth(status.RplStakeThreshold), 6), (status.RplStakeThresholdFraction)*100) + } } var unstakingPeriodEnd time.Time if status.UnstakingRPL.Cmp(big.NewInt(0)) > 0 { @@ -356,10 +360,10 @@ func getStatus(c *cli.Context) error { } else { // Withdrawal limit pre-saturn 1 rplTotalStake := math.RoundDown(eth.WeiToEth(status.RplStake), 6) - rplWithdrawalLimit := math.RoundDown(eth.WeiToEth(status.MaximumRplStake), 6) + rplWithdrawalLimit := math.RoundDown(eth.WeiToEth(status.RplStakeThreshold), 6) if rplTotalStake > rplWithdrawalLimit { fmt.Printf( - "You can withdraw down to %.6f RPL (%.0f%% of bonded eth)\n", math.RoundDown(eth.WeiToEth(status.MaximumRplStake), 6), (status.MaximumStakeFraction)*100) + "You can withdraw down to %.6f RPL (%.0f%% of bonded eth)\n", math.RoundDown(eth.WeiToEth(status.RplStakeThreshold), 6), (status.RplStakeThresholdFraction)*100) } } diff --git a/rocketpool-cli/node/withdraw-rpl.go b/rocketpool-cli/node/withdraw-rpl.go index 0b8cbcfc3..34c40b17c 100644 --- a/rocketpool-cli/node/withdraw-rpl.go +++ b/rocketpool-cli/node/withdraw-rpl.go @@ -231,7 +231,7 @@ func nodeWithdrawRpl(c *cli.Context) error { // defined by decreaseNodeLegacyRPLStake in RocketNodeStaking.sol var maxAmount big.Int var amountWei *big.Int - withdrawableFromLegacy := new(big.Int).Sub(status.RplStakeLegacy, status.MaximumRplStake) + withdrawableFromLegacy := new(big.Int).Sub(status.RplStakeLegacy, status.RplStakeThreshold) withdrawableFromTotal := new(big.Int).Sub(status.RplStake, status.NodeRPLLocked) if withdrawableFromLegacy.Cmp(withdrawableFromTotal) < 0 { maxAmount.Set(withdrawableFromLegacy) @@ -259,8 +259,8 @@ func nodeWithdrawRpl(c *cli.Context) error { } else { fmt.Printf("Cannot unstake legacy RPL - you have %.6f legacy RPL, but are not allowed to unstake below %.6f RPL (%d%% collateral).\n", math.RoundDown(eth.WeiToEth(status.RplStakeLegacy), 6), - math.RoundDown(eth.WeiToEth(status.MaximumRplStake), 6), - uint32(status.MaximumStakeFraction*100), + math.RoundDown(eth.WeiToEth(status.RplStakeThreshold), 6), + uint32(status.RplStakeThresholdFraction*100), ) return nil } @@ -324,8 +324,8 @@ func nodeWithdrawRpl(c *cli.Context) error { // Set amount to maximum withdrawable amount var maxAmount big.Int - if status.RplStake.Cmp(status.MaximumRplStake) > 0 { - maxAmount.Sub(status.RplStake, status.MaximumRplStake) + if status.RplStake.Cmp(status.RplStakeThreshold) > 0 { + maxAmount.Sub(status.RplStake, status.RplStakeThreshold) } amountWei = &maxAmount @@ -348,7 +348,7 @@ func nodeWithdrawRpl(c *cli.Context) error { // Get maximum withdrawable amount var maxAmount big.Int - maxAmount.Sub(status.RplStake, status.MaximumRplStake) + maxAmount.Sub(status.RplStake, status.RplStakeThreshold) maxAmount.Sub(&maxAmount, status.NodeRPLLocked) if maxAmount.Sign() == 1 { // Prompt for maximum amount @@ -368,8 +368,8 @@ func nodeWithdrawRpl(c *cli.Context) error { } else { fmt.Printf("Cannot withdraw staked RPL - you have %.6f RPL staked, but are not allowed to withdraw below %.6f RPL (%d%% collateral).\n", math.RoundDown(eth.WeiToEth(status.RplStake), 6), - math.RoundDown(eth.WeiToEth(status.MaximumRplStake), 6), - uint32(status.MaximumStakeFraction*100), + math.RoundDown(eth.WeiToEth(status.RplStakeThreshold), 6), + uint32(status.RplStakeThresholdFraction*100), ) return nil } diff --git a/rocketpool-cli/pdao/commands.go b/rocketpool-cli/pdao/commands.go index fffdc0129..4c0adf734 100644 --- a/rocketpool-cli/pdao/commands.go +++ b/rocketpool-cli/pdao/commands.go @@ -5,6 +5,7 @@ import ( "github.com/urfave/cli" + protocol131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/protocol" "github.com/rocket-pool/smartnode/bindings/settings/protocol" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" ) @@ -1893,7 +1894,7 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { { Name: "minimum-per-minipool-stake", Aliases: []string{"minpms"}, - Usage: fmt.Sprintf("Propose updating the %s setting; %s", protocol.MinimumPerMinipoolStakeSettingPath, unboundedPercentUsage), + Usage: fmt.Sprintf("Propose updating the %s setting; %s", protocol131.MinimumPerMinipoolStakeSettingPath, unboundedPercentUsage), UsageText: "rocketpool pdao propose setting node minimum-per-minipool-stake value", Flags: []cli.Flag{ cli.BoolFlag{ @@ -1925,7 +1926,7 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { { Name: "maximum-per-minipool-stake", Aliases: []string{"maxpms"}, - Usage: fmt.Sprintf("Propose updating the %s setting; %s", protocol.MaximumPerMinipoolStakeSettingPath, unboundedPercentUsage), + Usage: fmt.Sprintf("Propose updating the %s setting; %s", protocol131.MaximumPerMinipoolStakeSettingPath, unboundedPercentUsage), UsageText: "rocketpool pdao propose setting node maximum-per-minipool-stake value", Flags: []cli.Flag{ cli.BoolFlag{ @@ -1954,6 +1955,38 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { }, }, + { + Name: "minimum-legacy-staked-rpl", + Aliases: []string{"mlsr"}, + Usage: fmt.Sprintf("Propose updating the %s setting; %s", protocol.MinimumLegacyRplStakePath, unboundedPercentUsage), + UsageText: "rocketpool pdao propose setting node minimum-legacy-staked-rpl value", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "raw", + Usage: "Add this flag if your setting is an 18-decimal-fixed-point-integer (wei) value instead of a float", + }, + cli.BoolFlag{ + Name: "yes, y", + Usage: "Automatically confirm all interactive questions", + }, + }, + Action: func(c *cli.Context) error { + + // Validate args + if err := cliutils.ValidateArgCount(c, 1); err != nil { + return err + } + value, err := cliutils.ValidateFloat(c, "value", c.Args().Get(0), false) + if err != nil { + return err + } + + // Run + return proposeSettingNodeMinimumLegacyRplStake(c, value) + + }, + }, + { Name: "reduced-bond", Aliases: []string{"rb"}, diff --git a/rocketpool-cli/pdao/get-settings.go b/rocketpool-cli/pdao/get-settings.go index f4b414964..8934a6990 100644 --- a/rocketpool-cli/pdao/get-settings.go +++ b/rocketpool-cli/pdao/get-settings.go @@ -100,11 +100,14 @@ func getSettings(c *cli.Context) error { fmt.Printf("\tSmoothing Pool Opt-In Enabled: %t\n", response.Node.IsSmoothingPoolRegistrationEnabled) fmt.Printf("\tNode Deposits Enabled: %t\n", response.Node.IsDepositingEnabled) fmt.Printf("\tVacant Minipools Enabled: %t\n", response.Node.AreVacantMinipoolsEnabled) - fmt.Printf("\tMin Stake per Minipool: %.2f%%\n", eth.WeiToEth(response.Node.MinimumPerMinipoolStake)*100) - fmt.Printf("\tMax Stake per Minipool: %.2f%%\n", eth.WeiToEth(response.Node.MaximumPerMinipoolStake)*100) + if !response.SaturnDeployed { + fmt.Printf("\tMin Stake per Minipool: %.2f%%\n", eth.WeiToEth(response.Node.MinimumPerMinipoolStake)*100) + fmt.Printf("\tMax Stake per Minipool: %.2f%%\n", eth.WeiToEth(response.Node.MaximumPerMinipoolStake)*100) + } if response.SaturnDeployed { fmt.Printf("\tReduced Bond: %.6f ETH\n", response.Node.ReducedBond) fmt.Printf("\tNode Unstaking Period: %s\n", response.Node.NodeUnstakingPeriod) + fmt.Printf("\tMin Legacy RPL Stake: %s\n", response.Node.MinimumLegacyRplStake) } fmt.Println() diff --git a/rocketpool-cli/pdao/propose-settings.go b/rocketpool-cli/pdao/propose-settings.go index 6b128f139..6a87fa6a9 100644 --- a/rocketpool-cli/pdao/propose-settings.go +++ b/rocketpool-cli/pdao/propose-settings.go @@ -11,9 +11,11 @@ import ( "github.com/rocket-pool/smartnode/bindings/utils/eth" "github.com/urfave/cli" + protocol131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/protocol" "github.com/rocket-pool/smartnode/shared/services/gas" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) @@ -214,12 +216,17 @@ func proposeSettingNodeAreVacantMinipoolsEnabled(c *cli.Context, value bool) err func proposeSettingNodeMinimumPerMinipoolStake(c *cli.Context, value *big.Int) error { trueValue := value.String() - return proposeSetting(c, protocol.NodeSettingsContractName, protocol.MinimumPerMinipoolStakeSettingPath, trueValue) + return proposeSetting(c, protocol.NodeSettingsContractName, protocol131.MinimumPerMinipoolStakeSettingPath, trueValue) } func proposeSettingNodeMaximumPerMinipoolStake(c *cli.Context, value *big.Int) error { trueValue := value.String() - return proposeSetting(c, protocol.NodeSettingsContractName, protocol.MaximumPerMinipoolStakeSettingPath, trueValue) + return proposeSetting(c, protocol.NodeSettingsContractName, protocol131.MaximumPerMinipoolStakeSettingPath, trueValue) +} + +func proposeSettingNodeMinimumLegacyRplStake(c *cli.Context, value *big.Int) error { + trueValue := value.String() + return proposeSetting(c, protocol.NodeSettingsContractName, protocol.MinimumLegacyRplStakePath, trueValue) } func proposeSettingReducedBond(c *cli.Context, value *big.Int) error { @@ -394,6 +401,10 @@ func proposeSetting(c *cli.Context, contract string, setting string, value strin fmt.Println("This command is only available after the Saturn upgrade.") return nil } + if saturnResp.IsSaturnDeployed && isHoustonOnlySetting(setting) { + fmt.Println("This command no longer available in Saturn.") + return nil + } // Check if proposal can be made canPropose, err := rp.PDAOCanProposeSetting(contract, setting, value) @@ -456,6 +467,7 @@ func isSaturnOnlySetting(setting string) bool { protocol.NetworkPDAOSharePath: {}, protocol.NetworkMaxNodeShareSecurityCouncilAdderPath: {}, protocol.NetworkMaxRethBalanceDeltaPath: {}, + protocol.MinimumLegacyRplStakePath: {}, protocol.ReducedBondSettingPath: {}, protocol.NodeUnstakingPeriodSettingPath: {}, protocol.MegapoolTimeBeforeDissolveSettingsPath: {}, @@ -468,3 +480,16 @@ func isSaturnOnlySetting(setting string) bool { _, exists := saturnOnlySettings[setting] return exists } + +// Returns true if the given setting is only available on Houston 1.3.1 (before the Saturn upgrade). +func isHoustonOnlySetting(setting string) bool { + + // Map of Houston only settings + houstonOnlySettings := map[string]struct{}{ + protocol131.MinimumPerMinipoolStakeSettingPath: {}, + protocol131.MaximumPerMinipoolStakeSettingPath: {}, + } + + _, exists := houstonOnlySettings[setting] + return exists +} diff --git a/rocketpool/api/node/claim-rewards.go b/rocketpool/api/node/claim-rewards.go index 5ae11ac29..d1d7f6618 100644 --- a/rocketpool/api/node/claim-rewards.go +++ b/rocketpool/api/node/claim-rewards.go @@ -12,11 +12,11 @@ import ( node131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/node" rewards131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/rewards" + "github.com/rocket-pool/smartnode/bindings/network" "github.com/rocket-pool/smartnode/bindings/node" "github.com/rocket-pool/smartnode/bindings/rewards" "github.com/rocket-pool/smartnode/bindings/rocketpool" - "github.com/rocket-pool/smartnode/bindings/settings/protocol" "github.com/rocket-pool/smartnode/bindings/types" "github.com/rocket-pool/smartnode/bindings/utils/eth" "github.com/rocket-pool/smartnode/shared/services" @@ -126,11 +126,6 @@ func getRewardsInfo(c *cli.Context) (*api.NodeGetRewardsInfoResponse, error) { response.RplStake, err = node131.GetNodeRPLStake(rp, nodeAccount.Address, nil) return err }) - wg.Go(func() error { - var err error - response.MinimumRplStake, err = node131.GetNodeMinimumRPLStake(rp, nodeAccount.Address, nil) - return err - }) } wg.Go(func() error { @@ -146,12 +141,6 @@ func getRewardsInfo(c *cli.Context) (*api.NodeGetRewardsInfoResponse, error) { if activeMinipools > 0 { var wg2 errgroup.Group - var minStakeFraction *big.Int - wg2.Go(func() error { - var err error - minStakeFraction, err = protocol.GetMinimumPerMinipoolStakeRaw(rp, nil) - return err - }) wg2.Go(func() error { var err error response.EthBorrowed, response.EthBorrowLimit, response.PendingBorrowAmount, err = rputils.CheckCollateral(saturnDeployed, rp, nodeAccount.Address, nil) @@ -163,13 +152,6 @@ func getRewardsInfo(c *cli.Context) (*api.NodeGetRewardsInfoResponse, error) { return nil, err } - // Calculate the *real* minimum, including the pending bond reductions - trueMinimumStake := big.NewInt(0).Add(response.EthBorrowed, response.PendingBorrowAmount) - trueMinimumStake.Mul(trueMinimumStake, minStakeFraction) - trueMinimumStake.Div(trueMinimumStake, response.RplPrice) - - response.MinimumRplStake = trueMinimumStake - response.BondedCollateralRatio = eth.WeiToEth(response.RplPrice) * eth.WeiToEth(response.RplStake) / (float64(activeMinipools)*32.0 - eth.WeiToEth(response.EthBorrowed) - eth.WeiToEth(response.PendingBorrowAmount)) response.BorrowedCollateralRatio = eth.WeiToEth(response.RplPrice) * eth.WeiToEth(response.RplStake) / (eth.WeiToEth(response.EthBorrowed) + eth.WeiToEth(response.PendingBorrowAmount)) } else { diff --git a/rocketpool/api/node/stake-rpl.go b/rocketpool/api/node/stake-rpl.go index c4b9a7719..6f6b112e9 100644 --- a/rocketpool/api/node/stake-rpl.go +++ b/rocketpool/api/node/stake-rpl.go @@ -5,15 +5,13 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - node131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/node" + "github.com/rocket-pool/smartnode/bindings/node" - "github.com/rocket-pool/smartnode/bindings/settings/protocol" "github.com/rocket-pool/smartnode/bindings/tokens" "github.com/rocket-pool/smartnode/bindings/utils" "github.com/urfave/cli" "github.com/rocket-pool/smartnode/shared/services" - updateCheck "github.com/rocket-pool/smartnode/shared/services/state" "github.com/rocket-pool/smartnode/shared/types/api" "github.com/rocket-pool/smartnode/shared/utils/eth1" ) @@ -42,12 +40,6 @@ func canNodeStakeRpl(c *cli.Context, amountWei *big.Int) (*api.CanNodeStakeRplRe return nil, err } - // Check if Saturn is already deployed - saturnDeployed, err := updateCheck.IsSaturnDeployed(rp, nil) - if err != nil { - return nil, err - } - // Check RPL balance rplBalance, err := tokens.GetRPLBalance(rp, nodeAccount.Address, nil) if err != nil { @@ -55,22 +47,6 @@ func canNodeStakeRpl(c *cli.Context, amountWei *big.Int) (*api.CanNodeStakeRplRe } response.InsufficientBalance = (amountWei.Cmp(rplBalance) > 0) - if !saturnDeployed { - // Get the min RPL stake - minRplStake, err := node131.GetNodeMinimumRPLStake(rp, nodeAccount.Address, nil) - if err != nil { - return nil, err - } - response.MinimumRplStake = minRplStake - } - - // Get the max stake fraction - maxStakeFraction, err := protocol.GetMaximumPerMinipoolStake(rp, nil) - if err != nil { - return nil, err - } - response.MaximumStakeFraction = maxStakeFraction - // Get gas estimates opts, err := w.GetNodeAccountTransactor() if err != nil { diff --git a/rocketpool/api/node/status.go b/rocketpool/api/node/status.go index b2555e9fd..f9f314d4a 100644 --- a/rocketpool/api/node/status.go +++ b/rocketpool/api/node/status.go @@ -24,6 +24,7 @@ import ( "golang.org/x/sync/errgroup" node131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/node" + protocol131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/protocol" mp "github.com/rocket-pool/smartnode/rocketpool/api/minipool" "github.com/rocket-pool/smartnode/rocketpool/api/pdao" "github.com/rocket-pool/smartnode/shared/services" @@ -242,7 +243,7 @@ func getStatus(c *cli.Context) (*api.NodeStatusResponse, error) { }) wg.Go(func() error { var err error - response.MaximumRplStake, err = node.GetNodeMaximumRPLStakeForMinipools(rp, nodeAccount.Address, nil) + response.RplStakeThreshold, err = node.GetNodeMinimumLegacyRPLStake(rp, nodeAccount.Address, nil) return err }) wg.Go(func() error { @@ -262,15 +263,9 @@ func getStatus(c *cli.Context) (*api.NodeStatusResponse, error) { response.NodeRPLLocked, err = node131.GetNodeRPLLocked(rp, nodeAccount.Address, nil) return err }) - - wg.Go(func() error { - var err error - response.MaximumRplStake, err = node131.GetNodeMaximumRPLStake(rp, nodeAccount.Address, nil) - return err - }) wg.Go(func() error { var err error - response.MinimumRplStake, err = node131.GetNodeMinimumRPLStake(rp, nodeAccount.Address, nil) + response.RplStakeThreshold, err = node131.GetNodeMaximumRPLStake(rp, nodeAccount.Address, nil) return err }) wg.Go(func() error { @@ -297,11 +292,21 @@ func getStatus(c *cli.Context) (*api.NodeStatusResponse, error) { return err }) - wg.Go(func() error { - var err error - response.MaximumStakeFraction, err = protocol.GetMaximumPerMinipoolStake(rp, nil) - return err - }) + // MinimumLegacyRPLStake and MaximumPerMinipoolStake are both used to compute the RPL amount that a node cannot fall under when withdrawing + if saturnDeployed { + wg.Go(func() error { + var err error + response.RplStakeThresholdFraction, err = protocol.GetMinimumLegacyRPLStake(rp, nil) + return err + }) + } else { + wg.Go(func() error { + var err error + response.RplStakeThresholdFraction, err = protocol131.GetMaximumPerMinipoolStake(rp, nil) + return err + }) + } + wg.Go(func() error { var err error response.EthBorrowed, response.EthBorrowedLimit, response.PendingBorrowAmount, err = rputils.CheckCollateral(saturnDeployed, rp, nodeAccount.Address, nil) @@ -476,39 +481,37 @@ func getStatus(c *cli.Context) (*api.NodeStatusResponse, error) { if totalActiveValidators > 0 { var wg2 errgroup.Group - var minStakeFraction *big.Int - var maxStakeFraction *big.Int - wg2.Go(func() error { - var err error - minStakeFraction, err = protocol.GetMinimumPerMinipoolStakeRaw(rp, nil) - return err - }) - wg2.Go(func() error { - var err error - maxStakeFraction, err = protocol.GetMaximumPerMinipoolStakeRaw(rp, nil) - return err - }) + var rplStakeThresholdFraction *big.Int + + // MinimumLegacyRPLStake and MaximumPerMinipoolStake are both used to compute the RPL amount that a node cannot fall under when withdrawing + if saturnDeployed { + wg2.Go(func() error { + var err error + rplStakeThresholdFraction, err = protocol.GetMinimumLegacyRPLStakeRaw(rp, nil) + return err + }) + } else { + wg2.Go(func() error { + var err error + rplStakeThresholdFraction, err = protocol131.GetMaximumPerMinipoolStakeRaw(rp, nil) + return err + }) + } // Wait for data if err := wg2.Wait(); err != nil { return nil, err } - // Calculate the *real* minimum, including the pending bond reductions - trueMinimumStake := big.NewInt(0).Add(response.EthBorrowed, response.PendingBorrowAmount) - trueMinimumStake.Mul(trueMinimumStake, minStakeFraction) - trueMinimumStake.Div(trueMinimumStake, rplPrice) - // Calculate the *real* maximum, including the pending bond reductions - trueMaximumStake := eth.EthToWei(32) - trueMaximumStake.Mul(trueMaximumStake, big.NewInt(int64(totalActiveValidators))) - trueMaximumStake.Sub(trueMaximumStake, response.EthBorrowed) - trueMaximumStake.Sub(trueMaximumStake, response.PendingBorrowAmount) // (32 * totalActiveValidators - ethBorrowed - pendingBorrow) - trueMaximumStake.Mul(trueMaximumStake, maxStakeFraction) - trueMaximumStake.Div(trueMaximumStake, rplPrice) + trueRplStakeThreshold := eth.EthToWei(32) + trueRplStakeThreshold.Mul(trueRplStakeThreshold, big.NewInt(int64(totalActiveValidators))) + trueRplStakeThreshold.Sub(trueRplStakeThreshold, response.EthBorrowed) + trueRplStakeThreshold.Sub(trueRplStakeThreshold, response.PendingBorrowAmount) // (32 * totalActiveValidators - ethBorrowed - pendingBorrow) + trueRplStakeThreshold.Mul(trueRplStakeThreshold, rplStakeThresholdFraction) + trueRplStakeThreshold.Div(trueRplStakeThreshold, rplPrice) - response.MinimumRplStake = trueMinimumStake - response.MaximumRplStake = trueMaximumStake + response.RplStakeThreshold = trueRplStakeThreshold response.BondedCollateralRatio = eth.WeiToEth(rplPrice) * eth.WeiToEth(response.RplStake) / (float64(totalActiveValidators)*32.0 - eth.WeiToEth(response.EthBorrowed) - eth.WeiToEth(response.PendingBorrowAmount)) response.BorrowedCollateralRatio = eth.WeiToEth(rplPrice) * eth.WeiToEth(response.RplStake) / (eth.WeiToEth(response.EthBorrowed) + eth.WeiToEth(response.PendingBorrowAmount)) @@ -519,15 +522,10 @@ func getStatus(c *cli.Context) (*api.NodeStatusResponse, error) { return nil, fmt.Errorf("error calculating eligible borrowed and bonded amounts: %w", err) } - // Calculate the "eligible real" minimum based on the Beacon Chain, including pending bond reductions - pendingTrueMinimumStake := big.NewInt(0).Mul(pendingEligibleBorrowedEth, minStakeFraction) - pendingTrueMinimumStake.Div(pendingTrueMinimumStake, rplPrice) - // Calculate the "eligible real" maximum based on the Beacon Chain, including the pending bond reductions - pendingTrueMaximumStake := big.NewInt(0).Mul(pendingEligibleBondedEth, maxStakeFraction) + pendingTrueMaximumStake := big.NewInt(0).Mul(pendingEligibleBondedEth, rplStakeThresholdFraction) pendingTrueMaximumStake.Div(pendingTrueMaximumStake, rplPrice) - response.PendingMinimumRplStake = pendingTrueMinimumStake response.PendingMaximumRplStake = pendingTrueMaximumStake pendingEligibleBondedEthFloat := eth.WeiToEth(pendingEligibleBondedEth) diff --git a/rocketpool/api/node/withdraw-legacy-rpl.go b/rocketpool/api/node/withdraw-legacy-rpl.go index 2de43ba58..b8bc9152b 100644 --- a/rocketpool/api/node/withdraw-legacy-rpl.go +++ b/rocketpool/api/node/withdraw-legacy-rpl.go @@ -44,7 +44,7 @@ func canNodeUnstakeLegacyRpl(c *cli.Context, amountWei *big.Int) (*api.CanNodeUn nodeRplLocked := big.NewInt(0) var isRPLWithdrawalAddressSet bool var rplWithdrawalAddress common.Address - maximumRplStake := big.NewInt(0) + rplStakeThreshold := big.NewInt(0) // Get RPL stake wg.Go(func() error { @@ -74,10 +74,10 @@ func canNodeUnstakeLegacyRpl(c *cli.Context, amountWei *big.Int) (*api.CanNodeUn return err }) - // Get the minimum requirement for minipool bond + // Get the minimum amount of legacy staked RPL a node must have after unstaking wg.Go(func() error { var err error - maximumRplStake, err = node.GetNodeMaximumRPLStakeForMinipools(rp, nodeAccount.Address, nil) + rplStakeThreshold, err = node.GetNodeMinimumLegacyRPLStake(rp, nodeAccount.Address, nil) return err }) @@ -105,7 +105,7 @@ func canNodeUnstakeLegacyRpl(c *cli.Context, amountWei *big.Int) (*api.CanNodeUn remainingLegacyRplStake.Sub(&remainingLegacyRplStake, nodeRplLocked) response.InsufficientBalance = (amountWei.Cmp(legacyRplStake) > 0) response.HasDifferentRPLWithdrawalAddress = (isRPLWithdrawalAddressSet && nodeAccount.Address != rplWithdrawalAddress) - response.BelowMaxRPLStake = (remainingLegacyRplStake.Cmp(maximumRplStake) < 0) + response.BelowMaxRPLStake = (remainingLegacyRplStake.Cmp(rplStakeThreshold) < 0) // Update & return response response.CanUnstake = !(response.InsufficientBalance || response.HasDifferentRPLWithdrawalAddress) diff --git a/rocketpool/api/pdao/get-settings.go b/rocketpool/api/pdao/get-settings.go index c88d1521d..39af07c0d 100644 --- a/rocketpool/api/pdao/get-settings.go +++ b/rocketpool/api/pdao/get-settings.go @@ -7,6 +7,7 @@ import ( "github.com/urfave/cli" "golang.org/x/sync/errgroup" + protocol131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/protocol" "github.com/rocket-pool/smartnode/shared/services" "github.com/rocket-pool/smartnode/shared/services/state" "github.com/rocket-pool/smartnode/shared/types/api" @@ -119,6 +120,12 @@ func getSettings(c *cli.Context) (*api.GetPDAOSettingsResponse, error) { return err }) + wg.Go(func() error { + var err error + response.Node.MinimumLegacyRplStake, err = protocol.GetMinimumLegacyRPLStakeRaw(rp, nil) + return err + }) + // === Megapool === wg.Go(func() error { @@ -412,17 +419,20 @@ func getSettings(c *cli.Context) (*api.GetPDAOSettingsResponse, error) { return err }) - wg.Go(func() error { - var err error - response.Node.MinimumPerMinipoolStake, err = protocol.GetMinimumPerMinipoolStakeRaw(rp, nil) - return err - }) + // In Saturn, these two bindings are deprecated in favor of 'GetMinimumLegacyRPLStake' + if !response.SaturnDeployed { + wg.Go(func() error { + var err error + response.Node.MinimumPerMinipoolStake, err = protocol131.GetMinimumPerMinipoolStakeRaw(rp, nil) + return err + }) - wg.Go(func() error { - var err error - response.Node.MaximumPerMinipoolStake, err = protocol.GetMaximumPerMinipoolStakeRaw(rp, nil) - return err - }) + wg.Go(func() error { + var err error + response.Node.MaximumPerMinipoolStake, err = protocol131.GetMaximumPerMinipoolStakeRaw(rp, nil) + return err + }) + } // === Proposals === diff --git a/rocketpool/api/pdao/propose-settings.go b/rocketpool/api/pdao/propose-settings.go index 7a989667e..47314c3ca 100644 --- a/rocketpool/api/pdao/propose-settings.go +++ b/rocketpool/api/pdao/propose-settings.go @@ -6,6 +6,8 @@ import ( "github.com/ethereum/go-ethereum/common" node131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/node" + protocol131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/protocol" + "github.com/rocket-pool/smartnode/bindings/node" "github.com/rocket-pool/smartnode/bindings/rocketpool" "github.com/rocket-pool/smartnode/bindings/settings/protocol" @@ -628,28 +630,37 @@ func canProposeSetting(c *cli.Context, contractName string, settingName string, if err != nil { return nil, fmt.Errorf("error estimating gas for proposing VacantMinipoolsEnabled: %w", err) } - // MinimumPerMinipoolStake - case protocol.MinimumPerMinipoolStakeSettingPath: + case protocol131.MinimumPerMinipoolStakeSettingPath: newValue, err := cliutils.ValidateBigInt(valueName, value) if err != nil { return nil, err } - response.GasInfo, err = protocol.EstimateProposeMinimumPerMinipoolStakeGas(rp, newValue, blockNumber, pollard, opts) + response.GasInfo, err = protocol131.EstimateProposeMinimumPerMinipoolStakeGas(rp, newValue, blockNumber, pollard, opts) if err != nil { return nil, fmt.Errorf("error estimating gas for proposing MinimumPerMinipoolStake: %w", err) } - // MaximumPerMinipoolStake - case protocol.MaximumPerMinipoolStakeSettingPath: + case protocol131.MaximumPerMinipoolStakeSettingPath: newValue, err := cliutils.ValidateBigInt(valueName, value) if err != nil { return nil, err } - response.GasInfo, err = protocol.EstimateProposeMaximumPerMinipoolStakeGas(rp, newValue, blockNumber, pollard, opts) + response.GasInfo, err = protocol131.EstimateProposeMaximumPerMinipoolStakeGas(rp, newValue, blockNumber, pollard, opts) if err != nil { return nil, fmt.Errorf("error estimating gas for proposing MaximumPerMinipoolStake: %w", err) } + // MinimumLegacyRplStake + case protocol.MinimumLegacyRplStakePath: + newValue, err := cliutils.ValidateBigInt(valueName, value) + if err != nil { + return nil, err + } + response.GasInfo, err = protocol.EstimateProposeMinimumLecacyRPLStakeGas(rp, newValue, blockNumber, pollard, opts) + if err != nil { + return nil, fmt.Errorf("error estimating gas for proposing MinimumLegacyRplStake: %w", err) + } + // ReducedBond case protocol.ReducedBondSettingPath: newValue, err := cliutils.ValidateBigInt(valueName, value) @@ -1456,28 +1467,36 @@ func proposeSetting(c *cli.Context, contractName string, settingName string, val if err != nil { return nil, fmt.Errorf("error proposing VacantMinipoolsEnabled: %w", err) } - // MinimumPerMinipoolStake - case protocol.MinimumPerMinipoolStakeSettingPath: + case protocol131.MinimumPerMinipoolStakeSettingPath: newValue, err := cliutils.ValidateBigInt(valueName, value) if err != nil { return nil, err } - proposalID, hash, err = protocol.ProposeMinimumPerMinipoolStake(rp, newValue, blockNumber, pollard, opts) + proposalID, hash, err = protocol131.ProposeMinimumPerMinipoolStake(rp, newValue, blockNumber, pollard, opts) if err != nil { return nil, fmt.Errorf("error proposing MinimumPerMinipoolStake: %w", err) } - // MaximumPerMinipoolStake - case protocol.MaximumPerMinipoolStakeSettingPath: + case protocol131.MaximumPerMinipoolStakeSettingPath: newValue, err := cliutils.ValidateBigInt(valueName, value) if err != nil { return nil, err } - proposalID, hash, err = protocol.ProposeMaximumPerMinipoolStake(rp, newValue, blockNumber, pollard, opts) + proposalID, hash, err = protocol131.ProposeMaximumPerMinipoolStake(rp, newValue, blockNumber, pollard, opts) if err != nil { return nil, fmt.Errorf("error proposing MaximumPerMinipoolStake: %w", err) } + // MinimumLegacyRplStake + case protocol.MinimumLegacyRplStakePath: + newValue, err := cliutils.ValidateBigInt(valueName, value) + if err != nil { + return nil, err + } + proposalID, hash, err = protocol.ProposeMinimumLecacyRPLStake(rp, newValue, blockNumber, pollard, opts) + if err != nil { + return nil, fmt.Errorf("error proposing MinimumLegacyRplStake: %w", err) + } // ReducedBond case protocol.ReducedBondSettingPath: newValue, err := cliutils.ValidateBigInt(valueName, value) diff --git a/shared/services/rocketpool/node.go b/shared/services/rocketpool/node.go index 6e40a9623..9345ba488 100644 --- a/shared/services/rocketpool/node.go +++ b/shared/services/rocketpool/node.go @@ -31,7 +31,7 @@ func (c *Client) NodeStatus() (api.NodeStatusResponse, error) { utils.ZeroIfNil(&response.RplStake) utils.ZeroIfNil(&response.RplStakeMegapool) utils.ZeroIfNil(&response.RplStakeLegacy) - utils.ZeroIfNil(&response.MaximumRplStake) + utils.ZeroIfNil(&response.RplStakeThreshold) utils.ZeroIfNil(&response.AccountBalances.ETH) utils.ZeroIfNil(&response.AccountBalances.RPL) utils.ZeroIfNil(&response.AccountBalances.RETH) diff --git a/shared/services/state/network-state.go b/shared/services/state/network-state.go index b6120fd97..bc49ac1de 100644 --- a/shared/services/state/network-state.go +++ b/shared/services/state/network-state.go @@ -218,21 +218,21 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64) (*NetworkSta if err != nil { return nil, fmt.Errorf("error getting network details: %w", err) } - m.logLine("1/6 - Retrieved network details (%s so far)", time.Since(start)) + m.logLine("1/7 - Retrieved network details (%s so far)", time.Since(start)) // Node details state.NodeDetails, err = rpstate.GetAllNativeNodeDetails(m.rp, contracts) if err != nil { return nil, fmt.Errorf("error getting all node details: %w", err) } - m.logLine("2/6 - Retrieved node details (%s so far)", time.Since(start)) + m.logLine("2/7 - Retrieved node details (%s so far)", time.Since(start)) // Minipool details state.MinipoolDetails, err = rpstate.GetAllNativeMinipoolDetails(m.rp, contracts) if err != nil { return nil, fmt.Errorf("error getting all minipool details: %w", err) } - m.logLine("3/6 - Retrieved minipool details (%s so far)", time.Since(start)) + m.logLine("3/7 - Retrieved minipool details (%s so far)", time.Since(start)) // Create the node lookup for i, details := range state.NodeDetails { @@ -314,6 +314,7 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64) (*NetworkSta } } } + m.logLine("4/7 - Retrieved megapool validator details (%s so far)", time.Since(start)) // Calculate avg node fees and distributor shares for _, details := range state.NodeDetails { @@ -325,7 +326,7 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64) (*NetworkSta if err != nil { return nil, fmt.Errorf("error getting Oracle DAO details: %w", err) } - m.logLine("4/6 - Retrieved Oracle DAO details (%s so far)", time.Since(start)) + m.logLine("5/7 - Retrieved Oracle DAO details (%s so far)", time.Since(start)) // Get the validator stats from Beacon statusMap, err := m.bc.GetValidatorStatuses(pubkeys, &beacon.ValidatorStatusOptions{ @@ -335,7 +336,7 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64) (*NetworkSta return nil, err } state.MinipoolValidatorDetails = statusMap - m.logLine("5/6 - Retrieved validator details (total time: %s)", time.Since(start)) + m.logLine("6/7 - Retrieved validator details (total time: %s)", time.Since(start)) // Get the complete node and user shares mpds := make([]*rpstate.NativeMinipoolDetails, len(state.MinipoolDetails)) @@ -354,14 +355,14 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64) (*NetworkSta return nil, err } state.MinipoolValidatorDetails = statusMap - m.logLine("6/6 - Calculated complete node and user balance shares (total time: %s)", time.Since(start)) + m.logLine("7/7 - Calculated complete node and user balance shares (total time: %s)", time.Since(start)) return state, nil } // Creates a snapshot of the Rocket Pool network, but only for a single node func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeAddress common.Address) (*NetworkState, error) { - steps := 5 + steps := 7 // Get the execution block for the given slot beaconBlock, exists, err := m.bc.GetBeaconBlock(fmt.Sprintf("%d", slotNumber)) @@ -497,6 +498,68 @@ func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeA m.logLine("%d/%d - Retrieved Protocol DAO proposals (total time: %s)", currentStep, steps, time.Since(start)) currentStep++ + if isSaturnDeployed { + state.MegapoolValidatorGlobalIndex, err = rpstate.GetAllMegapoolValidators(m.rp, contracts) + if err != nil { + return nil, fmt.Errorf("error getting all megapool validator details: %w", err) + } + megapoolValidatorPubkeys := make([]types.ValidatorPubkey, 0, len(state.MegapoolValidatorGlobalIndex)) + // Iterate over the megapool validators to add their pubkey to the list of pubkeys + megapoolAddressMap := make(map[common.Address][]types.ValidatorPubkey) + megapoolValidatorInfo := make(map[types.ValidatorPubkey]*megapool.ValidatorInfoFromGlobalIndex) + for _, validator := range state.MegapoolValidatorGlobalIndex { + // Add the megapool address to a set + if len(validator.Pubkey) > 0 { // TODO CHECK validators without a pubkey + megapoolAddressMap[validator.MegapoolAddress] = append(megapoolAddressMap[validator.MegapoolAddress], types.ValidatorPubkey(validator.Pubkey)) + megapoolValidatorPubkeys = append(megapoolValidatorPubkeys, types.ValidatorPubkey(validator.Pubkey)) + megapoolValidatorInfo[types.ValidatorPubkey(validator.Pubkey)] = &validator + } + } + state.MegapoolToPubkeysMap = megapoolAddressMap + statusMap, err := m.bc.GetValidatorStatuses(megapoolValidatorPubkeys, &beacon.ValidatorStatusOptions{ + Slot: &slotNumber, + }) + if err != nil { + return nil, err + } + state.MegapoolValidatorDetails = statusMap + state.MegapoolValidatorInfo = megapoolValidatorInfo + + // initialize state.MegapoolDetails + state.MegapoolDetails = make(map[common.Address]rpstate.NativeMegapoolDetails) + // Sync + var wg errgroup.Group + // Iterate the maps and query megapool details + for megapoolAddress := range megapoolAddressMap { + + megapoolAddress := megapoolAddress + wg.Go(func() error { + + // Load the megapool + mp, err := megapool.NewMegaPoolV1(m.rp, megapoolAddress, opts) + if err != nil { + return err + } + nodeAddress, err := mp.GetNodeAddress(opts) + if err != nil { + return err + } + megapoolDetails, err := rpstate.GetNodeMegapoolDetails(m.rp, nodeAddress) + if err != nil { + return err + } + + state.MegapoolDetails[megapoolAddress] = megapoolDetails + return nil + }) + if err := wg.Wait(); err != nil { + return nil, fmt.Errorf("error getting all megapool details: %w", err) + } + } + } + m.logLine("%d/%d - Retrieved megapool validator details (total time: %s)", currentStep, steps, time.Since(start)) + currentStep++ + return state, nil } diff --git a/shared/types/api/node.go b/shared/types/api/node.go index 31209ceb4..091efce14 100644 --- a/shared/types/api/node.go +++ b/shared/types/api/node.go @@ -40,9 +40,8 @@ type NodeStatusResponse struct { RplStake *big.Int `json:"rplStake"` RplStakeMegapool *big.Int `json:"rplStakeMegapool"` RplStakeLegacy *big.Int `json:"rplStakeLegacy"` - MaximumRplStake *big.Int `json:"maximumRplStake"` - MinimumRplStake *big.Int `json:"minimumRplStake"` - MaximumStakeFraction float64 `json:"maximumStakeFraction"` + RplStakeThreshold *big.Int `json:"maximumRplStake"` + RplStakeThresholdFraction float64 `json:"maximumStakeFraction"` BorrowedCollateralRatio float64 `json:"borrowedCollateralRatio"` BondedCollateralRatio float64 `json:"bondedCollateralRatio"` PendingMinimumRplStake *big.Int `json:"pendingMinimumRplStake"` @@ -296,14 +295,12 @@ type NodeSwapRplAllowanceResponse struct { } type CanNodeStakeRplResponse struct { - Status string `json:"status"` - Error string `json:"error"` - CanStake bool `json:"canStake"` - InsufficientBalance bool `json:"insufficientBalance"` - MinimumRplStake *big.Int `json:"minimumRplStake"` - InConsensus bool `json:"inConsensus"` - MaximumStakeFraction float64 `json:"maximumStakeFraction"` - GasInfo rocketpool.GasInfo `json:"gasInfo"` + Status string `json:"status"` + Error string `json:"error"` + CanStake bool `json:"canStake"` + InsufficientBalance bool `json:"insufficientBalance"` + InConsensus bool `json:"inConsensus"` + GasInfo rocketpool.GasInfo `json:"gasInfo"` } type NodeStakeRplApproveGasResponse struct { Status string `json:"status"` @@ -612,7 +609,6 @@ type NodeGetRewardsInfoResponse struct { RplStake *big.Int `json:"rplStake"` RplPrice *big.Int `json:"rplPrice"` ActiveMinipools int `json:"activeMinipools"` - MinimumRplStake *big.Int `json:"minimumRplStake"` EthBorrowed *big.Int `json:"ethBorrowed"` EthBorrowLimit *big.Int `json:"ethBorrowLimit"` PendingBorrowAmount *big.Int `json:"pendingBorrowAmount"` diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index c8da55c2e..8e8047672 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -145,6 +145,7 @@ type GetPDAOSettingsResponse struct { AreVacantMinipoolsEnabled bool `json:"areVacantMinipoolsEnabled"` MinimumPerMinipoolStake *big.Int `json:"minimumPerMinipoolStake"` MaximumPerMinipoolStake *big.Int `json:"maximumPerMinipoolStake"` + MinimumLegacyRplStake *big.Int `json:"minimumLegacyRplStake"` ReducedBond float64 `json:"reducedBond"` NodeUnstakingPeriod time.Duration `json:"nodeUnstakingPeriod"` } `json:"node"`