Skip to content

Commit

Permalink
Merge pull request #694 from rocket-pool/v1-deposit-saturn0
Browse files Browse the repository at this point in the history
V1 Improved `node deposit` and `node stake-rpl` flow
  • Loading branch information
0xfornax authored Nov 6, 2024
2 parents 8886afb + 2ba93ae commit c84d442
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 96 deletions.
34 changes: 25 additions & 9 deletions rocketpool-cli/node/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package node

import (
"fmt"
"strconv"
"strings"

"github.com/urfave/cli"

Expand Down Expand Up @@ -320,7 +322,7 @@ func RegisterCommands(app *cli.App, name string, aliases []string) {
Flags: []cli.Flag{
cli.StringFlag{
Name: "amount, a",
Usage: "The amount of RPL to stake (also accepts 'min8' for 8-ETH minipools, or 'all' for all of your RPL)",
Usage: "The amount of RPL to stake (also accepts custom percentages for 8-ETH minipools (eg. 3% of borrowed ETH as RPL), or 'all' for all of your RPL)",
},
cli.BoolFlag{
Name: "yes, y",
Expand All @@ -339,14 +341,20 @@ func RegisterCommands(app *cli.App, name string, aliases []string) {
}

// Validate flags
if c.String("amount") != "" &&
c.String("amount") != "min8" &&
c.String("amount") != "max8" &&
c.String("amount") != "min16" &&
c.String("amount") != "max16" &&
c.String("amount") != "all" {
if _, err := cliutils.ValidatePositiveEthAmount("stake amount", c.String("amount")); err != nil {
return err
amount := c.String("amount")
if amount != "" {
if strings.HasSuffix(amount, "%") {
trimmedAmount := strings.TrimSuffix(amount, "%")
stakePercent, err := strconv.ParseFloat(trimmedAmount, 64)
if err != nil || stakePercent <= 0 {
return fmt.Errorf("invalid percentage value: %s", amount)
}

} else if amount != "all" {
// Validate it as a positive ETH amount if it's not a percentage or "all"
if _, err := cliutils.ValidatePositiveEthAmount("stake amount", amount); err != nil {
return err
}
}
}

Expand Down Expand Up @@ -499,10 +507,18 @@ func RegisterCommands(app *cli.App, name string, aliases []string) {
Usage: "Make a deposit and create a minipool",
UsageText: "rocketpool node deposit [options]",
Flags: []cli.Flag{
cli.StringFlag{
Name: "amount, a",
Usage: "The amount of ETH to deposit (8 or 16)",
},
cli.StringFlag{
Name: "max-slippage, s",
Usage: "The maximum acceptable slippage in node commission rate for the deposit (or 'auto'). Only relevant when the commission rate is not fixed.",
},
cli.BoolFlag{
Name: "yes, y",
Usage: "Automatically confirm deposit",
},
cli.StringFlag{
Name: "salt, l",
Usage: "An optional seed to use when generating the new minipool's address. Use this if you want it to have a custom vanity address.",
Expand Down
18 changes: 5 additions & 13 deletions rocketpool-cli/node/create-vacant-minipool.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func createVacantMinipool(c *cli.Context, pubkey types.ValidatorPubkey) error {
}

// Post a warning about fee distribution
if !(c.Bool("yes") || cliutils.Confirm(fmt.Sprintf("%sNOTE: by creating a new minipool, your node will automatically claim and distribute any balance you have in your fee distributor contract. If you don't want to claim your balance at this time, you should not create a new minipool.%s\nWould you like to continue?", colorYellow, colorReset))) {
if !(c.Bool("yes") || cliutils.Confirm(fmt.Sprintf("%sNOTE: By creating a new minipool, your node will automatically claim and distribute any balance you have in your fee distributor contract. If you don't want to claim your balance at this time, you should not create a new minipool.%s\nWould you like to continue?", colorYellow, colorReset))) {
fmt.Println("Cancelled.")
return nil
}
Expand All @@ -75,19 +75,11 @@ func createVacantMinipool(c *cli.Context, pubkey types.ValidatorPubkey) error {
amount = depositAmount

} else {

// Get deposit amount options
amountOptions := []string{
"8 ETH",
}

// Prompt for amount
selected, _ := cliutils.Select("Please choose an amount of ETH you want to use as your deposit for the new minipool (this will become your share of the balance, and the remainder will become the pool stakers' share):", amountOptions)
switch selected {
case 0:
amount = 8
if !(c.Bool("yes") || cliutils.Confirm(fmt.Sprintf("%sNOTE: Your new minipool will use an 8 ETH deposit (this will become your share of the balance, and the remainder will become the pool stakers' share):%s\nWould you like to continue?", colorYellow, colorReset))) {
fmt.Println("Cancelled.")
return nil
}

amount = 8
}

amountWei := eth.EthToWei(amount)
Expand Down
25 changes: 14 additions & 11 deletions rocketpool-cli/node/deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
// Config
const (
defaultMaxNodeFeeSlippage = 0.01 // 1% below current network fee
depositWarningMessage = "NOTE: by creating a new minipool, your node will automatically initialize voting power to itself. If you would like to delegate your on-chain voting power, you should run the command `rocketpool pdao initialize-voting` before creating a new minipool."
depositWarningMessage = "NOTE: By creating a new minipool, your node will automatically initialize voting power to itself. If you would like to delegate your on-chain voting power, you should run the command `rocketpool pdao initialize-voting` before creating a new minipool."
)

func nodeDeposit(c *cli.Context) error {
Expand Down Expand Up @@ -92,23 +92,26 @@ func nodeDeposit(c *cli.Context) error {
}

// Post a warning about fee distribution
if !(c.Bool("yes") || cliutils.Confirm(fmt.Sprintf("%sNOTE: by creating a new minipool, your node will automatically claim and distribute any balance you have in your fee distributor contract. If you don't want to claim your balance at this time, you should not create a new minipool.%s\nWould you like to continue?", colorYellow, colorReset))) {
if !(c.Bool("yes") || cliutils.Confirm(fmt.Sprintf("%sNOTE: By creating a new minipool, your node will automatically claim and distribute any balance you have in your fee distributor contract. If you don't want to claim your balance at this time, you should not create a new minipool.%s\nWould you like to continue?", colorYellow, colorReset))) {
fmt.Println("Cancelled.")
return nil
}

// Get deposit amount
var amount float64

// Get deposit amount options
amountOptions := []string{
"8 ETH",
}

// Prompt for amount
selected, _ := cliutils.Select("Please choose an amount of ETH to deposit:", amountOptions)
switch selected {
case 0:
if c.String("amount") != "" {
// Parse amount
depositAmount, err := strconv.ParseFloat(c.String("amount"), 64)
if err != nil {
return fmt.Errorf("Invalid deposit amount '%s': %w", c.String("amount"), err)
}
amount = depositAmount
} else {
if !(c.Bool("yes") || cliutils.Confirm(fmt.Sprintf("%sNOTE: You are about to make an 8 ETH deposit.%s\nWould you like to continue?", colorYellow, colorReset))) {
fmt.Println("Cancelled.")
return nil
}
amount = 8
}

Expand Down
76 changes: 49 additions & 27 deletions rocketpool-cli/node/stake-rpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"math/big"
"strconv"
"strings"

"github.com/rocket-pool/rocketpool-go/utils/eth"
"github.com/urfave/cli"
Expand All @@ -16,7 +17,7 @@ import (

// Config
const (
stakeRPLWarningMessage = "NOTE: by staking RPL, your node will automatically initialize voting power to itself. If you would like to delegate your on-chain voting power, you should run the command `rocketpool pdao initialize-voting` before staking RPL."
stakeRPLWarningMessage = "NOTE: By staking RPL, your node will automatically initialize voting power to itself. If you would like to delegate your on-chain voting power, you should run the command `rocketpool pdao initialize-voting` before staking RPL."
)

func nodeStakeRpl(c *cli.Context) error {
Expand Down Expand Up @@ -161,24 +162,24 @@ func nodeStakeRpl(c *cli.Context) error {

}

// Get stake amount
// Get RPL price
rplPrice, err := rp.RplPrice()
if err != nil {
return err
}
var amountWei *big.Int
if c.String("amount") == "min8" {
var stakePercent float64

// Set amount to min per 8 ETH minipool RPL stake
rplPrice, err := rp.RplPrice()
if err != nil {
return err
}
amountWei = rplPrice.MinPer8EthMinipoolRplStake
// Amount flag custom percentage input
if strings.HasSuffix(c.String("amount"), "%") {
fmt.Sscanf(c.String("amount"), "%f%%", &stakePercent)
amountWei = rplStakeForLEB8(eth.EthToWei(stakePercent/100), rplPrice.RplPrice)

} else if c.String("amount") == "all" {

// Set amount to node's entire RPL balance
amountWei = &rplBalance

} else if c.String("amount") != "" {

// Parse amount
stakeAmount, err := strconv.ParseFloat(c.String("amount"), 64)
if err != nil {
Expand All @@ -187,38 +188,48 @@ func nodeStakeRpl(c *cli.Context) error {
amountWei = eth.EthToWei(stakeAmount)

} else {

// Get min/max per minipool RPL stake amounts
rplPrice, err := rp.RplPrice()
if err != nil {
return err
}
minAmount8 := rplPrice.MinPer8EthMinipoolRplStake
// Get the RPL stake amounts for 5,10,15% borrowed ETH per LEB8
fivePercentBorrowedPerMinipool := new(big.Int)
fivePercentBorrowedPerMinipool.SetString("50000000000000000", 10)
fivePercentBorrowedRplStake := rplStakeForLEB8(fivePercentBorrowedPerMinipool, rplPrice.RplPrice)
tenPercentBorrowedRplStake := new(big.Int).Mul(fivePercentBorrowedRplStake, big.NewInt(2))
fifteenPercentBorrowedRplStake := new(big.Int).Mul(fivePercentBorrowedRplStake, big.NewInt(3))

// Prompt for amount option
amountOptions := []string{
fmt.Sprintf("The minimum minipool stake amount for an 8-ETH minipool (%.6f RPL)?", math.RoundUp(eth.WeiToEth(minAmount8), 6)),
fmt.Sprintf("5%% of borrowed ETH (%.6f RPL) for one minipool?", math.RoundUp(eth.WeiToEth(fivePercentBorrowedRplStake), 6)),
fmt.Sprintf("10%% of borrowed ETH (%.6f RPL) for one minipool?", math.RoundUp(eth.WeiToEth(tenPercentBorrowedRplStake), 6)),
fmt.Sprintf("15%% of borrowed ETH (%.6f RPL) for one minipool?", math.RoundUp(eth.WeiToEth(fifteenPercentBorrowedRplStake), 6)),
fmt.Sprintf("Your entire RPL balance (%.6f RPL)?", math.RoundDown(eth.WeiToEth(&rplBalance), 6)),
"A custom amount",
}
selected, _ := cliutils.Select("Please choose an amount of RPL to stake:", amountOptions)

switch selected {
case 0:
amountWei = minAmount8
amountWei = fivePercentBorrowedRplStake
case 1:
amountWei = tenPercentBorrowedRplStake
case 2:
amountWei = fifteenPercentBorrowedRplStake
case 3:
amountWei = &rplBalance
}

// Prompt for custom amount
// Prompt for custom amount or percentage
if amountWei == nil {
inputAmount := cliutils.Prompt("Please enter an amount of RPL to stake:", "^\\d+(\\.\\d+)?$", "Invalid amount")
stakeAmount, err := strconv.ParseFloat(inputAmount, 64)
if err != nil {
return fmt.Errorf("Invalid stake amount '%s': %w", inputAmount, err)
inputAmountOrPercent := cliutils.Prompt("Please enter an amount of RPL or percentage of borrowed ETH to stake. (e.g '50' for 50 RPL or '5%' for 5% borrowed ETH as RPL):", "^(0|[1-9]\\d*)(\\.\\d+)?%?$", "Invalid amount")
if strings.HasSuffix(inputAmountOrPercent, "%") {
fmt.Sscanf(inputAmountOrPercent, "%f%%", &stakePercent)
amountWei = rplStakeForLEB8(eth.EthToWei(stakePercent/100), rplPrice.RplPrice)
} else {
stakeAmount, err := strconv.ParseFloat(inputAmountOrPercent, 64)
if err != nil {
return fmt.Errorf("Invalid stake amount '%s': %w", inputAmountOrPercent, err)
}
amountWei = eth.EthToWei(stakeAmount)
}
amountWei = eth.EthToWei(stakeAmount)
}

}

// Check allowance
Expand Down Expand Up @@ -323,3 +334,14 @@ func nodeStakeRpl(c *cli.Context) error {
return nil

}

func rplStakeForLEB8(borrowedPerMinipool *big.Int, rplPrice *big.Int) *big.Int {
percentBorrowedRplStake := big.NewInt(0)
percentBorrowedRplStake.Mul(eth.EthToWei(24), borrowedPerMinipool)
percentBorrowedRplStake.Div(percentBorrowedRplStake, rplPrice)
percentBorrowedRplStake.Add(percentBorrowedRplStake, big.NewInt(1))
amountWei := percentBorrowedRplStake

return amountWei

}
24 changes: 0 additions & 24 deletions rocketpool/api/network/rpl-price.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"math/big"

"github.com/rocket-pool/rocketpool-go/network"
"github.com/rocket-pool/rocketpool-go/settings/protocol"
"github.com/rocket-pool/rocketpool-go/utils/eth"
"github.com/urfave/cli"
"golang.org/x/sync/errgroup"

Expand All @@ -30,9 +28,6 @@ func getRplPrice(c *cli.Context) (*api.RplPriceResponse, error) {
// Data
var wg errgroup.Group
var rplPrice *big.Int
_24Eth := eth.EthToWei(24)
_16Eth := eth.EthToWei(16)
var minPerMinipoolStake *big.Int

// Get RPL price set block
wg.Go(func() error {
Expand All @@ -49,31 +44,12 @@ func getRplPrice(c *cli.Context) (*api.RplPriceResponse, error) {
rplPrice, err = network.GetRPLPrice(rp, nil)
return err
})
wg.Go(func() error {
var err error
minPerMinipoolStake, err = protocol.GetMinimumPerMinipoolStakeRaw(rp, nil)
return err
})

// Wait for data
if err := wg.Wait(); err != nil {
return nil, err
}

// Min for LEB8s
minPer8EthMinipoolRplStake := big.NewInt(0)
minPer8EthMinipoolRplStake.Mul(_24Eth, minPerMinipoolStake) // Min is 10% of borrowed (24 ETH)
minPer8EthMinipoolRplStake.Div(minPer8EthMinipoolRplStake, rplPrice)
minPer8EthMinipoolRplStake.Add(minPer8EthMinipoolRplStake, big.NewInt(1))
response.MinPer8EthMinipoolRplStake = minPer8EthMinipoolRplStake

// Min for 16s
minPer16EthMinipoolRplStake := big.NewInt(0)
minPer16EthMinipoolRplStake.Mul(_16Eth, minPerMinipoolStake) // Min is 10% of borrowed (16 ETH)
minPer16EthMinipoolRplStake.Div(minPer16EthMinipoolRplStake, rplPrice)
minPer16EthMinipoolRplStake.Add(minPer16EthMinipoolRplStake, big.NewInt(1))
response.MinPer16EthMinipoolRplStake = minPer16EthMinipoolRplStake

// Update & return response
response.RplPrice = rplPrice
return &response, nil
Expand Down
6 changes: 0 additions & 6 deletions shared/services/rocketpool/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ func (c *Client) RplPrice() (api.RplPriceResponse, error) {
if response.RplPrice == nil {
response.RplPrice = big.NewInt(0)
}
if response.MinPer8EthMinipoolRplStake == nil {
response.MinPer8EthMinipoolRplStake = big.NewInt(0)
}
if response.MinPer16EthMinipoolRplStake == nil {
response.MinPer16EthMinipoolRplStake = big.NewInt(0)
}
return response, nil
}

Expand Down
10 changes: 4 additions & 6 deletions shared/types/api/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ type NodeFeeResponse struct {
}

type RplPriceResponse struct {
Status string `json:"status"`
Error string `json:"error"`
RplPrice *big.Int `json:"rplPrice"`
RplPriceBlock uint64 `json:"rplPriceBlock"`
MinPer8EthMinipoolRplStake *big.Int `json:"minPer8EthMinipoolRplStake"`
MinPer16EthMinipoolRplStake *big.Int `json:"minPer16EthMinipoolRplStake"`
Status string `json:"status"`
Error string `json:"error"`
RplPrice *big.Int `json:"rplPrice"`
RplPriceBlock uint64 `json:"rplPriceBlock"`
}

type NetworkStatsResponse struct {
Expand Down

0 comments on commit c84d442

Please sign in to comment.