diff --git a/rocketpool-cli/commands/pdao/get-voting-power.go b/rocketpool-cli/commands/pdao/get-voting-power.go index d158fa9db..726c809f1 100644 --- a/rocketpool-cli/commands/pdao/get-voting-power.go +++ b/rocketpool-cli/commands/pdao/get-voting-power.go @@ -5,6 +5,7 @@ import ( "github.com/rocket-pool/node-manager-core/eth" "github.com/rocket-pool/smartnode/v2/rocketpool-cli/client" + "github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/terminal" "github.com/urfave/cli/v2" ) @@ -13,13 +14,31 @@ func getVotePower(c *cli.Context) error { rp := client.NewClientFromCtx(c) // Get node's voting power at the latest block - response, err := rp.Api.PDao.GetVotingPower() + vpResponse, err := rp.Api.PDao.GetVotingPower() + if err != nil { + return err + } + + // Get the node's status + statusResponse, err := rp.Api.Node.Status() if err != nil { return err } // Print Results fmt.Println("== Node Voting Power ==") - fmt.Printf("Your current voting power: %.10f\n", eth.WeiToEth(response.Data.VotingPower)) + if statusResponse.Data.IsVotingInitialized { + fmt.Println("The node has been initialized for onchain voting.") + + } else { + fmt.Println("The node has NOT been initialized for onchain voting. You need to run `rocketpool pdao initialize-voting` to participate in onchain votes.") + } + if statusResponse.Data.OnchainVotingDelegate == statusResponse.Data.AccountAddress { + fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals.") + } else { + fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.\n", terminal.ColorBlue, statusResponse.Data.OnchainVotingDelegateFormatted, terminal.ColorReset) + } + + fmt.Printf("Your current voting power: %.10f\n", eth.WeiToEth(vpResponse.Data.VotingPower)) return nil } diff --git a/rocketpool-daemon/api/node/stake-rpl.go b/rocketpool-daemon/api/node/stake-rpl.go index b3989e224..912044255 100644 --- a/rocketpool-daemon/api/node/stake-rpl.go +++ b/rocketpool-daemon/api/node/stake-rpl.go @@ -17,6 +17,7 @@ import ( "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" "github.com/rocket-pool/node-manager-core/utils/input" + "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/utils" "github.com/rocket-pool/smartnode/v2/shared/types/api" ) @@ -102,7 +103,7 @@ func (c *nodeStakeRplContext) PrepareData(data *api.NodeStakeRplData, opts *bind if data.CanStake { if c.allowance.Cmp(c.amount) < 0 { // Do the approve TX if needed - approvalAmount := getMaxApproval() + approvalAmount := utils.GetMaxApproval() txInfo, err := c.rpl.Approve(c.nsAddress, approvalAmount, opts) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting TX info to approve increasing RPL's allowance: %w", err) diff --git a/rocketpool-daemon/api/node/status.go b/rocketpool-daemon/api/node/status.go index df162bb28..e289f5728 100644 --- a/rocketpool-daemon/api/node/status.go +++ b/rocketpool-daemon/api/node/status.go @@ -20,7 +20,6 @@ import ( "github.com/rocket-pool/rocketpool-go/v2/rocketpool" "github.com/rocket-pool/rocketpool-go/v2/tokens" rptypes "github.com/rocket-pool/rocketpool-go/v2/types" - ens "github.com/wealdtech/go-ens/v3" "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" @@ -29,6 +28,7 @@ import ( "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/alerting/alertmanager/models" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/collateral" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/contracts" + "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/utils" "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/voting" "github.com/rocket-pool/smartnode/v2/shared/config" "github.com/rocket-pool/smartnode/v2/shared/types/api" @@ -211,18 +211,18 @@ func (c *nodeStatusContext) PrepareData(data *api.NodeStatusData, opts *bind.Tra // Basic properties data.AccountAddress = c.node.Address - data.AccountAddressFormatted = c.getFormattedAddress(data.AccountAddress) + data.AccountAddressFormatted = utils.GetFormattedAddress(c.ec, data.AccountAddress) data.Trusted = c.odaoMember.Exists.Get() data.Registered = c.node.Exists.Get() data.PrimaryWithdrawalAddress = c.node.PrimaryWithdrawalAddress.Get() - data.PrimaryWithdrawalAddressFormatted = c.getFormattedAddress(data.PrimaryWithdrawalAddress) + data.PrimaryWithdrawalAddressFormatted = utils.GetFormattedAddress(c.ec, data.PrimaryWithdrawalAddress) data.PendingPrimaryWithdrawalAddress = c.node.PendingPrimaryWithdrawalAddress.Get() - data.PendingPrimaryWithdrawalAddressFormatted = c.getFormattedAddress(data.PendingPrimaryWithdrawalAddress) + data.PendingPrimaryWithdrawalAddressFormatted = utils.GetFormattedAddress(c.ec, data.PendingPrimaryWithdrawalAddress) data.IsRplWithdrawalAddressSet = c.node.IsRplWithdrawalAddressSet.Get() data.RplWithdrawalAddress = c.node.RplWithdrawalAddress.Get() - data.RplWithdrawalAddressFormatted = c.getFormattedAddress(data.RplWithdrawalAddress) + data.RplWithdrawalAddressFormatted = utils.GetFormattedAddress(c.ec, data.RplWithdrawalAddress) data.PendingRplWithdrawalAddress = c.node.PendingRplWithdrawalAddress.Get() - data.PendingRplWithdrawalAddressFormatted = c.getFormattedAddress(data.PendingRplWithdrawalAddress) + data.PendingRplWithdrawalAddressFormatted = utils.GetFormattedAddress(c.ec, data.PendingRplWithdrawalAddress) data.TimezoneLocation = c.node.TimezoneLocation.Get() data.RplStake = c.node.RplStake.Get() data.EffectiveRplStake = c.node.EffectiveRplStake.Get() @@ -238,7 +238,7 @@ func (c *nodeStatusContext) PrepareData(data *api.NodeStatusData, opts *bind.Tra data.RplLocked = c.node.RplLocked.Get() data.IsVotingInitialized = c.node.IsVotingInitialized.Get() data.OnchainVotingDelegate = c.node.CurrentVotingDelegate.Get() - data.OnchainVotingDelegateFormatted = c.getFormattedAddress(data.OnchainVotingDelegate) + data.OnchainVotingDelegateFormatted = utils.GetFormattedAddress(c.ec, data.OnchainVotingDelegate) // Minipool info mps, err := c.getMinipoolInfo(data) @@ -266,7 +266,7 @@ func (c *nodeStatusContext) PrepareData(data *api.NodeStatusData, opts *bind.Tra emptyAddress := common.Address{} data.SnapshotVotingDelegate = c.delegate if data.SnapshotVotingDelegate != emptyAddress { - data.SnapshotVotingDelegateFormatted = c.getFormattedAddress(data.SnapshotVotingDelegate) + data.SnapshotVotingDelegateFormatted = utils.GetFormattedAddress(c.ec, data.SnapshotVotingDelegate) } props, err := voting.GetSnapshotProposals(c.cfg, c.node.Address, c.delegate, true) if err != nil { @@ -332,15 +332,6 @@ func (c *nodeStatusContext) PrepareData(data *api.NodeStatusData, opts *bind.Tra return types.ResponseStatus_Success, nil } -// Get a formatting string containing the ENS name for an address (if it exists) -func (c *nodeStatusContext) getFormattedAddress(address common.Address) string { - name, err := ens.ReverseResolve(c.ec, address) - if err != nil { - return address.Hex() - } - return fmt.Sprintf("%s (%s)", name, address.Hex()) -} - // Get info pertaining to the node's minipools func (c *nodeStatusContext) getMinipoolInfo(data *api.NodeStatusData) ([]minipool.IMinipool, error) { // Minipool info diff --git a/rocketpool-daemon/api/node/swap-rpl.go b/rocketpool-daemon/api/node/swap-rpl.go index bd5dc37e6..8f25e42a4 100644 --- a/rocketpool-daemon/api/node/swap-rpl.go +++ b/rocketpool-daemon/api/node/swap-rpl.go @@ -16,6 +16,7 @@ import ( "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" "github.com/rocket-pool/node-manager-core/utils/input" + "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/utils" "github.com/rocket-pool/smartnode/v2/shared/types/api" ) @@ -101,7 +102,7 @@ func (c *nodeSwapRplContext) PrepareData(data *api.NodeSwapRplData, opts *bind.T if data.CanSwap { if c.allowance.Cmp(c.amount) < 0 { // Do the approve TX if needed - approvalAmount := getMaxApproval() + approvalAmount := utils.GetMaxApproval() txInfo, err := c.fsrpl.Approve(c.rplAddress, approvalAmount, opts) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting TX info to approve increasing legacy RPL's allowance: %w", err) diff --git a/rocketpool-daemon/api/node/utils.go b/rocketpool-daemon/api/node/utils.go deleted file mode 100644 index bd2af9207..000000000 --- a/rocketpool-daemon/api/node/utils.go +++ /dev/null @@ -1,11 +0,0 @@ -package node - -import "math/big" - -func getMaxApproval() *big.Int { - // Calculate max uint256 value - maxApproval := big.NewInt(2) - maxApproval = maxApproval.Exp(maxApproval, big.NewInt(256), nil) - maxApproval = maxApproval.Sub(maxApproval, big.NewInt(1)) - return maxApproval -} diff --git a/rocketpool-daemon/api/pdao/get-voting-power.go b/rocketpool-daemon/api/pdao/get-voting-power.go index 74f53560f..52add35f6 100644 --- a/rocketpool-daemon/api/pdao/get-voting-power.go +++ b/rocketpool-daemon/api/pdao/get-voting-power.go @@ -11,6 +11,7 @@ import ( "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" + "github.com/rocket-pool/smartnode/v2/rocketpool-daemon/common/utils" "github.com/rocket-pool/smartnode/v2/shared/types/api" ) @@ -67,14 +68,17 @@ func (c *protocolDaoGetVotingPowerContext) PrepareData(data *api.ProtocolDaoGetV if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error getting latest block number: %w", err) } + data.BlockNumber = uint32(blockNumber) - // Get the voting power at that block + // Get the voting power and delegate at that block err = rp.Query(func(mc *batch.MultiCaller) error { - node.GetVotingPowerAtBlock(mc, &data.VotingPower, uint32(blockNumber)) + node.GetVotingPowerAtBlock(mc, &data.VotingPower, data.BlockNumber) + node.GetVotingDelegateAtBlock(mc, &data.OnchainVotingDelegate, data.BlockNumber) return nil }, nil) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting voting power for block %d: %w", blockNumber, err) + return types.ResponseStatus_Error, fmt.Errorf("error getting voting info for block %d: %w", blockNumber, err) } + data.OnchainVotingDelegateFormatted = utils.GetFormattedAddress(ec, data.OnchainVotingDelegate) return types.ResponseStatus_Success, nil } diff --git a/rocketpool-daemon/api/wallet/set-ens-name.go b/rocketpool-daemon/api/wallet/set-ens-name.go index 3bbc11f28..cc18abd22 100644 --- a/rocketpool-daemon/api/wallet/set-ens-name.go +++ b/rocketpool-daemon/api/wallet/set-ens-name.go @@ -10,7 +10,7 @@ import ( "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" "github.com/rocket-pool/smartnode/v2/shared/types/api" - ens "github.com/wealdtech/go-ens/v3" + "github.com/wealdtech/go-ens/v3" ) // =============== diff --git a/rocketpool-daemon/common/utils/utils.go b/rocketpool-daemon/common/utils/utils.go new file mode 100644 index 000000000..cf4665f28 --- /dev/null +++ b/rocketpool-daemon/common/utils/utils.go @@ -0,0 +1,27 @@ +package utils + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/rocket-pool/node-manager-core/eth" + ens "github.com/wealdtech/go-ens/v3" +) + +func GetMaxApproval() *big.Int { + // Calculate max uint256 value + maxApproval := big.NewInt(2) + maxApproval = maxApproval.Exp(maxApproval, big.NewInt(256), nil) + maxApproval = maxApproval.Sub(maxApproval, big.NewInt(1)) + return maxApproval +} + +// Get a formatting string containing the ENS name for an address (if it exists) +func GetFormattedAddress(ec eth.IExecutionClient, address common.Address) string { + name, err := ens.ReverseResolve(ec, address) + if err != nil { + return address.Hex() + } + return fmt.Sprintf("%s (%s)", name, address.Hex()) +} diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index e70446c5a..e52e23bef 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -299,5 +299,8 @@ type ProtocolDaoCurrentVotingDelegateData struct { } type ProtocolDaoGetVotingPowerData struct { - VotingPower *big.Int `json:"votingPower"` + VotingPower *big.Int `json:"votingPower"` + OnchainVotingDelegate common.Address `json:"onchainVotingDelegate"` + OnchainVotingDelegateFormatted string `json:"onchainVotingDelegateFormatted"` + BlockNumber uint32 `json:"blockNumber"` }