Skip to content

Commit 3c9fe83

Browse files
committed
Add the feature to make multiple deposits on a single tx
1 parent 06fb318 commit 3c9fe83

File tree

9 files changed

+679
-84
lines changed

9 files changed

+679
-84
lines changed

bindings/node/deposit.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ import (
1414
"github.com/rocket-pool/smartnode/bindings/utils/eth"
1515
)
1616

17+
type NodeDeposit struct {
18+
BondAmount *big.Int `json:"bondAmount"`
19+
UseExpressTicket bool `json:"useExpressTicket"`
20+
ValidatorPubkey []byte `json:"validatorPubkey"`
21+
ValidatorSignature []byte `json:"validatorSignature"`
22+
DepositDataRoot common.Hash `json:"depositDataRoot"`
23+
}
24+
25+
type Deposits []NodeDeposit
26+
1727
// Estimate the gas of Deposit
1828
func EstimateDepositGas(rp *rocketpool.RocketPool, bondAmount *big.Int, useExpressTicket bool, validatorPubkey rptypes.ValidatorPubkey, validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
1929
rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
@@ -36,6 +46,28 @@ func Deposit(rp *rocketpool.RocketPool, bondAmount *big.Int, useExpressTicket bo
3646
return tx, nil
3747
}
3848

49+
// Estimate the gas of DepositMulti
50+
func EstimateDepositMultiGas(rp *rocketpool.RocketPool, deposits Deposits, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
51+
rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
52+
if err != nil {
53+
return rocketpool.GasInfo{}, err
54+
}
55+
return rocketNodeDeposit.GetTransactionGasInfo(opts, "depositMulti", deposits)
56+
}
57+
58+
// Make multiple node deposits
59+
func DepositMulti(rp *rocketpool.RocketPool, deposits Deposits, opts *bind.TransactOpts) (*types.Transaction, error) {
60+
rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
61+
if err != nil {
62+
return nil, err
63+
}
64+
tx, err := rocketNodeDeposit.Transact(opts, "depositMulti", deposits)
65+
if err != nil {
66+
return nil, fmt.Errorf("error making multiple node deposits: %w", err)
67+
}
68+
return tx, nil
69+
}
70+
3971
// Estimate the gas to WithdrawETH
4072
func EstimateWithdrawEthGas(rp *rocketpool.RocketPool, nodeAccount common.Address, ethAmount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
4173
rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)

rocketpool-cli/megapool/commands.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package megapool
22

33
import (
4+
"fmt"
5+
46
"github.com/urfave/cli"
57

68
cliutils "github.com/rocket-pool/smartnode/shared/utils/cli"
@@ -30,8 +32,8 @@ func RegisterCommands(app *cli.App, name string, aliases []string) {
3032
{
3133
Name: "deposit",
3234
Aliases: []string{"d"},
33-
Usage: "Make a deposit and create a new validator on the megapool",
34-
UsageText: "rocketpool node deposit [options]",
35+
Usage: "Make a deposit and create a new validator on the megapool. Optionally specify count to make multiple deposits.",
36+
UsageText: "rocketpool megapool deposit [options]",
3537
Flags: []cli.Flag{
3638
cli.BoolFlag{
3739
Name: "yes, y",
@@ -41,6 +43,11 @@ func RegisterCommands(app *cli.App, name string, aliases []string) {
4143
Name: "use-express-ticket, e",
4244
Usage: "Use an express ticket to create a new validator",
4345
},
46+
cli.UintFlag{
47+
Name: "count, c",
48+
Usage: "Number of deposits to make",
49+
Value: 0,
50+
},
4451
},
4552
Action: func(c *cli.Context) error {
4653

@@ -49,6 +56,11 @@ func RegisterCommands(app *cli.App, name string, aliases []string) {
4956
return err
5057
}
5158

59+
// Validate count
60+
if c.Uint("count") == 0 {
61+
return fmt.Errorf("Count must be greater than 0")
62+
}
63+
5264
// Run
5365
return nodeMegapoolDeposit(c)
5466

rocketpool-cli/megapool/deposit.go

Lines changed: 92 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package megapool
33
import (
44
"fmt"
55
"math/big"
6+
"strconv"
67

78
"github.com/rocket-pool/smartnode/bindings/utils/eth"
89
"github.com/urfave/cli"
@@ -64,24 +65,6 @@ func nodeMegapoolDeposit(c *cli.Context) error {
6465
return nil
6566
}
6667

67-
/*
68-
// Check if the fee distributor has been initialized
69-
isInitializedResponse, err := rp.IsFeeDistributorInitialized()
70-
if err != nil {
71-
return err
72-
}
73-
if !isInitializedResponse.IsInitialized {
74-
fmt.Println("Your fee distributor has not been initialized yet so you cannot create a new validator.\nPlease run `rocketpool node initialize-fee-distributor` to initialize it first.")
75-
return nil
76-
}
77-
78-
// Post a warning about fee distribution
79-
if !(c.Bool("yes") || prompt.Confirm(fmt.Sprintf("%sNOTE: By creating a new validator, 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))) {
80-
fmt.Println("Cancelled.")
81-
return nil
82-
}
83-
*/
84-
8568
useExpressTicket := false
8669

8770
var wg errgroup.Group
@@ -118,7 +101,20 @@ func nodeMegapoolDeposit(c *cli.Context) error {
118101
return err
119102
}
120103

121-
if !(c.Bool("yes") || prompt.Confirm(fmt.Sprintf("%sNOTE: You are about to create a new megapool validator with a %.0f ETH deposit.%s\nWould you like to continue?", colorYellow, amount, colorReset))) {
104+
count := c.Uint64("count")
105+
106+
// If the count was not provided, prompt the user for the number of deposits
107+
for count == 0 {
108+
countStr := prompt.Prompt("How many validators would you like to create?", "^\\d+$", "Invalid number.")
109+
count, err = strconv.ParseUint(countStr, 10, 64)
110+
if err != nil {
111+
fmt.Println("Invalid number. Please try again.")
112+
continue
113+
}
114+
break
115+
}
116+
117+
if !(c.Bool("yes") || prompt.Confirm(fmt.Sprintf("%sNOTE: You are about to create %d new megapool validators, each with a %.0f ETH deposit (total: %.0f ETH).%s\nWould you like to continue?", colorYellow, count, amount, amount*float64(count), colorReset))) {
122118
fmt.Println("Cancelled.")
123119
return nil
124120
}
@@ -139,7 +135,7 @@ func nodeMegapoolDeposit(c *cli.Context) error {
139135
fmt.Printf("You have %d express tickets available.", expressTicketCount)
140136
fmt.Println()
141137
// Prompt for confirmation
142-
if c.Bool("yes") || prompt.Confirm("Would you like to use an express ticket?") {
138+
if c.Bool("yes") || prompt.Confirm("Would you like to use your express tickets?") {
143139
useExpressTicket = true
144140
}
145141
}
@@ -149,20 +145,30 @@ func nodeMegapoolDeposit(c *cli.Context) error {
149145
minNodeFee := 0.0
150146

151147
// Check deposit can be made
152-
canDeposit, err := rp.CanNodeDeposit(amountWei, minNodeFee, big.NewInt(0), useExpressTicket)
148+
var canDeposit api.CanNodeDepositResponse
149+
if count > 1 {
150+
canDeposit, err = rp.CanNodeDeposits(count, amountWei, minNodeFee, big.NewInt(0), useExpressTicket)
151+
} else {
152+
canDeposit, err = rp.CanNodeDeposit(amountWei, minNodeFee, big.NewInt(0), useExpressTicket)
153+
}
153154
if err != nil {
154155
return err
155156
}
156157
if !canDeposit.CanDeposit {
157-
fmt.Println("Cannot make node deposit:")
158+
fmt.Printf("Cannot make %d node deposits:\n", count)
159+
158160
if canDeposit.InsufficientBalanceWithoutCredit {
159161
nodeBalance := eth.WeiToEth(canDeposit.NodeBalance)
160162
fmt.Printf("There is not enough ETH in the staking pool to use your credit balance (it needs at least 1 ETH but only has %.2f ETH) and you don't have enough ETH in your wallet (%.6f ETH) to cover the deposit amount yourself. If you want to continue creating a minipool, you will either need to wait for the staking pool to have more ETH deposited or add more ETH to your node wallet.", eth.WeiToEth(canDeposit.DepositBalance), nodeBalance)
161163
}
162164
if canDeposit.InsufficientBalance {
163165
nodeBalance := eth.WeiToEth(canDeposit.NodeBalance)
164166
creditBalance := eth.WeiToEth(canDeposit.CreditBalance)
165-
fmt.Printf("The node's balance of %.6f ETH and credit balance of %.6f ETH are not enough to create a megapool validator with a %.1f ETH bond.", nodeBalance, creditBalance, amount)
167+
if count > 1 {
168+
fmt.Printf("The node's balance of %.6f ETH and credit balance of %.6f ETH are not enough to create %d megapool validators with a %.1f ETH bond each (total: %.1f ETH).", nodeBalance, creditBalance, count, amount, amount*float64(count))
169+
} else {
170+
fmt.Printf("The node's balance of %.6f ETH and credit balance of %.6f ETH are not enough to create a megapool validator with a %.1f ETH bond.", nodeBalance, creditBalance, amount)
171+
}
166172
}
167173
if canDeposit.InvalidAmount {
168174
fmt.Println("The deposit amount is invalid.")
@@ -174,12 +180,13 @@ func nodeMegapoolDeposit(c *cli.Context) error {
174180
}
175181

176182
useCreditBalance := false
183+
totalAmountWei := big.NewInt(0).Mul(amountWei, big.NewInt(int64(count)))
177184
fmt.Printf("You currently have %.2f ETH in your credit balance plus ETH staked on your behalf.\n", eth.WeiToEth(canDeposit.CreditBalance))
178185
if canDeposit.CreditBalance.Cmp(big.NewInt(0)) > 0 {
179186
if canDeposit.CanUseCredit {
180187
useCreditBalance = true
181188
// Get how much credit to use
182-
remainingAmount := big.NewInt(0).Sub(amountWei, canDeposit.CreditBalance)
189+
remainingAmount := big.NewInt(0).Sub(totalAmountWei, canDeposit.CreditBalance)
183190
if remainingAmount.Cmp(big.NewInt(0)) > 0 {
184191
fmt.Printf("This deposit will use all %.6f ETH from your credit balance plus ETH staked on your behalf and %.6f ETH from your node.\n\n", eth.WeiToEth(canDeposit.CreditBalance), eth.WeiToEth(remainingAmount))
185192
} else {
@@ -189,6 +196,7 @@ func nodeMegapoolDeposit(c *cli.Context) error {
189196
fmt.Printf("%sNOTE: Your credit balance *cannot* currently be used to create a new megapool validator; there is not enough ETH in the staking pool to cover the initial deposit on your behalf (it needs at least 1 ETH but only has %.2f ETH).%s\nIf you want to continue creating this megapool validator now, you will have to pay for the full bond amount.\n\n", colorYellow, eth.WeiToEth(canDeposit.DepositBalance), colorReset)
190197
}
191198
}
199+
192200
// Prompt for confirmation
193201
if !(c.Bool("yes") || prompt.Confirm("Would you like to continue?")) {
194202
fmt.Println("Cancelled.")
@@ -224,40 +232,77 @@ func nodeMegapoolDeposit(c *cli.Context) error {
224232
}
225233

226234
// Prompt for confirmation
235+
227236
if !(c.Bool("yes") || prompt.Confirm(fmt.Sprintf(
228-
"You are about to deposit %.6f ETH to create a new megapool validator.\n"+
229-
"%sARE YOU SURE YOU WANT TO DO THIS? Exiting this validator and retrieving your capital cannot be done until the validator has been *active* on the Beacon Chain for 256 epochs (approx. 27 hours).%s\n",
237+
"You are about to deposit %.6f ETH to create %d new megapool validators (%.6f ETH total).\n"+
238+
"%sARE YOU SURE YOU WANT TO DO THIS? Exiting these validators and retrieving your capital cannot be done until each validator has been *active* on the Beacon Chain for 256 epochs (approx. 27 hours).%s\n",
230239
math.RoundDown(eth.WeiToEth(amountWei), 6),
240+
count,
241+
math.RoundDown(eth.WeiToEth(amountWei), 6)*float64(count),
231242
colorYellow,
232243
colorReset))) {
233244
fmt.Println("Cancelled.")
234245
return nil
235246
}
236247

237-
// Make deposit
238-
response, err := rp.NodeDeposit(amountWei, minNodeFee, big.NewInt(0), useCreditBalance, useExpressTicket, true)
239-
if err != nil {
240-
return err
241-
}
248+
// Make deposit(s)
249+
if count == 1 {
250+
// Single deposit
251+
response, err := rp.NodeDeposit(amountWei, minNodeFee, big.NewInt(0), useCreditBalance, useExpressTicket, true)
252+
if err != nil {
253+
return err
254+
}
242255

243-
// Log and wait for the megapool validator deposit
244-
fmt.Printf("Creating megapool validator...\n")
245-
cliutils.PrintTransactionHash(rp, response.TxHash)
246-
_, err = rp.WaitForTransaction(response.TxHash)
247-
if err != nil {
248-
return err
249-
}
256+
// Log and wait for the megapool validator deposit
257+
fmt.Printf("Creating megapool validator...\n")
258+
cliutils.PrintTransactionHash(rp, response.TxHash)
259+
_, err = rp.WaitForTransaction(response.TxHash)
260+
if err != nil {
261+
return err
262+
}
263+
264+
// Log & return
265+
fmt.Printf("The node deposit of %.6f ETH was made successfully!\n", math.RoundDown(eth.WeiToEth(amountWei), 6))
266+
fmt.Printf("The validator pubkey is: %s\n\n", response.ValidatorPubkey.Hex())
250267

251-
// Log & return
252-
fmt.Printf("The node deposit of %.6f ETH was made successfully!\n", math.RoundDown(eth.WeiToEth(amountWei), 6))
253-
fmt.Printf("The validator pubkey is: %s\n\n", response.ValidatorPubkey.Hex())
268+
fmt.Println("The new megapool validator has been created.")
269+
fmt.Println("Once your validator progresses through the queue, ETH will be assigned and a 1 ETH prestake submitted.")
270+
fmt.Printf("After the prestake, your node will automatically perform a stake transaction, to complete the progress.")
271+
fmt.Println("")
272+
fmt.Println("To check the status of your validators use `rocketpool megapool validators`")
273+
fmt.Println("To monitor the stake transaction use `rocketpool service logs node`")
274+
} else {
275+
// Multiple deposits
276+
responses, err := rp.NodeDeposits(count, amountWei, minNodeFee, big.NewInt(0), useCreditBalance, useExpressTicket, true)
277+
if err != nil {
278+
return err
279+
}
254280

255-
fmt.Println("The new megapool validator has been created.")
256-
fmt.Println("Once your validator progresses through the queue, ETH will be assigned and a 1 ETH prestake submitted.")
257-
fmt.Printf("After the prestake, your node will automatically perform a stake transaction, to complete the progress.")
258-
fmt.Println("")
259-
fmt.Println("To check the status of your validators use `rocketpool megapool validators`")
260-
fmt.Println("To monitor the stake transaction use `rocketpool service logs node`")
281+
// Log and wait for the megapool validator deposits
282+
fmt.Printf("Creating %d megapool validators in a single transaction...\n", count)
283+
cliutils.PrintTransactionHash(rp, responses.TxHash)
284+
_, err = rp.WaitForTransaction(responses.TxHash)
285+
if err != nil {
286+
return err
287+
}
288+
289+
// Log & return
290+
fmt.Printf("The node deposits of %.6f ETH each (%.6f ETH total) were made successfully!\n",
291+
math.RoundDown(eth.WeiToEth(amountWei), 6),
292+
math.RoundDown(eth.WeiToEth(amountWei), 6)*float64(count))
293+
fmt.Printf("Validator pubkeys:\n")
294+
for i, pubkey := range responses.ValidatorPubkeys {
295+
fmt.Printf(" %d. %s\n", i+1, pubkey.Hex())
296+
}
297+
fmt.Println()
298+
299+
fmt.Printf("The %d new megapool validators have been created.\n", count)
300+
fmt.Println("Once your validators progress through the queue, ETH will be assigned and a 1 ETH prestake submitted for each.")
301+
fmt.Printf("After the prestake, your node will automatically perform a stake transaction for each validator, to complete the progress.")
302+
fmt.Println("")
303+
fmt.Println("To check the status of your validators use `rocketpool megapool validators`")
304+
fmt.Println("To monitor the stake transactions use `rocketpool service logs node`")
305+
}
261306

262307
return nil
263308

rocketpool-cli/megapool/dissolve-validator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func dissolveValidator(c *cli.Context) error {
3838
// Get Megapool status
3939
status, err := rp.MegapoolStatus(false)
4040
if err != nil {
41-
return err
41+
return err
4242
}
4343

4444
validatorsInPrestake := []api.MegapoolValidatorDetails{}

0 commit comments

Comments
 (0)