From 0638c7a42c9dcddf1f5e189e3ea08421968535f6 Mon Sep 17 00:00:00 2001 From: batphonghan Date: Tue, 5 Dec 2023 21:32:50 +0700 Subject: [PATCH] Approve SD for repay --- shared/services/stader/node.go | 48 +++++++++ shared/types/api/node.go | 6 ++ stader-cli/node/approve-sd.go | 76 +++++++++++++ stader-cli/node/commands.go | 24 +++++ stader-cli/node/repay-sd.go | 14 +++ stader/api/node/commands.go | 54 ++++++++++ stader/api/node/utility-sd-allowance.go | 136 ++++++++++++++++++++++++ 7 files changed, 358 insertions(+) create mode 100644 stader/api/node/utility-sd-allowance.go diff --git a/shared/services/stader/node.go b/shared/services/stader/node.go index d9a53d3ab..0465c6262 100644 --- a/shared/services/stader/node.go +++ b/shared/services/stader/node.go @@ -727,3 +727,51 @@ func (c *Client) GetSDStatus(numValidators *big.Int) (api.GetSdStatusResponse, e return response, nil } + +// Get the node's SD allowance +func (c *Client) GetSDPoolUtilitySdAllowance() (api.SDPoolUtilitySdAllowanceResponse, error) { + responseBytes, err := c.callAPI(fmt.Sprintf("node utility-sd-allowance")) + if err != nil { + return api.SDPoolUtilitySdAllowanceResponse{}, fmt.Errorf("could not get node deposit SD allowance: %w", err) + } + var response api.SDPoolUtilitySdAllowanceResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return api.SDPoolUtilitySdAllowanceResponse{}, fmt.Errorf("could not decode node deposit SD allowance response: %w", err) + } + if response.Error != "" { + return api.SDPoolUtilitySdAllowanceResponse{}, fmt.Errorf("could not get node deposit SD allowance: %s", response.Error) + } + return response, nil +} + +// Approve SD for utility contract +func (c *Client) SDPoolUtilitySdApprove(amountWei *big.Int) (api.NodeDepositSdApproveResponse, error) { + responseBytes, err := c.callAPI(fmt.Sprintf("node utility-approve-sd %s", amountWei.String())) + if err != nil { + return api.NodeDepositSdApproveResponse{}, fmt.Errorf("could not approve SD for staking: %w", err) + } + var response api.NodeDepositSdApproveResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return api.NodeDepositSdApproveResponse{}, fmt.Errorf("could not decode deposit node SD approve response: %w", err) + } + if response.Error != "" { + return api.NodeDepositSdApproveResponse{}, fmt.Errorf("could not approve SD for staking: %s", response.Error) + } + return response, nil +} + +// Get the gas estimate for approving new SD interaction +func (c *Client) SDPoolUtilitySdApproveGas(amountWei *big.Int) (api.NodeDepositSdApproveGasResponse, error) { + responseBytes, err := c.callAPI(fmt.Sprintf("node utility-approve-sd-gas %s", amountWei.String())) + if err != nil { + return api.NodeDepositSdApproveGasResponse{}, fmt.Errorf("could not get new SD approval gas: %w", err) + } + var response api.NodeDepositSdApproveGasResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return api.NodeDepositSdApproveGasResponse{}, fmt.Errorf("could not decode utility SD approve gas response: %w", err) + } + if response.Error != "" { + return api.NodeDepositSdApproveGasResponse{}, fmt.Errorf("could not get utility SD approval gas: %s", response.Error) + } + return response, nil +} diff --git a/shared/types/api/node.go b/shared/types/api/node.go index 9afa9a511..7427326e0 100644 --- a/shared/types/api/node.go +++ b/shared/types/api/node.go @@ -91,6 +91,12 @@ type NodeDepositSdAllowanceResponse struct { Allowance *big.Int `json:"allowance"` } +type SDPoolUtilitySdAllowanceResponse struct { + Status string `json:"status"` + Error string `json:"error"` + Allowance *big.Int `json:"allowance"` +} + type CanNodeDepositResponse struct { Status string `json:"status"` Error string `json:"error"` diff --git a/stader-cli/node/approve-sd.go b/stader-cli/node/approve-sd.go index 4c9d702d7..eeb780ce6 100644 --- a/stader-cli/node/approve-sd.go +++ b/stader-cli/node/approve-sd.go @@ -98,3 +98,79 @@ func nodeApproveSdWithAmount(staderClient *stader.Client, amountWei *big.Int, au return nil } + +func nodeApproveUtilitySd(c *cli.Context) error { + staderClient, err := stader.NewClientFromCtx(c) + if err != nil { + return err + } + defer staderClient.Close() + + // Check and assign the EC status + err = cliutils.CheckClientStatus(staderClient) + if err != nil { + return err + } + + // If a custom nonce is set, print the multi-transaction warning + if c.GlobalUint64("nonce") != 0 { + cliutils.PrintMultiTransactionNonceWarning() + } + + amountInString := c.String("amount") + + amount, err := strconv.ParseFloat(amountInString, 64) + if err != nil { + return err + } + + amountWei := eth.EthToWei(amount) + + autoConfirm := c.Bool("yes") + + nonce := c.GlobalUint64("nonce") + + if nonce != 0 { + cliutils.PrintMultiTransactionNonceWarning() + } + + // Get approval gas + approvalGas, err := staderClient.SDPoolUtilitySdApproveGas(amountWei) + if err != nil { + return err + } + + // Assign max fees + err = gas.AssignMaxFeeAndLimit(approvalGas.GasInfo, staderClient, autoConfirm) + if err != nil { + return err + } + + // Prompt for confirmation + if !(autoConfirm || cliutils.Confirm("Do you want to approve SD to be spent by the Utility Contract?")) { + fmt.Println("Cancelled.") + return nil + } + + response, err := staderClient.SDPoolUtilitySdApprove(amountWei) + if err != nil { + return err + } + + hash := response.ApproveTxHash + + fmt.Println("Approving SD..") + cliutils.PrintTransactionHash(staderClient, hash) + + if _, err = staderClient.WaitForTransaction(hash); err != nil { + return err + } + + fmt.Println("Successfully approved SD.") + + // If a custom nonce is set, increment it for the next transaction + if nonce != 0 { + staderClient.IncrementCustomNonce() + } + return nil +} diff --git a/stader-cli/node/commands.go b/stader-cli/node/commands.go index 3da475ea2..376cd6fb5 100644 --- a/stader-cli/node/commands.go +++ b/stader-cli/node/commands.go @@ -428,6 +428,30 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { return repayExcessSD(c) }, }, + { + Name: "approve-utility-sd", + Aliases: []string{"aus"}, + Usage: "Approve SD for utility pool", + UsageText: "stader-cli node approve-utility-sd [options]", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "amount, a", + Usage: "The amount of SD to approve", + }, + cli.BoolFlag{ + Name: "yes, y", + Usage: "Automatically confirm SD approve", + }, + }, + Action: func(c *cli.Context) error { + if _, err := cliutils.ValidatePositiveEthAmount("sd amount", c.String("amount")); err != nil { + return err + } + + // Run + return nodeApproveUtilitySd(c) + }, + }, }, }) } diff --git a/stader-cli/node/repay-sd.go b/stader-cli/node/repay-sd.go index 428347eab..19d3e5ad1 100644 --- a/stader-cli/node/repay-sd.go +++ b/stader-cli/node/repay-sd.go @@ -41,6 +41,20 @@ func repaySD(c *cli.Context) error { amountWei := eth.EthToWei(amount) + // Check allowance + allowance, err := staderClient.GetSDPoolUtilitySdAllowance() + if err != nil { + return err + } + + if allowance.Allowance.Cmp(amountWei) < 0 { + fmt.Println("Before repay SD, you must first give the utility contract approval to interact with your SD. Amount to approve: ", eth.WeiToEth(amountWei)) + err = nodeApproveUtilitySd(c) + if err != nil { + return err + } + } + canClaimElRewardsResponse, err := staderClient.CanRepaySd(amountWei) if err != nil { return err diff --git a/stader/api/node/commands.go b/stader/api/node/commands.go index 56b392997..8ab1f9a0f 100644 --- a/stader/api/node/commands.go +++ b/stader/api/node/commands.go @@ -817,6 +817,60 @@ func RegisterSubcommands(command *cli.Command, name string, aliases []string) { return nil }, }, + { + Name: "utility-sd-allowance", + Usage: "Get the node's SD allowance for the utility contract", + UsageText: "stader-cli api node utility-sd-allowance", + Action: func(c *cli.Context) error { + // Validate args + if err := cliutils.ValidateArgCount(c, 0); err != nil { + return err + } + + // Run + api.PrintResponse(utilityAllowanceSd(c)) + return nil + + }, + }, + { + Name: "utility-approve-sd", + Usage: "Get the node's SD allowance for the utility contract", + UsageText: "stader-cli api node utility-approve-sd", + Action: func(c *cli.Context) error { + // Validate args + if err := cliutils.ValidateArgCount(c, 1); err != nil { + return err + } + amountWei, err := cliutils.ValidatePositiveWeiAmount("amount", c.Args().Get(0)) + if err != nil { + return err + } + + // Run + api.PrintResponse(utilityApproveSd(c, amountWei)) + return nil + }, + }, + { + Name: "utility-approve-sd-gas", + Usage: "Get the gas info of node approve SD for the utility contract", + UsageText: "stader-cli api node utility-approve-sd-gas", + Action: func(c *cli.Context) error { + // Validate args + if err := cliutils.ValidateArgCount(c, 1); err != nil { + return err + } + amountWei, err := cliutils.ValidatePositiveWeiAmount("amount", c.Args().Get(0)) + if err != nil { + return err + } + + // Run + api.PrintResponse(getUtilitySdApprovalGas(c, amountWei)) + return nil + }, + }, }, }) } diff --git a/stader/api/node/utility-sd-allowance.go b/stader/api/node/utility-sd-allowance.go new file mode 100644 index 000000000..77cb03000 --- /dev/null +++ b/stader/api/node/utility-sd-allowance.go @@ -0,0 +1,136 @@ +package node + +import ( + "fmt" + "math/big" + + "github.com/stader-labs/stader-node/stader-lib/tokens" + "github.com/urfave/cli" + + "github.com/stader-labs/stader-node/shared/services" + "github.com/stader-labs/stader-node/shared/types/api" + "github.com/stader-labs/stader-node/shared/utils/eth1" +) + +func utilityAllowanceSd(c *cli.Context) (*api.SDPoolUtilitySdAllowanceResponse, error) { + // Get services + if err := services.RequireNodeRegistered(c); err != nil { + return nil, err + } + + w, err := services.GetWallet(c) + if err != nil { + return nil, err + } + + sdt, err := services.GetSdTokenContract(c) + if err != nil { + return nil, err + } + + sduAddress, err := services.GetSdUtilityAddress(c) + if err != nil { + return nil, err + } + + // Response + response := api.SDPoolUtilitySdAllowanceResponse{} + + // Get node account + account, err := w.GetNodeAccount() + if err != nil { + return nil, err + } + + allowance, err := tokens.Allowance(sdt, account.Address, sduAddress, nil) + if err != nil { + return nil, err + } + + response.Allowance = allowance + + return &response, nil +} + +func utilityApproveSd(c *cli.Context, amountWei *big.Int) (*api.NodeDepositSdApproveResponse, error) { + // Get services + if err := services.RequireNodeRegistered(c); err != nil { + return nil, err + } + + w, err := services.GetWallet(c) + if err != nil { + return nil, err + } + + sdt, err := services.GetSdTokenContract(c) + if err != nil { + return nil, err + } + + sduAddress, err := services.GetSdUtilityAddress(c) + if err != nil { + return nil, err + } + // Response + response := api.NodeDepositSdApproveResponse{} + + opts, err := w.GetNodeAccountTransactor() + if err != nil { + return nil, err + } + + err = eth1.CheckForNonceOverride(c, opts) + if err != nil { + return nil, fmt.Errorf("Error checking for nonce override: %w", err) + } + + hash, err := tokens.Approve(sdt, sduAddress, amountWei, opts) + if err != nil { + return nil, err + } + + response.ApproveTxHash = hash + + // Return response + return &response, nil +} + +func getUtilitySdApprovalGas(c *cli.Context, amountWei *big.Int) (*api.NodeDepositSdApproveGasResponse, error) { + // Get services + if err := services.RequireNodeWallet(c); err != nil { + return nil, err + } + + w, err := services.GetWallet(c) + if err != nil { + return nil, err + } + + sdt, err := services.GetSdTokenContract(c) + if err != nil { + return nil, err + } + + utilityAddress, err := services.GetPoolUtilsAddress(c) + if err != nil { + return nil, err + } + + // Response + response := api.NodeDepositSdApproveGasResponse{} + + // Get gas estimates + opts, err := w.GetNodeAccountTransactor() + if err != nil { + return nil, err + } + + gasInfo, err := tokens.EstimateApproveGas(sdt, utilityAddress, amountWei, opts) + if err != nil { + return nil, err + } + + response.GasInfo = gasInfo + return &response, nil +}