diff --git a/README.md b/README.md index 662f0299a..a3d385845 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,8 @@ The following commands are available via the Smart Node client: - `rocketpool pdao set-voting-delegate, svd` - Set the address you want to use when voting on Rocket Pool on-chain governance proposals, or the address you want to delegate your voting power to. - `rocketpool pdao claim-bonds, cb` - Unlock any bonded RPL you have for a proposal or set of challenges, and claim any bond rewards for defending or defeating the proposal - `rocketpool pdao propose, p` - Make a Protocol DAO proposal + - `rocketpool pdao propose submit-batch, sb` - Submit a single proposal that changes multiple Protocol DAO settings from a JSON file + - Setting propose commands accept `--to-json ` to write/append a setting change to a JSON file instead of submitting a transaction - `rocketpool pdao proposals, o` - Manage Protocol DAO proposals - **queue**, q - Manage the Rocket Pool deposit queue - `rocketpool queue status, s` - Get the deposit pool and minipool queue status diff --git a/bindings/dao/protocol/proposal-payload.go b/bindings/dao/protocol/proposal-payload.go new file mode 100644 index 000000000..9c21e44a5 --- /dev/null +++ b/bindings/dao/protocol/proposal-payload.go @@ -0,0 +1,153 @@ +package protocol + +import ( + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/rocket-pool/smartnode/bindings/types" + strutils "github.com/rocket-pool/smartnode/bindings/utils/strings" +) + +const proposalSettingMultiMethod = "proposalSettingMulti" + +type DecodedProposalSetting struct { + Contract string `json:"contract"` + Path string `json:"path"` + Type types.ProposalSettingType `json:"type"` + Value string `json:"value"` +} + +func decodeProposalSettingMultiArgs(args []any) ([]DecodedProposalSetting, error) { + if len(args) != 4 { + return nil, fmt.Errorf("proposalSettingMulti expected 4 arguments, got %d", len(args)) + } + + contracts, err := asStringSlice(args[0]) + if err != nil { + return nil, fmt.Errorf("contract names: %w", err) + } + paths, err := asStringSlice(args[1]) + if err != nil { + return nil, fmt.Errorf("setting paths: %w", err) + } + settingTypes, err := asSettingTypes(args[2]) + if err != nil { + return nil, fmt.Errorf("setting types: %w", err) + } + values, err := asBytesSlice(args[3]) + if err != nil { + return nil, fmt.Errorf("setting values: %w", err) + } + + if len(contracts) != len(paths) || len(paths) != len(settingTypes) || len(settingTypes) != len(values) { + return nil, fmt.Errorf("proposalSettingMulti argument lengths do not match") + } + + settings := make([]DecodedProposalSetting, len(contracts)) + for i := range contracts { + value, err := decodeMultiSettingValue(settingTypes[i], values[i]) + if err != nil { + return nil, fmt.Errorf("setting %s: %w", paths[i], err) + } + settings[i] = DecodedProposalSetting{ + Contract: contracts[i], + Path: paths[i], + Type: settingTypes[i], + Value: value, + } + } + return settings, nil +} + +func FormatProposalSettingMulti(settings []DecodedProposalSetting) string { + parts := make([]string, len(settings)) + for i, setting := range settings { + parts[i] = fmt.Sprintf("%s=%s", setting.Path, setting.Value) + } + return strutils.Sanitize(fmt.Sprintf("%s(%s)", proposalSettingMultiMethod, strings.Join(parts, ", "))) +} + +func formatGenericProposalPayload(method *abi.Method, args []any) string { + argStrs := make([]string, 0, len(args)) + for ai, arg := range args { + switch method.Inputs[ai].Type.T { + case abi.AddressTy: + argStrs = append(argStrs, arg.(common.Address).Hex()) + case abi.HashTy: + argStrs = append(argStrs, arg.(common.Hash).Hex()) + case abi.FixedBytesTy: + fallthrough + case abi.BytesTy: + argStrs = append(argStrs, fmt.Sprintf("%x", arg.([]byte))) + default: + argStrs = append(argStrs, fmt.Sprintf("%v", arg)) + } + } + return strutils.Sanitize(fmt.Sprintf("%s(%s)", method.RawName, strings.Join(argStrs, ","))) +} + +func decodeMultiSettingValue(settingType types.ProposalSettingType, data []byte) (string, error) { + switch settingType { + case types.ProposalSettingType_Uint256: + return new(big.Int).SetBytes(data).String(), nil + case types.ProposalSettingType_Bool: + return fmt.Sprint(new(big.Int).SetBytes(data).Sign() != 0), nil + case types.ProposalSettingType_Address: + if len(data) >= common.AddressLength { + return common.BytesToAddress(data[len(data)-common.AddressLength:]).Hex(), nil + } + return common.BytesToAddress(data).Hex(), nil + default: + return "", fmt.Errorf("unknown setting type %d", settingType) + } +} + +func asStringSlice(value any) ([]string, error) { + switch typed := value.(type) { + case []string: + return typed, nil + default: + return nil, fmt.Errorf("expected []string, got %T", value) + } +} + +func asSettingTypes(value any) ([]types.ProposalSettingType, error) { + switch typed := value.(type) { + case []uint8: + out := make([]types.ProposalSettingType, len(typed)) + for i, item := range typed { + out[i] = types.ProposalSettingType(item) + } + return out, nil + case []*big.Int: + out := make([]types.ProposalSettingType, len(typed)) + for i, item := range typed { + if item == nil { + return nil, fmt.Errorf("nil setting type at index %d", i) + } + out[i] = types.ProposalSettingType(item.Uint64()) + } + return out, nil + case []uint16: + out := make([]types.ProposalSettingType, len(typed)) + for i, item := range typed { + out[i] = types.ProposalSettingType(item) + } + return out, nil + default: + return nil, fmt.Errorf("expected uint8 array, got %T", value) + } +} + +func asBytesSlice(value any) ([][]byte, error) { + switch typed := value.(type) { + case [][]byte: + return typed, nil + default: + return nil, fmt.Errorf("expected [][]byte, got %T", value) + } +} diff --git a/bindings/dao/protocol/proposal-payload_test.go b/bindings/dao/protocol/proposal-payload_test.go new file mode 100644 index 000000000..b4770b18b --- /dev/null +++ b/bindings/dao/protocol/proposal-payload_test.go @@ -0,0 +1,120 @@ +package protocol + +import ( + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + + "github.com/rocket-pool/smartnode/bindings/types" +) + +const proposalSettingMultiABI = `[{ + "name": "proposalSettingMulti", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + {"name": "_settingContractNames", "type": "string[]"}, + {"name": "_settingPaths", "type": "string[]"}, + {"name": "_types", "type": "uint8[]"}, + {"name": "_data", "type": "bytes[]"} + ], + "outputs": [] +}]` + +func TestDecodeAndFormatProposalSettingMulti(t *testing.T) { + parsed, err := abi.JSON(strings.NewReader(proposalSettingMultiABI)) + if err != nil { + t.Fatalf("parse ABI: %v", err) + } + + amount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1)) + address := common.HexToAddress("0x1234567890123456789012345678901234567890") + encoded := [][]byte{ + math.PaddedBigBytes(common.Big1, 32), + math.U256Bytes(new(big.Int).Set(amount)), + common.LeftPadBytes(address.Bytes(), 32), + } + + payload, err := parsed.Pack( + "proposalSettingMulti", + []string{"rocketDAOProtocolSettingsAuction", "rocketDAOProtocolSettingsDeposit", "rocketDAOProtocolSettingsNetwork"}, + []string{"auction.lot.create.enabled", "deposit.minimum", "network.node.fee.target"}, + []uint8{ + uint8(types.ProposalSettingType_Bool), + uint8(types.ProposalSettingType_Uint256), + uint8(types.ProposalSettingType_Address), + }, + encoded, + ) + if err != nil { + t.Fatalf("pack: %v", err) + } + + method, err := parsed.MethodById(payload) + if err != nil { + t.Fatalf("method: %v", err) + } + args, err := method.Inputs.UnpackValues(payload[4:]) + if err != nil { + t.Fatalf("unpack: %v", err) + } + + settings, err := decodeProposalSettingMultiArgs(args) + if err != nil { + t.Fatalf("decode: %v", err) + } + if len(settings) != 3 { + t.Fatalf("got %d settings, want 3", len(settings)) + } + if settings[0].Path != "auction.lot.create.enabled" || settings[0].Value != "true" || settings[0].Type != types.ProposalSettingType_Bool { + t.Fatalf("bool setting = %+v", settings[0]) + } + if settings[1].Path != "deposit.minimum" || settings[1].Value != amount.String() || settings[1].Type != types.ProposalSettingType_Uint256 { + t.Fatalf("uint setting = %+v", settings[1]) + } + if settings[2].Path != "network.node.fee.target" || settings[2].Value != address.Hex() || settings[2].Type != types.ProposalSettingType_Address { + t.Fatalf("address setting = %+v", settings[2]) + } + + formatted := FormatProposalSettingMulti(settings) + if !strings.Contains(formatted, "auction.lot.create.enabled=true") { + t.Fatalf("formatted missing bool setting: %s", formatted) + } + if !strings.Contains(formatted, "deposit.minimum="+amount.String()) { + t.Fatalf("formatted missing uint setting: %s", formatted) + } + if !strings.Contains(formatted, "network.node.fee.target="+address.Hex()) { + t.Fatalf("formatted missing address setting: %s", formatted) + } + if strings.Contains(formatted, "[") || strings.Contains(formatted, "0x000000") { + t.Fatalf("formatted still looks like raw ABI dump: %s", formatted) + } +} + +func TestDecodeProposalSettingMultiArgs_LengthMismatch(t *testing.T) { + _, err := decodeProposalSettingMultiArgs([]any{ + []string{"a"}, + []string{"b", "c"}, + []uint8{0}, + [][]byte{{1}}, + }) + if err == nil { + t.Fatal("expected length mismatch error") + } +} + +func TestProposalSettingTypeString(t *testing.T) { + if types.ProposalSettingType_Bool.String() != "bool" { + t.Fatalf("bool string = %s", types.ProposalSettingType_Bool) + } + if types.ProposalSettingType_Uint256.String() != "uint256" { + t.Fatalf("uint string = %s", types.ProposalSettingType_Uint256) + } + if types.ProposalSettingType_Address.String() != "address" { + t.Fatalf("address string = %s", types.ProposalSettingType_Address) + } +} diff --git a/bindings/dao/protocol/proposal.go b/bindings/dao/protocol/proposal.go index 47b99728d..accb8430b 100644 --- a/bindings/dao/protocol/proposal.go +++ b/bindings/dao/protocol/proposal.go @@ -2,15 +2,12 @@ package protocol import ( "context" - "encoding/hex" "fmt" "math/big" - "strings" "sync" "time" "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "golang.org/x/sync/errgroup" @@ -19,7 +16,6 @@ import ( "github.com/rocket-pool/smartnode/bindings/rocketpool" "github.com/rocket-pool/smartnode/bindings/transactions/gaslimit" "github.com/rocket-pool/smartnode/bindings/types" - strutils "github.com/rocket-pool/smartnode/bindings/utils/strings" ) // Settings @@ -57,6 +53,7 @@ type ProtocolDaoProposalDetails struct { VetoQuorum *big.Int `json:"vetoQuorum"` Payload []byte `json:"payload"` PayloadStr string `json:"payloadStr"` + MultiSettings []DecodedProposalSetting `json:"multiSettings,omitempty"` State types.ProtocolDaoProposalState `json:"state"` ProposalBond *big.Int `json:"proposalBond"` ChallengeBond *big.Int `json:"challengeBond"` @@ -238,11 +235,12 @@ func GetProposalDetails(rp *rocketpool.RocketPool, proposalId uint64, opts *bind } // Get proposal payload string - payloadStr, err := GetProposalPayloadString(rp, prop.Payload, opts) + payloadStr, multiSettings, err := ParseProposalPayload(rp, prop.Payload, opts) if err != nil { payloadStr = "(unknown)" } prop.PayloadStr = payloadStr + prop.MultiSettings = multiSettings return prop, nil } @@ -508,45 +506,35 @@ func GetProposalPayload(rp *rocketpool.RocketPool, proposalId uint64, opts *bind // Get a proposal's payload as a human-readable string func GetProposalPayloadString(rp *rocketpool.RocketPool, payload []byte, opts *bind.CallOpts) (string, error) { + payloadStr, _, err := ParseProposalPayload(rp, payload, opts) + return payloadStr, err +} + +func ParseProposalPayload(rp *rocketpool.RocketPool, payload []byte, opts *bind.CallOpts) (string, []DecodedProposalSetting, error) { rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil) if err != nil { - return "", err + return "", nil, err } - // Get proposal DAO contract ABI - daoContractAbi := rocketDAOProtocolProposals.ABI - - // Get proposal payload method - method, err := daoContractAbi.MethodById(payload) + method, err := rocketDAOProtocolProposals.ABI.MethodById(payload) if err != nil { - return "", fmt.Errorf("error getting proposal payload method: %w", err) + return "", nil, fmt.Errorf("error getting proposal payload method: %w", err) } - // Get proposal payload argument values args, err := method.Inputs.UnpackValues(payload[4:]) if err != nil { - return "", fmt.Errorf("error getting proposal payload arguments: %w", err) - } - - // Format argument values as strings - argStrs := []string{} - for ai, arg := range args { - switch method.Inputs[ai].Type.T { - case abi.AddressTy: - argStrs = append(argStrs, arg.(common.Address).Hex()) - case abi.HashTy: - argStrs = append(argStrs, arg.(common.Hash).Hex()) - case abi.FixedBytesTy: - fallthrough - case abi.BytesTy: - argStrs = append(argStrs, hex.EncodeToString(arg.([]byte))) - default: - argStrs = append(argStrs, fmt.Sprintf("%v", arg)) + return "", nil, fmt.Errorf("error getting proposal payload arguments: %w", err) + } + + if method.RawName == proposalSettingMultiMethod { + settings, err := decodeProposalSettingMultiArgs(args) + if err != nil { + return "", nil, err } + return FormatProposalSettingMulti(settings), settings, nil } - // Build & return payload string - return strutils.Sanitize(fmt.Sprintf("%s(%s)", method.RawName, strings.Join(argStrs, ","))), nil + return formatGenericProposalPayload(method, args), nil, nil } // Get the proposal's state diff --git a/bindings/settings/protocol/setting-types.go b/bindings/settings/protocol/setting-types.go new file mode 100644 index 000000000..f90efa86f --- /dev/null +++ b/bindings/settings/protocol/setting-types.go @@ -0,0 +1,180 @@ +package protocol + +import ( + "fmt" + + "github.com/rocket-pool/smartnode/bindings/types" +) + +// Proposal setting type names written to --to-json files. +const ( + ProposalSettingTypeNameUint256 = "uint256" + ProposalSettingTypeNameBool = "bool" + ProposalSettingTypeNameAddress = "address" +) + +var ( + ErrUnknownPDAOSetting = fmt.Errorf("unknown protocol DAO setting") + ErrUnsupportedBatchSetting = fmt.Errorf("setting type is not supported in a multi-setting proposal") + ErrDuplicateBatchSetting = fmt.Errorf("duplicate setting in multi-setting proposal") + ErrEmptyBatchSettings = fmt.Errorf("multi-setting proposal must contain at least one setting") +) + +// settingKind is the on-chain value type of a protocol DAO setting. +type settingKind uint8 + +const ( + settingKindUint256 settingKind = iota + settingKindBool + settingKindAddress + settingKindAddressList +) + +// pdaoSettingKinds maps contract name -> setting path -> value type for every +// setting the Smart Node can propose. +var pdaoSettingKinds = map[string]map[string]settingKind{ + AuctionSettingsContractName: { + CreateLotEnabledSettingPath: settingKindBool, + BidOnLotEnabledSettingPath: settingKindBool, + LotMinimumEthValueSettingPath: settingKindUint256, + LotMaximumEthValueSettingPath: settingKindUint256, + LotDurationSettingPath: settingKindUint256, + LotStartingPriceRatioSettingPath: settingKindUint256, + LotReservePriceRatioSettingPath: settingKindUint256, + }, + DepositSettingsContractName: { + DepositEnabledSettingPath: settingKindBool, + AssignDepositsEnabledSettingPath: settingKindBool, + MinimumDepositSettingPath: settingKindUint256, + MaximumDepositPoolSizeSettingPath: settingKindUint256, + MaximumDepositAssignmentsSettingPath: settingKindUint256, + MaximumSocializedDepositAssignmentsSettingPath: settingKindUint256, + DepositFeeSettingPath: settingKindUint256, + ExpressQueueRatePath: settingKindUint256, + ExpressQueueTicketsBaseProvisionPath: settingKindUint256, + }, + MinipoolSettingsContractName: { + MinipoolSubmitWithdrawableEnabledSettingPath: settingKindBool, + MinipoolLaunchTimeoutSettingPath: settingKindUint256, + BondReductionEnabledSettingPath: settingKindBool, + MaximumMinipoolCountSettingPath: settingKindUint256, + MinipoolUserDistributeWindowStartSettingPath: settingKindUint256, + MinipoolUserDistributeWindowLengthSettingPath: settingKindUint256, + }, + NetworkSettingsContractName: { + NodeConsensusThresholdSettingPath: settingKindUint256, + SubmitBalancesEnabledSettingPath: settingKindBool, + SubmitBalancesFrequencySettingPath: settingKindUint256, + SubmitPricesEnabledSettingPath: settingKindBool, + SubmitPricesFrequencySettingPath: settingKindUint256, + MinimumNodeFeeSettingPath: settingKindUint256, + TargetNodeFeeSettingPath: settingKindUint256, + MaximumNodeFeeSettingPath: settingKindUint256, + NodeFeeDemandRangeSettingPath: settingKindUint256, + TargetRethCollateralRateSettingPath: settingKindUint256, + NetworkPenaltyThresholdSettingPath: settingKindUint256, + NetworkPenaltyPerRateSettingPath: settingKindUint256, + SubmitRewardsEnabledSettingPath: settingKindBool, + NetworkAllowListedControllersPath: settingKindAddressList, + NetworkNodeCommissionSharePath: settingKindUint256, + NetworkNodeCommissionShareSecurityCouncilAdderPath: settingKindUint256, + NetworkVoterSharePath: settingKindUint256, + NetworkPDAOSharePath: settingKindUint256, + NetworkMaxNodeShareSecurityCouncilAdderPath: settingKindUint256, + NetworkMaxRethBalanceDeltaPath: settingKindUint256, + }, + NodeSettingsContractName: { + NodeRegistrationEnabledSettingPath: settingKindBool, + SmoothingPoolRegistrationEnabledSettingPath: settingKindBool, + NodeDepositEnabledSettingPath: settingKindBool, + VacantMinipoolsEnabledSettingPath: settingKindBool, + MinimumLegacyRplStakePath: settingKindUint256, + ReducedBondSettingPath: settingKindUint256, + NodeUnstakingPeriodSettingPath: settingKindUint256, + }, + ProposalsSettingsContractName: { + VotePhase1TimeSettingPath: settingKindUint256, + VotePhase2TimeSettingPath: settingKindUint256, + VoteDelayTimeSettingPath: settingKindUint256, + ExecuteTimeSettingPath: settingKindUint256, + ProposalBondSettingPath: settingKindUint256, + ChallengeBondSettingPath: settingKindUint256, + ChallengePeriodSettingPath: settingKindUint256, + ProposalQuorumSettingPath: settingKindUint256, + ProposalVetoQuorumSettingPath: settingKindUint256, + ProposalMaxBlockAgeSettingPath: settingKindUint256, + }, + RewardsSettingsContractName: { + RewardsClaimIntervalPeriodsSettingPath: settingKindUint256, + }, + SecuritySettingsContractName: { + SecurityMembersQuorumSettingPath: settingKindUint256, + SecurityMembersLeaveTimeSettingPath: settingKindUint256, + SecurityProposalVoteTimeSettingPath: settingKindUint256, + SecurityProposalExecuteTimeSettingPath: settingKindUint256, + SecurityProposalActionTimeSettingPath: settingKindUint256, + }, + MegapoolSettingsContractName: { + MegapoolTimeBeforeDissolveSettingsPath: settingKindUint256, + MegapoolMaximumMegapoolEthPenaltyPath: settingKindUint256, + MegapoolNotifyThresholdPath: settingKindUint256, + MegapoolLateNotifyFinePath: settingKindUint256, + MegapoolDissolvePenaltyPath: settingKindUint256, + MegapoolUserDistributeDelayPath: settingKindUint256, + MegapoolUserDistributeDelayShortfallPath: settingKindUint256, + MegapoolPenaltyThreshold: settingKindUint256, + }, +} + +// GetProposalSettingType returns the on-chain type used by proposalSettingMulti +// for the given contract/setting pair. +func GetProposalSettingType(contract string, setting string) (types.ProposalSettingType, error) { + kinds, ok := pdaoSettingKinds[contract] + if !ok { + return 0, fmt.Errorf("%w: [%s - %s]", ErrUnknownPDAOSetting, contract, setting) + } + kind, ok := kinds[setting] + if !ok { + return 0, fmt.Errorf("%w: [%s - %s]", ErrUnknownPDAOSetting, contract, setting) + } + switch kind { + case settingKindUint256: + return types.ProposalSettingType_Uint256, nil + case settingKindBool: + return types.ProposalSettingType_Bool, nil + case settingKindAddress: + return types.ProposalSettingType_Address, nil + case settingKindAddressList: + return 0, fmt.Errorf("%w: %s (address lists cannot be batched)", ErrUnsupportedBatchSetting, setting) + default: + return 0, fmt.Errorf("%w: [%s - %s]", ErrUnknownPDAOSetting, contract, setting) + } +} + +// ProposalSettingTypeName returns the JSON type string for a setting type. +func ProposalSettingTypeName(settingType types.ProposalSettingType) string { + switch settingType { + case types.ProposalSettingType_Uint256: + return ProposalSettingTypeNameUint256 + case types.ProposalSettingType_Bool: + return ProposalSettingTypeNameBool + case types.ProposalSettingType_Address: + return ProposalSettingTypeNameAddress + default: + return "" + } +} + +// ParseProposalSettingTypeName converts a JSON type string to a setting type. +func ParseProposalSettingTypeName(name string) (types.ProposalSettingType, error) { + switch name { + case ProposalSettingTypeNameUint256, "uint": + return types.ProposalSettingType_Uint256, nil + case ProposalSettingTypeNameBool: + return types.ProposalSettingType_Bool, nil + case ProposalSettingTypeNameAddress: + return types.ProposalSettingType_Address, nil + default: + return 0, fmt.Errorf("unknown setting type %q", name) + } +} diff --git a/bindings/types/dao.go b/bindings/types/dao.go index eb7aef5b4..df8f7fa75 100644 --- a/bindings/types/dao.go +++ b/bindings/types/dao.go @@ -76,6 +76,19 @@ const ( ProposalSettingType_Address ) +func (t ProposalSettingType) String() string { + switch t { + case ProposalSettingType_Uint256: + return "uint256" + case ProposalSettingType_Bool: + return "bool" + case ProposalSettingType_Address: + return "address" + default: + return fmt.Sprintf("unknown(%d)", t) + } +} + // Challenge states type ChallengeState uint8 diff --git a/bindings/utils/state/pdao.go b/bindings/utils/state/pdao.go index 30b81234b..9a988537b 100644 --- a/bindings/utils/state/pdao.go +++ b/bindings/utils/state/pdao.go @@ -216,7 +216,7 @@ func fixupPdaoProposalDetails(rp *rocketpool.RocketPool, rawDetails *protocolDao details.ChallengeWindow = time.Second * time.Duration(rawDetails.ChallengeWindow.Uint64()) var err error - details.PayloadStr, err = protocol.GetProposalPayloadString(rp, rawDetails.Payload, opts) + details.PayloadStr, details.MultiSettings, err = protocol.ParseProposalPayload(rp, rawDetails.Payload, opts) if err != nil { details.PayloadStr = fmt.Sprintf("", err.Error()) } diff --git a/rocketpool-cli/pdao/commands.go b/rocketpool-cli/pdao/commands.go index 6b11403ef..edbe0e057 100644 --- a/rocketpool-cli/pdao/commands.go +++ b/rocketpool-cli/pdao/commands.go @@ -6,7 +6,6 @@ import ( "github.com/urfave/cli/v3" - 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/rocketpool-cli/cli" ) @@ -557,6 +556,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -570,7 +573,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingAuctionIsCreateLotEnabled(value, c.Bool("yes")) + return proposeSettingAuctionIsCreateLotEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -586,6 +589,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -599,7 +606,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingAuctionIsBidOnLotEnabled(value, c.Bool("yes")) + return proposeSettingAuctionIsBidOnLotEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -619,6 +626,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -632,7 +643,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingAuctionLotMinimumEthValue(value, c.Bool("yes")) + return proposeSettingAuctionLotMinimumEthValue(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -652,6 +663,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -665,7 +680,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingAuctionLotMaximumEthValue(value, c.Bool("yes")) + return proposeSettingAuctionLotMaximumEthValue(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -681,6 +696,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -694,7 +713,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingAuctionLotDuration(value, c.Bool("yes")) + return proposeSettingAuctionLotDuration(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -714,6 +733,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -727,7 +750,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingAuctionLotStartingPriceRatio(value, c.Bool("yes")) + return proposeSettingAuctionLotStartingPriceRatio(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -747,6 +770,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -760,7 +787,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingAuctionLotReservePriceRatio(value, c.Bool("yes")) + return proposeSettingAuctionLotReservePriceRatio(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -784,6 +811,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -797,7 +828,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingDepositIsDepositingEnabled(value, c.Bool("yes")) + return proposeSettingDepositIsDepositingEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -813,6 +844,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -826,7 +861,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingDepositAreDepositAssignmentsEnabled(value, c.Bool("yes")) + return proposeSettingDepositAreDepositAssignmentsEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -846,6 +881,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -859,7 +898,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingDepositMinimumDeposit(value, c.Bool("yes")) + return proposeSettingDepositMinimumDeposit(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -879,6 +918,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -892,7 +935,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingDepositMaximumDepositPoolSize(value, c.Bool("yes")) + return proposeSettingDepositMaximumDepositPoolSize(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -908,6 +951,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -921,7 +968,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingDepositMaximumAssignmentsPerDeposit(value, c.Bool("yes")) + return proposeSettingDepositMaximumAssignmentsPerDeposit(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -937,6 +984,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -950,7 +1001,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingDepositMaximumSocialisedAssignmentsPerDeposit(value, c.Bool("yes")) + return proposeSettingDepositMaximumSocialisedAssignmentsPerDeposit(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -970,6 +1021,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -983,7 +1038,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingDepositDepositFee(value, c.Bool("yes")) + return proposeSettingDepositDepositFee(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -999,6 +1054,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { // Validate args @@ -1011,7 +1070,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingDepositExpressQueueRate(value, c.Bool("yes")) + return proposeSettingDepositExpressQueueRate(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1027,6 +1086,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { // Validate args @@ -1039,7 +1102,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingDepositExpressQueueTicketsBaseProvision(value, c.Bool("yes")) + return proposeSettingDepositExpressQueueTicketsBaseProvision(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1063,6 +1126,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1076,7 +1143,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMinipoolIsSubmitWithdrawableEnabled(value, c.Bool("yes")) + return proposeSettingMinipoolIsSubmitWithdrawableEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1092,6 +1159,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1105,7 +1176,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMinipoolLaunchTimeout(value, c.Bool("yes")) + return proposeSettingMinipoolLaunchTimeout(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1121,6 +1192,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1134,7 +1209,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMinipoolIsBondReductionEnabled(value, c.Bool("yes")) + return proposeSettingMinipoolIsBondReductionEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1150,6 +1225,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1163,7 +1242,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMinipoolMaximumCount(value, c.Bool("yes")) + return proposeSettingMinipoolMaximumCount(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1179,6 +1258,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1192,7 +1275,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMinipoolUserDistributeWindowStart(value, c.Bool("yes")) + return proposeSettingMinipoolUserDistributeWindowStart(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1208,6 +1291,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1221,7 +1308,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMinipoolUserDistributeWindowLength(value, c.Bool("yes")) + return proposeSettingMinipoolUserDistributeWindowLength(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1249,6 +1336,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1262,7 +1353,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkOracleDaoConsensusThreshold(value, c.Bool("yes")) + return proposeSettingNetworkOracleDaoConsensusThreshold(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1282,6 +1373,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1295,7 +1390,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkNodePenaltyThreshold(value, c.Bool("yes")) + return proposeSettingNetworkNodePenaltyThreshold(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1315,6 +1410,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1328,7 +1427,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkPerPenaltyRate(value, c.Bool("yes")) + return proposeSettingNetworkPerPenaltyRate(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1344,6 +1443,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1357,7 +1460,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkIsSubmitBalancesEnabled(value, c.Bool("yes")) + return proposeSettingNetworkIsSubmitBalancesEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1373,6 +1476,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1386,7 +1493,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkSubmitBalancesFrequency(value, c.Bool("yes")) + return proposeSettingNetworkSubmitBalancesFrequency(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1402,6 +1509,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1415,7 +1526,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkIsSubmitPricesEnabled(value, c.Bool("yes")) + return proposeSettingNetworkIsSubmitPricesEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1431,6 +1542,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1444,7 +1559,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkSubmitPricesFrequency(value, c.Bool("yes")) + return proposeSettingNetworkSubmitPricesFrequency(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1464,6 +1579,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1477,7 +1596,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkMinimumNodeFee(value, c.Bool("yes")) + return proposeSettingNetworkMinimumNodeFee(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1497,6 +1616,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1510,7 +1633,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkTargetNodeFee(value, c.Bool("yes")) + return proposeSettingNetworkTargetNodeFee(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1530,6 +1653,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1543,7 +1670,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkMaximumNodeFee(value, c.Bool("yes")) + return proposeSettingNetworkMaximumNodeFee(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1563,6 +1690,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1576,7 +1707,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkNodeFeeDemandRange(value, c.Bool("yes")) + return proposeSettingNetworkNodeFeeDemandRange(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1596,6 +1727,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1609,7 +1744,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkTargetRethCollateralRate(value, c.Bool("yes")) + return proposeSettingNetworkTargetRethCollateralRate(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1625,6 +1760,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1638,7 +1777,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNetworkIsSubmitRewardsEnabled(value, c.Bool("yes")) + return proposeSettingNetworkIsSubmitRewardsEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1654,6 +1793,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, &cli.StringFlag{ Name: "addressList, a", Usage: "One or more addresses, separated by commas with no spaces", @@ -1666,7 +1809,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { return err } // Run - return setAllowListedControllers(c.String("addressList"), c.Bool("yes")) + return setAllowListedControllers(c.String("addressList"), c.Bool("yes"), c.String("to-json")) }, }, @@ -1686,6 +1829,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1699,7 +1846,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNodeCommissionShare(value, c.Bool("yes")) + return proposeSettingNodeCommissionShare(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1719,6 +1866,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1732,7 +1883,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNodeCommissionShareSecurityCouncilAdder(value, c.Bool("yes")) + return proposeSettingNodeCommissionShareSecurityCouncilAdder(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1752,6 +1903,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1765,7 +1920,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingVoterShare(value, c.Bool("yes")) + return proposeSettingVoterShare(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1785,6 +1940,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1798,7 +1957,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingPDAOShare(value, c.Bool("yes")) + return proposeSettingPDAOShare(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1818,6 +1977,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1831,7 +1994,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeMaxNodeShareSecurityCouncilAdder(value, c.Bool("yes")) + return proposeMaxNodeShareSecurityCouncilAdder(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1851,6 +2014,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1864,7 +2031,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeMaxRethBalanceDelta(value, c.Bool("yes")) + return proposeMaxRethBalanceDelta(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1888,6 +2055,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1901,7 +2072,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNodeIsRegistrationEnabled(value, c.Bool("yes")) + return proposeSettingNodeIsRegistrationEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1917,6 +2088,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1930,7 +2105,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNodeIsSmoothingPoolRegistrationEnabled(value, c.Bool("yes")) + return proposeSettingNodeIsSmoothingPoolRegistrationEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1946,6 +2121,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -1959,7 +2138,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNodeIsDepositingEnabled(value, c.Bool("yes")) + return proposeSettingNodeIsDepositingEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -1975,71 +2154,9 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, - }, - Action: func(ctx context.Context, c *cli.Command) error { - - // Validate args - if err := cliutils.ValidateArgCount(c, 1); err != nil { - return err - } - value, err := cliutils.ValidateBool("value", c.Args().Get(0)) - if err != nil { - return err - } - - // Run - return proposeSettingNodeAreVacantMinipoolsEnabled(value, c.Bool("yes")) - - }, - }, - - { - Name: "minimum-per-minipool-stake", - Aliases: []string{"minpms"}, - 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{ - 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", - Aliases: []string{"y"}, - Usage: "Automatically confirm all interactive questions", - }, - }, - Action: func(ctx context.Context, c *cli.Command) error { - - // Validate args - if err := cliutils.ValidateArgCount(c, 1); err != nil { - return err - } - value, err := cliutils.ValidateFloat(c.Bool("raw"), "value", c.Args().Get(0), false, c.Bool("yes")) - if err != nil { - return err - } - - // Run - return proposeSettingNodeMinimumPerMinipoolStake(value, c.Bool("yes")) - - }, - }, - - { - Name: "maximum-per-minipool-stake", - Aliases: []string{"maxpms"}, - 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{ - 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", - Aliases: []string{"y"}, - Usage: "Automatically confirm all interactive questions", + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2048,13 +2165,13 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { if err := cliutils.ValidateArgCount(c, 1); err != nil { return err } - value, err := cliutils.ValidateFloat(c.Bool("raw"), "value", c.Args().Get(0), false, c.Bool("yes")) + value, err := cliutils.ValidateBool("value", c.Args().Get(0)) if err != nil { return err } // Run - return proposeSettingNodeMaximumPerMinipoolStake(value, c.Bool("yes")) + return proposeSettingNodeAreVacantMinipoolsEnabled(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2074,6 +2191,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2087,7 +2208,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNodeMinimumLegacyRplStake(value, c.Bool("yes")) + return proposeSettingNodeMinimumLegacyRplStake(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2107,6 +2228,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2120,7 +2245,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingReducedBond(value, c.Bool("yes")) + return proposeSettingReducedBond(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2136,6 +2261,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2149,7 +2278,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingNodeUnstakingPeriod(value, c.Bool("yes")) + return proposeSettingNodeUnstakingPeriod(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2173,6 +2302,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2186,7 +2319,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsVotePhase1Time(value, c.Bool("yes")) + return proposeSettingProposalsVotePhase1Time(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2202,6 +2335,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2215,7 +2352,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsVotePhase2Time(value, c.Bool("yes")) + return proposeSettingProposalsVotePhase2Time(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2231,6 +2368,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2244,7 +2385,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsVoteDelayTime(value, c.Bool("yes")) + return proposeSettingProposalsVoteDelayTime(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2260,6 +2401,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2273,7 +2418,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsExecuteTime(value, c.Bool("yes")) + return proposeSettingProposalsExecuteTime(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2293,6 +2438,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2306,7 +2455,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsProposalBond(value, c.Bool("yes")) + return proposeSettingProposalsProposalBond(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2326,6 +2475,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2339,7 +2492,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsChallengeBond(value, c.Bool("yes")) + return proposeSettingProposalsChallengeBond(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2355,6 +2508,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2368,7 +2525,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsChallengePeriod(value, c.Bool("yes")) + return proposeSettingProposalsChallengePeriod(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2388,6 +2545,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2401,7 +2562,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsQuorum(value, c.Bool("yes")) + return proposeSettingProposalsQuorum(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2421,6 +2582,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2434,7 +2599,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsVetoQuorum(value, c.Bool("yes")) + return proposeSettingProposalsVetoQuorum(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2450,6 +2615,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2463,7 +2632,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingProposalsMaxBlockAge(value, c.Bool("yes")) + return proposeSettingProposalsMaxBlockAge(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2487,6 +2656,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2500,7 +2673,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingRewardsIntervalPeriods(value, c.Bool("yes")) + return proposeSettingRewardsIntervalPeriods(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2528,6 +2701,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2541,7 +2718,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingSecurityMembersQuorum(value, c.Bool("yes")) + return proposeSettingSecurityMembersQuorum(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2557,6 +2734,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2570,7 +2751,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingSecurityMembersLeaveTime(value, c.Bool("yes")) + return proposeSettingSecurityMembersLeaveTime(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2586,6 +2767,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2599,7 +2784,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingSecurityProposalVoteTime(value, c.Bool("yes")) + return proposeSettingSecurityProposalVoteTime(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2615,6 +2800,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2628,7 +2817,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingSecurityProposalExecuteTime(value, c.Bool("yes")) + return proposeSettingSecurityProposalExecuteTime(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2644,6 +2833,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2657,7 +2850,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingSecurityProposalActionTime(value, c.Bool("yes")) + return proposeSettingSecurityProposalActionTime(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2681,6 +2874,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2694,7 +2891,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMegapoolTimeBeforeDissolve(value, c.Bool("yes")) + return proposeSettingMegapoolTimeBeforeDissolve(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2714,6 +2911,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2727,7 +2928,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMaximumMegapoolEthPenalty(value, c.Bool("yes")) + return proposeSettingMaximumMegapoolEthPenalty(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2743,6 +2944,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2756,7 +2961,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMegapoolNotifyThreshold(value, c.Bool("yes")) + return proposeSettingMegapoolNotifyThreshold(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2776,6 +2981,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2789,7 +2998,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMegapoolLateNotifyFine(value, c.Bool("yes")) + return proposeSettingMegapoolLateNotifyFine(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2805,6 +3014,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2818,7 +3031,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMegapoolDissolvePenalty(value, c.Bool("yes")) + return proposeSettingMegapoolDissolvePenalty(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2834,6 +3047,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { // Validate args @@ -2846,7 +3063,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMegapoolUserDistributeDelay(value, c.Bool("yes")) + return proposeSettingMegapoolUserDistributeDelay(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2862,6 +3079,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { // Validate args @@ -2874,7 +3095,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingMegapoolUserDistributeDelayWithShortfall(value, c.Bool("yes")) + return proposeSettingMegapoolUserDistributeDelayWithShortfall(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2894,6 +3115,10 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { Aliases: []string{"y"}, Usage: "Automatically confirm all interactive questions", }, + &cli.StringFlag{ + Name: "to-json", + Usage: "Write this setting to a JSON file instead of submitting a proposal (creates the file or appends to it)", + }, }, Action: func(ctx context.Context, c *cli.Command) error { @@ -2907,7 +3132,7 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { } // Run - return proposeSettingPenaltyThreshold(value, c.Bool("yes")) + return proposeSettingPenaltyThreshold(value, c.Bool("yes"), c.String("to-json")) }, }, @@ -2915,6 +3140,41 @@ func RegisterCommands(app *cli.Command, name string, aliases []string) { }, }, }, + + { + Name: "submit-batch", + Aliases: []string{"sb"}, + Usage: "Submit a single proposal that changes multiple Protocol DAO settings from a JSON file created with --to-json", + UsageText: "rocketpool pdao propose submit-batch", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "yes", + Aliases: []string{"y"}, + Usage: "Automatically confirm all interactive questions", + }, + &cli.StringFlag{ + Name: "file", + Aliases: []string{"f"}, + Usage: "The JSON file of setting changes created with --to-json. If omitted, you will be prompted.", + }, + &cli.StringFlag{ + Name: "message", + Aliases: []string{"m"}, + Usage: "A custom proposal message (no blank spaces). If omitted, you will be prompted.", + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + + // Validate args + if err := cliutils.ValidateArgCount(c, 0); err != nil { + return err + } + + // Run + return submitBatch(c.String("file"), c.String("message"), c.Bool("yes")) + + }, + }, }, }, diff --git a/rocketpool-cli/pdao/execute-proposal.go b/rocketpool-cli/pdao/execute-proposal.go index e5b5f0f20..1555f7f4f 100644 --- a/rocketpool-cli/pdao/execute-proposal.go +++ b/rocketpool-cli/pdao/execute-proposal.go @@ -6,7 +6,6 @@ import ( "github.com/rocket-pool/smartnode/bindings/transactions/gaslimit" "github.com/rocket-pool/smartnode/bindings/types" - "github.com/rocket-pool/smartnode/bindings/utils/strings" cliutils "github.com/rocket-pool/smartnode/rocketpool-cli/cli" "github.com/rocket-pool/smartnode/rocketpool-cli/cli/prompt" @@ -78,11 +77,8 @@ func executeProposal(proposal string, yes bool) error { options := make([]string, len(executableProposals)+1) options[0] = "All available proposals" for pi, proposal := range executableProposals { - if len(proposal.Message) > 200 { - proposal.Message = proposal.Message[:200] - } - proposal.Message = strings.Sanitize(proposal.Message) - options[pi+1] = fmt.Sprintf("proposal %d (message: '%s', payload: %s)", proposal.ID, proposal.Message, proposal.PayloadStr) + message, payload := proposalDisplayText(proposal) + options[pi+1] = fmt.Sprintf("proposal %d (message: '%s', payload: %s)", proposal.ID, message, payload) } selected, _ := prompt.Select("Please select a proposal to execute:", options) @@ -95,6 +91,10 @@ func executeProposal(proposal string, yes bool) error { } + if len(selectedProposals) == 1 { + printSelectedMultiSettings(selectedProposals[0].MultiSettings) + } + // Get the total gas limit estimate var gasLimits gaslimit.Limits for _, proposal := range selectedProposals { diff --git a/rocketpool-cli/pdao/proposals.go b/rocketpool-cli/pdao/proposals.go index 3f7633dc4..a159fdd9f 100644 --- a/rocketpool-cli/pdao/proposals.go +++ b/rocketpool-cli/pdao/proposals.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/rocket-pool/smartnode/bindings/dao/protocol" "github.com/rocket-pool/smartnode/bindings/types" utilsStrings "github.com/rocket-pool/smartnode/bindings/utils/strings" @@ -79,6 +80,9 @@ func getProposals(stateFilter string) error { } proposal.Message = utilsStrings.Sanitize(proposal.Message) fmt.Printf("%d: %s - Proposed by: %s\n", proposal.ID, proposal.Message, proposal.ProposerAddress) + if summary := formatMultiSettingsSummary(proposal.MultiSettings); summary != "" { + fmt.Printf(" %s\n", summary) + } } count += len(proposals) @@ -132,8 +136,7 @@ func getProposal(id uint64) error { // Main details fmt.Printf("Proposal ID: %d\n", proposal.ID) fmt.Printf("Message: %s\n", proposal.Message) - fmt.Printf("Payload: %s\n", proposal.PayloadStr) - fmt.Printf("Payload (bytes): %s\n", hex.EncodeToString(proposal.Payload)) + printProposalPayload(proposal.ProtocolDaoProposalDetails) fmt.Printf("Proposed by: %s\n", proposal.ProposerAddress.Hex()) fmt.Printf("Created at: %s, %s\n", proposal.CreatedTime.Format(time.RFC822), getTimeDifference(proposal.CreatedTime)) fmt.Printf("State: %s\n", types.ProtocolDaoProposalStates[proposal.State]) @@ -210,3 +213,52 @@ func getTimeDifference(t time.Time) string { return message } + +func printProposalPayload(proposal protocol.ProtocolDaoProposalDetails) { + if len(proposal.MultiSettings) > 0 { + fmt.Printf("Payload: proposalSettingMulti (%d settings)\n", len(proposal.MultiSettings)) + printMultiSettings(proposal.MultiSettings, " ") + } else { + fmt.Printf("Payload: %s\n", proposal.PayloadStr) + } + fmt.Printf("Payload (bytes): %s\n", hex.EncodeToString(proposal.Payload)) +} + +func printMultiSettings(settings []protocol.DecodedProposalSetting, indent string) { + for i, setting := range settings { + fmt.Printf("%s%d. %s / %s = %s (%s)\n", indent, i+1, setting.Contract, setting.Path, setting.Value, setting.Type) + } +} + +func formatMultiSettingsSummary(settings []protocol.DecodedProposalSetting) string { + if len(settings) == 0 { + return "" + } + return protocol.FormatProposalSettingMulti(settings) +} + +func proposalDisplayText(proposal api.PDAOProposalWithNodeVoteDirection) (message string, payload string) { + message = proposal.Message + if len(message) > 200 { + message = message[:200] + } + message = utilsStrings.Sanitize(message) + + payload = proposal.PayloadStr + if len(proposal.MultiSettings) > 0 { + payload = formatMultiSettingsSummary(proposal.MultiSettings) + } + if len(payload) > 200 { + payload = payload[:200] + "..." + } + return message, payload +} + +func printSelectedMultiSettings(settings []protocol.DecodedProposalSetting) { + if len(settings) == 0 { + return + } + fmt.Printf("This proposal updates %d settings:\n", len(settings)) + printMultiSettings(settings, " ") + fmt.Println() +} diff --git a/rocketpool-cli/pdao/propose-settings-json.go b/rocketpool-cli/pdao/propose-settings-json.go new file mode 100644 index 000000000..9b79f58e4 --- /dev/null +++ b/rocketpool-cli/pdao/propose-settings-json.go @@ -0,0 +1,94 @@ +package pdao + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/rocket-pool/smartnode/bindings/settings/protocol" + "github.com/rocket-pool/smartnode/shared/types/api" +) + +// writeSettingToBatchJSON creates or appends a setting change to a batch proposal JSON file. +// If the same contract+setting is already present, its value is replaced. +func writeSettingToBatchJSON(path string, contract string, setting string, value string) error { + settingType, err := protocol.GetProposalSettingType(contract, setting) + if err != nil { + return err + } + + entry := api.PDAOBatchSetting{ + Contract: contract, + Setting: setting, + Type: protocol.ProposalSettingTypeName(settingType), + Value: value, + } + + settings, err := readBatchSettingsFileOptional(path, true) + if err != nil { + return err + } + + replaced := false + for i, existing := range settings { + if existing.Contract == contract && existing.Setting == setting { + settings[i] = entry + replaced = true + break + } + } + if !replaced { + settings = append(settings, entry) + } + + if err := writeBatchSettingsFile(path, settings); err != nil { + return err + } + + if replaced { + fmt.Printf("Updated %s in %s (%d setting(s) total).\n", setting, path, len(settings)) + } else { + fmt.Printf("Added %s to %s (%d setting(s) total).\n", setting, path, len(settings)) + } + return nil +} + +func readBatchSettingsFile(path string) ([]api.PDAOBatchSetting, error) { + return readBatchSettingsFileOptional(path, false) +} + +func readBatchSettingsFileOptional(path string, allowMissing bool) ([]api.PDAOBatchSetting, error) { + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + if allowMissing { + return nil, nil + } + return nil, fmt.Errorf("JSON file %s does not exist", path) + } + return nil, fmt.Errorf("could not read JSON file %s: %w", path, err) + } + trimmed := strings.TrimSpace(string(data)) + if trimmed == "" { + return nil, nil + } + + var settings []api.PDAOBatchSetting + if err := json.Unmarshal([]byte(trimmed), &settings); err != nil { + return nil, fmt.Errorf("could not parse JSON file %s: %w", path, err) + } + return settings, nil +} + +func writeBatchSettingsFile(path string, settings []api.PDAOBatchSetting) error { + out, err := json.MarshalIndent(settings, "", " ") + if err != nil { + return fmt.Errorf("could not encode JSON file %s: %w", path, err) + } + out = append(out, '\n') + if err := os.WriteFile(path, out, 0644); err != nil { + return fmt.Errorf("could not write JSON file %s: %w", path, err) + } + return nil +} diff --git a/rocketpool-cli/pdao/propose-settings-json_test.go b/rocketpool-cli/pdao/propose-settings-json_test.go new file mode 100644 index 000000000..60b8f8670 --- /dev/null +++ b/rocketpool-cli/pdao/propose-settings-json_test.go @@ -0,0 +1,115 @@ +package pdao + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/rocket-pool/smartnode/bindings/settings/protocol" + "github.com/rocket-pool/smartnode/shared/types/api" +) + +func TestWriteSettingToBatchJSON_CreateAndAppend(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "batch.json") + + err := writeSettingToBatchJSON(path, protocol.AuctionSettingsContractName, protocol.CreateLotEnabledSettingPath, "true") + if err != nil { + t.Fatalf("create: %v", err) + } + + err = writeSettingToBatchJSON(path, protocol.DepositSettingsContractName, protocol.MinimumDepositSettingPath, "1000000000000000000") + if err != nil { + t.Fatalf("append: %v", err) + } + + settings, err := readBatchSettingsFile(path) + if err != nil { + t.Fatalf("read: %v", err) + } + if len(settings) != 2 { + t.Fatalf("got %d settings, want 2", len(settings)) + } + if settings[0].Setting != protocol.CreateLotEnabledSettingPath || settings[0].Type != protocol.ProposalSettingTypeNameBool { + t.Fatalf("unexpected first setting: %+v", settings[0]) + } + if settings[1].Setting != protocol.MinimumDepositSettingPath || settings[1].Type != protocol.ProposalSettingTypeNameUint256 { + t.Fatalf("unexpected second setting: %+v", settings[1]) + } +} + +func TestWriteSettingToBatchJSON_ReplaceDuplicate(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "batch.json") + + if err := writeSettingToBatchJSON(path, protocol.AuctionSettingsContractName, protocol.CreateLotEnabledSettingPath, "true"); err != nil { + t.Fatal(err) + } + if err := writeSettingToBatchJSON(path, protocol.AuctionSettingsContractName, protocol.CreateLotEnabledSettingPath, "false"); err != nil { + t.Fatal(err) + } + + settings, err := readBatchSettingsFile(path) + if err != nil { + t.Fatal(err) + } + if len(settings) != 1 { + t.Fatalf("got %d settings, want 1", len(settings)) + } + if settings[0].Value != "false" { + t.Fatalf("got value %q, want false", settings[0].Value) + } +} + +func TestWriteSettingToBatchJSON_RejectsAddressList(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "batch.json") + + err := writeSettingToBatchJSON(path, protocol.NetworkSettingsContractName, protocol.NetworkAllowListedControllersPath, "0x1") + if err == nil { + t.Fatal("expected error for address list setting") + } +} + +func TestReadBatchSettingsFile_MissingRequired(t *testing.T) { + _, err := readBatchSettingsFile(filepath.Join(t.TempDir(), "missing.json")) + if err == nil { + t.Fatal("expected missing file error") + } +} + +func TestReadBatchSettingsFile_InvalidJSON(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "bad.json") + if err := os.WriteFile(path, []byte("{not-an-array}"), 0644); err != nil { + t.Fatal(err) + } + if _, err := readBatchSettingsFile(path); err == nil { + t.Fatal("expected parse error") + } +} + +func TestWriteBatchSettingsFile_RoundTrip(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "roundtrip.json") + + want := []api.PDAOBatchSetting{ + {Contract: "a", Setting: "b", Type: "bool", Value: "true"}, + } + if err := writeBatchSettingsFile(path, want); err != nil { + t.Fatal(err) + } + + data, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + var got []api.PDAOBatchSetting + if err := json.Unmarshal(data, &got); err != nil { + t.Fatal(err) + } + if len(got) != 1 || got[0] != want[0] { + t.Fatalf("got %+v, want %+v", got, want) + } +} diff --git a/rocketpool-cli/pdao/propose-settings.go b/rocketpool-cli/pdao/propose-settings.go index 1efe0dfd7..7b3fef5e8 100644 --- a/rocketpool-cli/pdao/propose-settings.go +++ b/rocketpool-cli/pdao/propose-settings.go @@ -7,7 +7,6 @@ import ( "github.com/rocket-pool/smartnode/bindings/settings/protocol" - protocol131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/protocol" cliutils "github.com/rocket-pool/smartnode/rocketpool-cli/cli" "github.com/rocket-pool/smartnode/shared/math" "github.com/rocket-pool/smartnode/shared/services/gas" @@ -16,378 +15,372 @@ import ( "github.com/rocket-pool/smartnode/rocketpool-cli/cli/prompt" ) -func proposeSettingAuctionIsCreateLotEnabled(value bool, yes bool) error { +func proposeSettingAuctionIsCreateLotEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.AuctionSettingsContractName, protocol.CreateLotEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.AuctionSettingsContractName, protocol.CreateLotEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingAuctionIsBidOnLotEnabled(value bool, yes bool) error { +func proposeSettingAuctionIsBidOnLotEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.AuctionSettingsContractName, protocol.BidOnLotEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.AuctionSettingsContractName, protocol.BidOnLotEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingAuctionLotMinimumEthValue(value *big.Int, yes bool) error { +func proposeSettingAuctionLotMinimumEthValue(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotMinimumEthValueSettingPath, trueValue, yes) + return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotMinimumEthValueSettingPath, trueValue, yes, toJson) } -func proposeSettingAuctionLotMaximumEthValue(value *big.Int, yes bool) error { +func proposeSettingAuctionLotMaximumEthValue(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotMaximumEthValueSettingPath, trueValue, yes) + return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotMaximumEthValueSettingPath, trueValue, yes, toJson) } -func proposeSettingAuctionLotDuration(value time.Duration, yes bool) error { +func proposeSettingAuctionLotDuration(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotDurationSettingPath, trueValue, yes) + return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotDurationSettingPath, trueValue, yes, toJson) } -func proposeSettingAuctionLotStartingPriceRatio(value *big.Int, yes bool) error { +func proposeSettingAuctionLotStartingPriceRatio(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotStartingPriceRatioSettingPath, trueValue, yes) + return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotStartingPriceRatioSettingPath, trueValue, yes, toJson) } -func proposeSettingAuctionLotReservePriceRatio(value *big.Int, yes bool) error { +func proposeSettingAuctionLotReservePriceRatio(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotReservePriceRatioSettingPath, trueValue, yes) + return proposeSetting(protocol.AuctionSettingsContractName, protocol.LotReservePriceRatioSettingPath, trueValue, yes, toJson) } -func proposeSettingDepositIsDepositingEnabled(value bool, yes bool) error { +func proposeSettingDepositIsDepositingEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.DepositSettingsContractName, protocol.DepositEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.DepositSettingsContractName, protocol.DepositEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingDepositAreDepositAssignmentsEnabled(value bool, yes bool) error { +func proposeSettingDepositAreDepositAssignmentsEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.DepositSettingsContractName, protocol.AssignDepositsEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.DepositSettingsContractName, protocol.AssignDepositsEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingDepositMinimumDeposit(value *big.Int, yes bool) error { +func proposeSettingDepositMinimumDeposit(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.DepositSettingsContractName, protocol.MinimumDepositSettingPath, trueValue, yes) + return proposeSetting(protocol.DepositSettingsContractName, protocol.MinimumDepositSettingPath, trueValue, yes, toJson) } -func proposeSettingDepositMaximumDepositPoolSize(value *big.Int, yes bool) error { +func proposeSettingDepositMaximumDepositPoolSize(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.DepositSettingsContractName, protocol.MaximumDepositPoolSizeSettingPath, trueValue, yes) + return proposeSetting(protocol.DepositSettingsContractName, protocol.MaximumDepositPoolSizeSettingPath, trueValue, yes, toJson) } -func proposeSettingDepositMaximumAssignmentsPerDeposit(value uint64, yes bool) error { +func proposeSettingDepositMaximumAssignmentsPerDeposit(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.DepositSettingsContractName, protocol.MaximumDepositAssignmentsSettingPath, trueValue, yes) + return proposeSetting(protocol.DepositSettingsContractName, protocol.MaximumDepositAssignmentsSettingPath, trueValue, yes, toJson) } -func proposeSettingDepositMaximumSocialisedAssignmentsPerDeposit(value uint64, yes bool) error { +func proposeSettingDepositMaximumSocialisedAssignmentsPerDeposit(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.DepositSettingsContractName, protocol.MaximumSocializedDepositAssignmentsSettingPath, trueValue, yes) + return proposeSetting(protocol.DepositSettingsContractName, protocol.MaximumSocializedDepositAssignmentsSettingPath, trueValue, yes, toJson) } -func proposeSettingDepositExpressQueueRate(value uint64, yes bool) error { +func proposeSettingDepositExpressQueueRate(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.DepositSettingsContractName, protocol.ExpressQueueRatePath, trueValue, yes) + return proposeSetting(protocol.DepositSettingsContractName, protocol.ExpressQueueRatePath, trueValue, yes, toJson) } -func proposeSettingDepositExpressQueueTicketsBaseProvision(value uint64, yes bool) error { +func proposeSettingDepositExpressQueueTicketsBaseProvision(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.DepositSettingsContractName, protocol.ExpressQueueTicketsBaseProvisionPath, trueValue, yes) + return proposeSetting(protocol.DepositSettingsContractName, protocol.ExpressQueueTicketsBaseProvisionPath, trueValue, yes, toJson) } -func proposeSettingDepositDepositFee(value *big.Int, yes bool) error { +func proposeSettingDepositDepositFee(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.DepositSettingsContractName, protocol.DepositFeeSettingPath, trueValue, yes) + return proposeSetting(protocol.DepositSettingsContractName, protocol.DepositFeeSettingPath, trueValue, yes, toJson) } -func proposeSettingMinipoolIsSubmitWithdrawableEnabled(value bool, yes bool) error { +func proposeSettingMinipoolIsSubmitWithdrawableEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MinipoolSubmitWithdrawableEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MinipoolSubmitWithdrawableEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingMinipoolLaunchTimeout(value time.Duration, yes bool) error { +func proposeSettingMinipoolLaunchTimeout(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MinipoolLaunchTimeoutSettingPath, trueValue, yes) + return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MinipoolLaunchTimeoutSettingPath, trueValue, yes, toJson) } -func proposeSettingMinipoolIsBondReductionEnabled(value bool, yes bool) error { +func proposeSettingMinipoolIsBondReductionEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.MinipoolSettingsContractName, protocol.BondReductionEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.MinipoolSettingsContractName, protocol.BondReductionEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingMinipoolMaximumCount(value uint64, yes bool) error { +func proposeSettingMinipoolMaximumCount(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MaximumMinipoolCountSettingPath, trueValue, yes) + return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MaximumMinipoolCountSettingPath, trueValue, yes, toJson) } -func proposeSettingMinipoolUserDistributeWindowStart(value time.Duration, yes bool) error { +func proposeSettingMinipoolUserDistributeWindowStart(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MinipoolUserDistributeWindowStartSettingPath, trueValue, yes) + return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MinipoolUserDistributeWindowStartSettingPath, trueValue, yes, toJson) } -func proposeSettingMinipoolUserDistributeWindowLength(value time.Duration, yes bool) error { +func proposeSettingMinipoolUserDistributeWindowLength(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MinipoolUserDistributeWindowLengthSettingPath, trueValue, yes) + return proposeSetting(protocol.MinipoolSettingsContractName, protocol.MinipoolUserDistributeWindowLengthSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkOracleDaoConsensusThreshold(value *big.Int, yes bool) error { +func proposeSettingNetworkOracleDaoConsensusThreshold(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NodeConsensusThresholdSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NodeConsensusThresholdSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkNodePenaltyThreshold(value *big.Int, yes bool) error { +func proposeSettingNetworkNodePenaltyThreshold(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkPenaltyThresholdSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkPenaltyThresholdSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkPerPenaltyRate(value *big.Int, yes bool) error { +func proposeSettingNetworkPerPenaltyRate(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkPenaltyPerRateSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkPenaltyPerRateSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkIsSubmitBalancesEnabled(value bool, yes bool) error { +func proposeSettingNetworkIsSubmitBalancesEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitBalancesEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitBalancesEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkSubmitBalancesFrequency(value time.Duration, yes bool) error { +func proposeSettingNetworkSubmitBalancesFrequency(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitBalancesFrequencySettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitBalancesFrequencySettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkIsSubmitPricesEnabled(value bool, yes bool) error { +func proposeSettingNetworkIsSubmitPricesEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitPricesEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitPricesEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkSubmitPricesFrequency(value time.Duration, yes bool) error { +func proposeSettingNetworkSubmitPricesFrequency(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitPricesFrequencySettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitPricesFrequencySettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkMinimumNodeFee(value *big.Int, yes bool) error { +func proposeSettingNetworkMinimumNodeFee(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.MinimumNodeFeeSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.MinimumNodeFeeSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkTargetNodeFee(value *big.Int, yes bool) error { +func proposeSettingNetworkTargetNodeFee(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.TargetNodeFeeSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.TargetNodeFeeSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkMaximumNodeFee(value *big.Int, yes bool) error { +func proposeSettingNetworkMaximumNodeFee(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.MaximumNodeFeeSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.MaximumNodeFeeSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkNodeFeeDemandRange(value *big.Int, yes bool) error { +func proposeSettingNetworkNodeFeeDemandRange(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NodeFeeDemandRangeSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NodeFeeDemandRangeSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkTargetRethCollateralRate(value *big.Int, yes bool) error { +func proposeSettingNetworkTargetRethCollateralRate(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.TargetRethCollateralRateSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.TargetRethCollateralRateSettingPath, trueValue, yes, toJson) } -func proposeSettingNetworkIsSubmitRewardsEnabled(value bool, yes bool) error { +func proposeSettingNetworkIsSubmitRewardsEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitRewardsEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.SubmitRewardsEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingNodeIsRegistrationEnabled(value bool, yes bool) error { +func proposeSettingNodeIsRegistrationEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.NodeSettingsContractName, protocol.NodeRegistrationEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.NodeSettingsContractName, protocol.NodeRegistrationEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingNodeIsSmoothingPoolRegistrationEnabled(value bool, yes bool) error { +func proposeSettingNodeIsSmoothingPoolRegistrationEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.NodeSettingsContractName, protocol.SmoothingPoolRegistrationEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.NodeSettingsContractName, protocol.SmoothingPoolRegistrationEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingNodeIsDepositingEnabled(value bool, yes bool) error { +func proposeSettingNodeIsDepositingEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.NodeSettingsContractName, protocol.NodeDepositEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.NodeSettingsContractName, protocol.NodeDepositEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingNodeAreVacantMinipoolsEnabled(value bool, yes bool) error { +func proposeSettingNodeAreVacantMinipoolsEnabled(value bool, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.NodeSettingsContractName, protocol.VacantMinipoolsEnabledSettingPath, trueValue, yes) + return proposeSetting(protocol.NodeSettingsContractName, protocol.VacantMinipoolsEnabledSettingPath, trueValue, yes, toJson) } -func proposeSettingNodeMinimumPerMinipoolStake(value *big.Int, yes bool) error { +func proposeSettingNodeMinimumLegacyRplStake(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NodeSettingsContractName, protocol131.MinimumPerMinipoolStakeSettingPath, trueValue, yes) + return proposeSetting(protocol.NodeSettingsContractName, protocol.MinimumLegacyRplStakePath, trueValue, yes, toJson) } -func proposeSettingNodeMaximumPerMinipoolStake(value *big.Int, yes bool) error { +func proposeSettingReducedBond(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NodeSettingsContractName, protocol131.MaximumPerMinipoolStakeSettingPath, trueValue, yes) + return proposeSetting(protocol.NodeSettingsContractName, protocol.ReducedBondSettingPath, trueValue, yes, toJson) } -func proposeSettingNodeMinimumLegacyRplStake(value *big.Int, yes bool) error { - trueValue := value.String() - return proposeSetting(protocol.NodeSettingsContractName, protocol.MinimumLegacyRplStakePath, trueValue, yes) -} - -func proposeSettingReducedBond(value *big.Int, yes bool) error { - trueValue := value.String() - return proposeSetting(protocol.NodeSettingsContractName, protocol.ReducedBondSettingPath, trueValue, yes) -} - -func proposeSettingNodeUnstakingPeriod(value time.Duration, yes bool) error { +func proposeSettingNodeUnstakingPeriod(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.NodeSettingsContractName, protocol.NodeUnstakingPeriodSettingPath, trueValue, yes) + return proposeSetting(protocol.NodeSettingsContractName, protocol.NodeUnstakingPeriodSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsVotePhase1Time(value time.Duration, yes bool) error { +func proposeSettingProposalsVotePhase1Time(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.VotePhase1TimeSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.VotePhase1TimeSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsVotePhase2Time(value time.Duration, yes bool) error { +func proposeSettingProposalsVotePhase2Time(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.VotePhase2TimeSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.VotePhase2TimeSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsVoteDelayTime(value time.Duration, yes bool) error { +func proposeSettingProposalsVoteDelayTime(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.VoteDelayTimeSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.VoteDelayTimeSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsExecuteTime(value time.Duration, yes bool) error { +func proposeSettingProposalsExecuteTime(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ExecuteTimeSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ExecuteTimeSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsProposalBond(value *big.Int, yes bool) error { +func proposeSettingProposalsProposalBond(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ProposalBondSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ProposalBondSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsChallengeBond(value *big.Int, yes bool) error { +func proposeSettingProposalsChallengeBond(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ChallengeBondSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ChallengeBondSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsChallengePeriod(value time.Duration, yes bool) error { +func proposeSettingProposalsChallengePeriod(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ChallengePeriodSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ChallengePeriodSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsQuorum(value *big.Int, yes bool) error { +func proposeSettingProposalsQuorum(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ProposalQuorumSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ProposalQuorumSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsVetoQuorum(value *big.Int, yes bool) error { +func proposeSettingProposalsVetoQuorum(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ProposalVetoQuorumSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ProposalVetoQuorumSettingPath, trueValue, yes, toJson) } -func proposeSettingProposalsMaxBlockAge(value uint64, yes bool) error { +func proposeSettingProposalsMaxBlockAge(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ProposalMaxBlockAgeSettingPath, trueValue, yes) + return proposeSetting(protocol.ProposalsSettingsContractName, protocol.ProposalMaxBlockAgeSettingPath, trueValue, yes, toJson) } -func proposeSettingRewardsIntervalPeriods(value uint64, yes bool) error { +func proposeSettingRewardsIntervalPeriods(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.RewardsSettingsContractName, protocol.RewardsClaimIntervalPeriodsSettingPath, trueValue, yes) + return proposeSetting(protocol.RewardsSettingsContractName, protocol.RewardsClaimIntervalPeriodsSettingPath, trueValue, yes, toJson) } -func proposeSettingSecurityMembersQuorum(value *big.Int, yes bool) error { +func proposeSettingSecurityMembersQuorum(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityMembersQuorumSettingPath, trueValue, yes) + return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityMembersQuorumSettingPath, trueValue, yes, toJson) } -func proposeSettingSecurityMembersLeaveTime(value time.Duration, yes bool) error { +func proposeSettingSecurityMembersLeaveTime(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityMembersLeaveTimeSettingPath, trueValue, yes) + return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityMembersLeaveTimeSettingPath, trueValue, yes, toJson) } -func proposeSettingSecurityProposalVoteTime(value time.Duration, yes bool) error { +func proposeSettingSecurityProposalVoteTime(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityProposalVoteTimeSettingPath, trueValue, yes) + return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityProposalVoteTimeSettingPath, trueValue, yes, toJson) } -func proposeSettingSecurityProposalExecuteTime(value time.Duration, yes bool) error { +func proposeSettingSecurityProposalExecuteTime(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityProposalExecuteTimeSettingPath, trueValue, yes) + return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityProposalExecuteTimeSettingPath, trueValue, yes, toJson) } -func proposeSettingSecurityProposalActionTime(value time.Duration, yes bool) error { +func proposeSettingSecurityProposalActionTime(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityProposalActionTimeSettingPath, trueValue, yes) + return proposeSetting(protocol.SecuritySettingsContractName, protocol.SecurityProposalActionTimeSettingPath, trueValue, yes, toJson) } -func proposeSettingMegapoolTimeBeforeDissolve(value time.Duration, yes bool) error { +func proposeSettingMegapoolTimeBeforeDissolve(value time.Duration, yes bool, toJson string) error { trueValue := fmt.Sprint(uint64(value.Seconds())) - return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolTimeBeforeDissolveSettingsPath, trueValue, yes) + return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolTimeBeforeDissolveSettingsPath, trueValue, yes, toJson) } -func proposeSettingMaximumMegapoolEthPenalty(value *big.Int, yes bool) error { +func proposeSettingMaximumMegapoolEthPenalty(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolMaximumMegapoolEthPenaltyPath, trueValue, yes) + return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolMaximumMegapoolEthPenaltyPath, trueValue, yes, toJson) } -func proposeSettingMegapoolNotifyThreshold(value uint64, yes bool) error { +func proposeSettingMegapoolNotifyThreshold(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolNotifyThresholdPath, trueValue, yes) + return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolNotifyThresholdPath, trueValue, yes, toJson) } -func proposeSettingMegapoolLateNotifyFine(value *big.Int, yes bool) error { +func proposeSettingMegapoolLateNotifyFine(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolLateNotifyFinePath, trueValue, yes) + return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolLateNotifyFinePath, trueValue, yes, toJson) } -func proposeSettingMegapoolDissolvePenalty(value *big.Int, yes bool) error { +func proposeSettingMegapoolDissolvePenalty(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolDissolvePenaltyPath, trueValue, yes) + return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolDissolvePenaltyPath, trueValue, yes, toJson) } -func proposeSettingMegapoolUserDistributeDelay(value uint64, yes bool) error { +func proposeSettingMegapoolUserDistributeDelay(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolUserDistributeDelayPath, trueValue, yes) + return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolUserDistributeDelayPath, trueValue, yes, toJson) } -func proposeSettingMegapoolUserDistributeDelayWithShortfall(value uint64, yes bool) error { +func proposeSettingMegapoolUserDistributeDelayWithShortfall(value uint64, yes bool, toJson string) error { trueValue := fmt.Sprint(value) - return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolUserDistributeDelayShortfallPath, trueValue, yes) + return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolUserDistributeDelayShortfallPath, trueValue, yes, toJson) } -func proposeSettingPenaltyThreshold(value *big.Int, yes bool) error { +func proposeSettingPenaltyThreshold(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolPenaltyThreshold, trueValue, yes) + return proposeSetting(protocol.MegapoolSettingsContractName, protocol.MegapoolPenaltyThreshold, trueValue, yes, toJson) } -func proposeSettingNodeCommissionShare(value *big.Int, yes bool) error { +func proposeSettingNodeCommissionShare(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkNodeCommissionSharePath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkNodeCommissionSharePath, trueValue, yes, toJson) } -func proposeSettingNodeCommissionShareSecurityCouncilAdder(value *big.Int, yes bool) error { +func proposeSettingNodeCommissionShareSecurityCouncilAdder(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkNodeCommissionShareSecurityCouncilAdderPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkNodeCommissionShareSecurityCouncilAdderPath, trueValue, yes, toJson) } -func proposeSettingVoterShare(value *big.Int, yes bool) error { +func proposeSettingVoterShare(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkVoterSharePath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkVoterSharePath, trueValue, yes, toJson) } -func proposeSettingPDAOShare(value *big.Int, yes bool) error { +func proposeSettingPDAOShare(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkPDAOSharePath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkPDAOSharePath, trueValue, yes, toJson) } -func proposeMaxNodeShareSecurityCouncilAdder(value *big.Int, yes bool) error { +func proposeMaxNodeShareSecurityCouncilAdder(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkMaxNodeShareSecurityCouncilAdderPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkMaxNodeShareSecurityCouncilAdderPath, trueValue, yes, toJson) } -func proposeMaxRethBalanceDelta(value *big.Int, yes bool) error { +func proposeMaxRethBalanceDelta(value *big.Int, yes bool, toJson string) error { trueValue := value.String() - return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkMaxRethBalanceDeltaPath, trueValue, yes) + return proposeSetting(protocol.NetworkSettingsContractName, protocol.NetworkMaxRethBalanceDeltaPath, trueValue, yes, toJson) } // Master general proposal function -func proposeSetting(contract string, setting string, value string, yes bool) error { +func proposeSetting(contract string, setting string, value string, yes bool, toJson string) error { + if toJson != "" { + return writeSettingToBatchJSON(toJson, contract, setting, value) + } + // Get RP client rp, err := rocketpool.NewClient().WithReady() if err != nil { @@ -395,11 +388,6 @@ func proposeSetting(contract string, setting string, value string, yes bool) err } defer rp.Close() - if 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) if err != nil { @@ -446,16 +434,3 @@ func proposeSetting(contract string, setting string, value string, yes bool) err fmt.Printf("Successfully submitted a %s setting update proposal.\n", setting) return nil } - -// 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-cli/pdao/set-allow-list.go b/rocketpool-cli/pdao/set-allow-list.go index 02f668530..b7ba7cdf8 100644 --- a/rocketpool-cli/pdao/set-allow-list.go +++ b/rocketpool-cli/pdao/set-allow-list.go @@ -12,7 +12,10 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" ) -func setAllowListedControllers(addressListStr string, yes bool) error { +func setAllowListedControllers(addressListStr string, yes bool, toJson string) error { + if toJson != "" { + return fmt.Errorf("allow-listed-controllers cannot be included in a multi-setting proposal (the protocol only supports bool, uint256, and address values in batch proposals)") + } // Get RP client rp, err := rocketpool.NewClient().WithReady() diff --git a/rocketpool-cli/pdao/submit-batch.go b/rocketpool-cli/pdao/submit-batch.go new file mode 100644 index 000000000..4010196a4 --- /dev/null +++ b/rocketpool-cli/pdao/submit-batch.go @@ -0,0 +1,123 @@ +package pdao + +import ( + "fmt" + "math/big" + "strings" + + cliutils "github.com/rocket-pool/smartnode/rocketpool-cli/cli" + "github.com/rocket-pool/smartnode/rocketpool-cli/cli/prompt" + "github.com/rocket-pool/smartnode/shared/math" + "github.com/rocket-pool/smartnode/shared/services/gas" + "github.com/rocket-pool/smartnode/shared/services/rocketpool" +) + +func printSubmitBatchHelp() { + fmt.Print(`submit-batch creates one Protocol DAO proposal that changes multiple settings. + +Build a JSON file with --to-json on the usual setting propose commands (creates the file or appends; the same contract+setting is replaced): + + rocketpool pdao propose setting auction is-create-lot-enabled true --to-json settings.json + rocketpool pdao propose setting deposit minimum-deposit 1.0 --to-json settings.json + +Submit that file as a single proposal: + + rocketpool pdao propose submit-batch --file settings.json + +The file is a JSON array. Each object is one setting: + +[ + { + "contract": "rocketDAOProtocolSettingsAuction", + "setting": "auction.lot.create.enabled", + "type": "bool", + "value": "true" + }, + { + "contract": "rocketDAOProtocolSettingsDeposit", + "setting": "deposit.minimum", + "type": "uint256", + "value": "1000000000000000000" + } +] +`) +} + +func submitBatch(file string, message string, yes bool) error { + if file == "" { + printSubmitBatchHelp() + file = prompt.Prompt("Please enter the path to the JSON file:", "^.+$", "Invalid file path") + } + + settings, err := readBatchSettingsFile(file) + if err != nil { + return err + } + if len(settings) == 0 { + return fmt.Errorf("JSON file %s does not contain any settings", file) + } + + fmt.Println("The following settings will be submitted as a single proposal:") + for i, setting := range settings { + fmt.Printf(" %d. %s / %s = %s\n", i+1, setting.Contract, setting.Setting, setting.Value) + } + fmt.Println() + + if message == "" { + message = prompt.Prompt("Please enter a custom message for this multi-setting proposal (no blank spaces):", "^\\S*$", "Invalid message") + } + if message == "" { + paths := make([]string, len(settings)) + for i, setting := range settings { + paths[i] = setting.Setting + } + message = "set-" + strings.Join(paths, ",") + } + + rp, err := rocketpool.NewClient().WithReady() + if err != nil { + return err + } + defer rp.Close() + + canPropose, err := rp.PDAOCanProposeSettingMulti(settings, message) + if err != nil { + return err + } + if !canPropose.CanPropose { + fmt.Println("Cannot propose setting update:") + if canPropose.InsufficientRpl { + fmt.Printf("You do not have enough RPL staked but unlocked to make another proposal (unlocked: %.6f RPL, required: %.6f RPL).\n", + math.WeiToEth(big.NewInt(0).Sub(canPropose.StakedRpl, canPropose.LockedRpl)), math.WeiToEth(canPropose.ProposalBond), + ) + } + if canPropose.IsRplLockingDisallowed { + fmt.Println("Please enable RPL locking using the command 'rocketpool node allow-rpl-locking' to raise proposals.") + } + return nil + } + + err = gas.AssignMaxFeeAndLimit(canPropose.GasLimits, rp, yes) + if err != nil { + return err + } + + if prompt.Declined(yes, "Are you sure you want to submit this proposal with %d setting(s)?", len(settings)) { + fmt.Println("Cancelled.") + return nil + } + + response, err := rp.PDAOProposeSettingMulti(settings, message, canPropose.BlockNumber) + if err != nil { + return err + } + + fmt.Printf("Submitting multi-setting proposal...\n") + cliutils.PrintTransactionHash(rp, response.TxHash) + if _, err = rp.WaitForTransaction(response.TxHash); err != nil { + return err + } + + fmt.Printf("Successfully submitted a multi-setting proposal with %d setting(s).\n", len(settings)) + return nil +} diff --git a/rocketpool-cli/pdao/vote-proposal.go b/rocketpool-cli/pdao/vote-proposal.go index f1c15d2bb..daf8c1d97 100644 --- a/rocketpool-cli/pdao/vote-proposal.go +++ b/rocketpool-cli/pdao/vote-proposal.go @@ -84,11 +84,12 @@ func voteOnProposal(proposal, voteDirectionFlag string, yes bool) error { } else { endTime = fmt.Sprintf("phase 2 end: %s", proposal.Phase2EndTime.Format(time.RFC822)) } + message, payload := proposalDisplayText(proposal) options[pi] = fmt.Sprintf( "proposal %d (message: '%s', payload: %s, %s, vp required: %.2f, for: %.2f, against: %.2f, abstained: %.2f, veto: %.2f, proposed by: %s)", proposal.ID, - proposal.Message, - proposal.PayloadStr, + message, + payload, endTime, math.WeiToEth(proposal.VotingPowerRequired), math.WeiToEth(proposal.VotingPowerFor), @@ -102,6 +103,8 @@ func voteOnProposal(proposal, voteDirectionFlag string, yes bool) error { } + printSelectedMultiSettings(selectedProposal.MultiSettings) + // Check if delegate has voted if selectedProposal.DelegateVoteDirection != types.VoteDirection_NoVote && votingDelegateInfo.VotingDelegate != votingDelegateInfo.AccountAddress { fmt.Printf("Your Delegate: %s has voted: %s\n", votingDelegateInfo.VotingDelegate.Hex(), types.VoteDirections[selectedProposal.DelegateVoteDirection]) diff --git a/rocketpool/api/node/status.go b/rocketpool/api/node/status.go index 4fd73d72e..d6ca3412f 100644 --- a/rocketpool/api/node/status.go +++ b/rocketpool/api/node/status.go @@ -267,7 +267,7 @@ func getStatus(c *cli.Command) (*api.NodeStatusResponse, error) { return err }) - // MinimumLegacyRPLStake and MaximumPerMinipoolStake are both used to compute the RPL amount that a node cannot fall under when withdrawing + // MinimumLegacyRPLStake is used to compute the RPL amount that a node cannot fall under when withdrawing wg.Go(func() error { var err error response.RplStakeThresholdFraction, err = protocol.GetMinimumLegacyRPLStake(rp, nil) @@ -425,7 +425,7 @@ func getStatus(c *cli.Command) (*api.NodeStatusResponse, error) { var wg2 errgroup.Group var rplStakeThresholdFraction *big.Int - // MinimumLegacyRPLStake and MaximumPerMinipoolStake are both used to compute the RPL amount that a node cannot fall under when withdrawing + // MinimumLegacyRPLStake is used to compute the RPL amount that a node cannot fall under when withdrawing wg2.Go(func() error { var err error rplStakeThresholdFraction, err = protocol.GetMinimumLegacyRPLStakeRaw(rp, nil) diff --git a/rocketpool/api/pdao/propose-settings-multi.go b/rocketpool/api/pdao/propose-settings-multi.go new file mode 100644 index 000000000..990c9e292 --- /dev/null +++ b/rocketpool/api/pdao/propose-settings-multi.go @@ -0,0 +1,154 @@ +package pdao + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/urfave/cli/v3" + "golang.org/x/sync/errgroup" + + daoprotocol "github.com/rocket-pool/smartnode/bindings/dao/protocol" + "github.com/rocket-pool/smartnode/bindings/node" + "github.com/rocket-pool/smartnode/bindings/settings/protocol" + "github.com/rocket-pool/smartnode/shared/services" + "github.com/rocket-pool/smartnode/shared/types/api" +) + +func canProposeSettingMulti(c *cli.Command, settings []api.PDAOBatchSetting, customMessage string) (*api.CanProposePDAOSettingMultiResponse, error) { + if err := services.RequireNodeWallet(c); err != nil { + return nil, err + } + if err := services.RequireRocketStorage(c); err != nil { + return nil, err + } + cfg, err := services.GetConfig(c) + if err != nil { + return nil, err + } + w, err := services.GetWallet(c) + if err != nil { + return nil, err + } + rp, err := services.GetRocketPool(c) + if err != nil { + return nil, err + } + bc, err := services.GetBeaconClient(c) + if err != nil { + return nil, err + } + + response := api.CanProposePDAOSettingMultiResponse{} + + nodeAccount, err := w.GetNodeAccount() + if err != nil { + return nil, err + } + + var stakedRpl *big.Int + var lockedRpl *big.Int + var proposalBond *big.Int + var isRplLockingAllowed bool + var wg errgroup.Group + + wg.Go(func() error { + var err error + stakedRpl, err = node.GetNodeStakedRPL(rp, nodeAccount.Address, nil) + return err + }) + wg.Go(func() error { + var err error + lockedRpl, err = node.GetNodeLockedRPL(rp, nodeAccount.Address, nil) + return err + }) + wg.Go(func() error { + var err error + proposalBond, err = protocol.GetProposalBond(rp, nil) + return err + }) + wg.Go(func() error { + var err error + isRplLockingAllowed, err = node.GetRPLLockedAllowed(rp, nodeAccount.Address, nil) + return err + }) + if err := wg.Wait(); err != nil { + return nil, err + } + + response.StakedRpl = stakedRpl + response.LockedRpl = lockedRpl + response.ProposalBond = proposalBond + response.IsRplLockingDisallowed = !isRplLockingAllowed + + freeRpl := big.NewInt(0).Sub(stakedRpl, lockedRpl) + response.InsufficientRpl = freeRpl.Cmp(proposalBond) < 0 + response.CanPropose = !response.InsufficientRpl && !response.IsRplLockingDisallowed + if !response.CanPropose { + return &response, nil + } + + decoded, err := decodeBatchSettings(settings, customMessage) + if err != nil { + return nil, err + } + + blockNumber, pollard, err := createPollard(rp, cfg, bc) + if err != nil { + return nil, fmt.Errorf("error creating pollard: %w", err) + } + response.BlockNumber = blockNumber + + opts, err := w.GetNodeAccountTransactor() + if err != nil { + return nil, err + } + + response.GasLimits, err = daoprotocol.EstimateProposeSetMultiGas(rp, decoded.message, decoded.contractNames, decoded.settingPaths, decoded.settingTypes, decoded.values, blockNumber, pollard, opts) + if err != nil { + return nil, fmt.Errorf("error estimating gas for multi-setting proposal: %w", err) + } + + return &response, nil +} + +func proposeSettingMulti(c *cli.Command, settings []api.PDAOBatchSetting, customMessage string, blockNumber uint32, opts *bind.TransactOpts) (*api.ProposePDAOSettingMultiResponse, error) { + if err := services.RequireNodeWallet(c); err != nil { + return nil, err + } + if err := services.RequireRocketStorage(c); err != nil { + return nil, err + } + cfg, err := services.GetConfig(c) + if err != nil { + return nil, err + } + rp, err := services.GetRocketPool(c) + if err != nil { + return nil, err + } + bc, err := services.GetBeaconClient(c) + if err != nil { + return nil, err + } + + decoded, err := decodeBatchSettings(settings, customMessage) + if err != nil { + return nil, err + } + + pollard, err := getPollard(rp, cfg, bc, blockNumber) + if err != nil { + return nil, fmt.Errorf("error regenerating pollard: %w", err) + } + + proposalID, hash, err := daoprotocol.ProposeSetMulti(rp, decoded.message, decoded.contractNames, decoded.settingPaths, decoded.settingTypes, decoded.values, blockNumber, pollard, opts) + if err != nil { + return nil, fmt.Errorf("error proposing multi-setting update: %w", err) + } + + return &api.ProposePDAOSettingMultiResponse{ + ProposalId: proposalID, + TxHash: hash, + }, nil +} diff --git a/rocketpool/api/pdao/propose-settings.go b/rocketpool/api/pdao/propose-settings.go index d2593ae58..3c7a935ff 100644 --- a/rocketpool/api/pdao/propose-settings.go +++ b/rocketpool/api/pdao/propose-settings.go @@ -7,8 +7,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - protocol131 "github.com/rocket-pool/smartnode/bindings/legacy/v1.3.1/protocol" - "github.com/urfave/cli/v3" "golang.org/x/sync/errgroup" @@ -606,26 +604,6 @@ func canProposeSetting(c *cli.Command, contractName string, settingName string, if err != nil { return nil, fmt.Errorf("error estimating gas for proposing VacantMinipoolsEnabled: %w", err) } - // MinimumPerMinipoolStake - case protocol131.MinimumPerMinipoolStakeSettingPath: - newValue, err := cliutils.ValidateBigInt(valueName, value) - if err != nil { - return nil, err - } - response.GasLimits, 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 protocol131.MaximumPerMinipoolStakeSettingPath: - newValue, err := cliutils.ValidateBigInt(valueName, value) - if err != nil { - return nil, err - } - response.GasLimits, 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) @@ -1456,26 +1434,6 @@ func proposeSetting(c *cli.Command, contractName string, settingName string, val if err != nil { return nil, fmt.Errorf("error proposing VacantMinipoolsEnabled: %w", err) } - // MinimumPerMinipoolStake - case protocol131.MinimumPerMinipoolStakeSettingPath: - newValue, err := cliutils.ValidateBigInt(valueName, value) - if err != nil { - return nil, err - } - proposalID, hash, err = protocol131.ProposeMinimumPerMinipoolStake(rp, newValue, blockNumber, pollard, opts) - if err != nil { - return nil, fmt.Errorf("error proposing MinimumPerMinipoolStake: %w", err) - } - // MaximumPerMinipoolStake - case protocol131.MaximumPerMinipoolStakeSettingPath: - newValue, err := cliutils.ValidateBigInt(valueName, value) - if err != nil { - return nil, err - } - 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) diff --git a/rocketpool/api/pdao/routes.go b/rocketpool/api/pdao/routes.go index 57151516d..54701e511 100644 --- a/rocketpool/api/pdao/routes.go +++ b/rocketpool/api/pdao/routes.go @@ -1,6 +1,7 @@ package pdao import ( + "encoding/json" "fmt" "math/big" "net/http" @@ -15,6 +16,7 @@ import ( cliutils "github.com/rocket-pool/smartnode/rocketpool-cli/cli" "github.com/rocket-pool/smartnode/rocketpool/api/response" "github.com/rocket-pool/smartnode/shared/services" + "github.com/rocket-pool/smartnode/shared/types/api" ) // RegisterRoutes registers the pdao module's HTTP routes onto mux. @@ -145,6 +147,36 @@ func RegisterRoutes(mux *http.ServeMux, c *cli.Command) { response.WriteResponse(w, resp, err) }) + mux.HandleFunc("/api/pdao/can-propose-setting-multi", func(w http.ResponseWriter, r *http.Request) { + settings, customMessage, err := parseBatchSettings(r) + if err != nil { + response.WriteErrorResponse(w, err) + return + } + resp, err := canProposeSettingMulti(c, settings, customMessage) + response.WriteResponse(w, resp, err) + }) + + mux.HandleFunc("/api/pdao/propose-setting-multi", func(w http.ResponseWriter, r *http.Request) { + settings, customMessage, err := parseBatchSettings(r) + if err != nil { + response.WriteErrorResponse(w, err) + return + } + blockNumber, err := parseUint32Param(r, "blockNumber") + if err != nil { + response.WriteErrorResponse(w, err) + return + } + opts, err := services.GetNodeAccountTransactorFromRequest(c, r) + if err != nil { + response.WriteErrorResponse(w, err) + return + } + resp, err := proposeSettingMulti(c, settings, customMessage, blockNumber, opts) + response.WriteResponse(w, resp, err) + }) + mux.HandleFunc("/api/pdao/get-rewards-percentages", func(w http.ResponseWriter, r *http.Request) { resp, err := getRewardsPercentages(c) response.WriteResponse(w, resp, err) @@ -680,6 +712,18 @@ func parseRawAddressList(raw string) []common.Address { return addresses } +func parseBatchSettings(r *http.Request) ([]api.PDAOBatchSetting, string, error) { + raw := paramVal(r, "settings") + if raw == "" { + return nil, "", fmt.Errorf("missing required parameter: settings") + } + var settings []api.PDAOBatchSetting + if err := json.Unmarshal([]byte(raw), &settings); err != nil { + return nil, "", fmt.Errorf("invalid settings JSON: %w", err) + } + return settings, paramVal(r, "customMessage"), nil +} + func parseClaimBondsParams(r *http.Request) (uint64, []uint64, error) { proposalID, err := parseUint64Param(r, "proposalId") if err != nil { diff --git a/rocketpool/api/pdao/setting-decode.go b/rocketpool/api/pdao/setting-decode.go new file mode 100644 index 000000000..fc644f1f7 --- /dev/null +++ b/rocketpool/api/pdao/setting-decode.go @@ -0,0 +1,93 @@ +package pdao + +import ( + "fmt" + "strings" + + "github.com/rocket-pool/smartnode/bindings/settings/protocol" + "github.com/rocket-pool/smartnode/bindings/types" + cliutils "github.com/rocket-pool/smartnode/rocketpool-cli/cli" + "github.com/rocket-pool/smartnode/shared/types/api" +) + +type decodedBatchSettings struct { + contractNames []string + settingPaths []string + settingTypes []types.ProposalSettingType + values []any + message string +} + +func decodeBatchSettings(entries []api.PDAOBatchSetting, customMessage string) (*decodedBatchSettings, error) { + if len(entries) == 0 { + return nil, protocol.ErrEmptyBatchSettings + } + + decoded := &decodedBatchSettings{ + contractNames: make([]string, 0, len(entries)), + settingPaths: make([]string, 0, len(entries)), + settingTypes: make([]types.ProposalSettingType, 0, len(entries)), + values: make([]any, 0, len(entries)), + } + + seen := make(map[string]struct{}, len(entries)) + for i, entry := range entries { + if entry.Contract == "" || entry.Setting == "" { + return nil, fmt.Errorf("setting %d is missing contract or setting path", i) + } + + key := entry.Contract + "\x00" + entry.Setting + if _, exists := seen[key]; exists { + return nil, fmt.Errorf("%w: %s / %s", protocol.ErrDuplicateBatchSetting, entry.Contract, entry.Setting) + } + seen[key] = struct{}{} + + settingType, err := protocol.GetProposalSettingType(entry.Contract, entry.Setting) + if err != nil { + return nil, err + } + if entry.Type != "" { + namedType, err := protocol.ParseProposalSettingTypeName(entry.Type) + if err != nil { + return nil, fmt.Errorf("setting %s: %w", entry.Setting, err) + } + if namedType != settingType { + return nil, fmt.Errorf("setting %s has type %s but JSON claims %s", entry.Setting, protocol.ProposalSettingTypeName(settingType), entry.Type) + } + } + + value, err := decodeSettingValue(settingType, entry.Value) + if err != nil { + return nil, fmt.Errorf("setting %s: %w", entry.Setting, err) + } + + decoded.contractNames = append(decoded.contractNames, entry.Contract) + decoded.settingPaths = append(decoded.settingPaths, entry.Setting) + decoded.settingTypes = append(decoded.settingTypes, settingType) + decoded.values = append(decoded.values, value) + } + + if customMessage != "" { + decoded.message = customMessage + } else { + decoded.message = "set " + strings.Join(decoded.settingPaths, ", ") + } + return decoded, nil +} + +func decodeSettingValue(settingType types.ProposalSettingType, value string) (any, error) { + switch settingType { + case types.ProposalSettingType_Bool: + return cliutils.ValidateBool("value", value) + case types.ProposalSettingType_Uint256: + return cliutils.ValidateBigInt("value", value) + case types.ProposalSettingType_Address: + address, err := cliutils.ValidateAddress("value", value) + if err != nil { + return nil, err + } + return address, nil + default: + return nil, fmt.Errorf("unsupported setting type %v", settingType) + } +} diff --git a/rocketpool/api/pdao/setting-decode_test.go b/rocketpool/api/pdao/setting-decode_test.go new file mode 100644 index 000000000..4fb28292e --- /dev/null +++ b/rocketpool/api/pdao/setting-decode_test.go @@ -0,0 +1,112 @@ +package pdao + +import ( + "errors" + "math/big" + "testing" + + "github.com/rocket-pool/smartnode/bindings/settings/protocol" + "github.com/rocket-pool/smartnode/bindings/types" + "github.com/rocket-pool/smartnode/shared/types/api" +) + +func TestDecodeBatchSettings(t *testing.T) { + entries := []api.PDAOBatchSetting{ + { + Contract: protocol.AuctionSettingsContractName, + Setting: protocol.CreateLotEnabledSettingPath, + Type: protocol.ProposalSettingTypeNameBool, + Value: "true", + }, + { + Contract: protocol.DepositSettingsContractName, + Setting: protocol.MinimumDepositSettingPath, + Value: "1000000000000000000", + }, + } + + decoded, err := decodeBatchSettings(entries, "") + if err != nil { + t.Fatalf("decode: %v", err) + } + if len(decoded.values) != 2 { + t.Fatalf("got %d values, want 2", len(decoded.values)) + } + if decoded.settingTypes[0] != types.ProposalSettingType_Bool { + t.Fatalf("first type = %v", decoded.settingTypes[0]) + } + if decoded.values[0] != true { + t.Fatalf("first value = %v", decoded.values[0]) + } + amount, ok := decoded.values[1].(*big.Int) + if !ok || amount.String() != "1000000000000000000" { + t.Fatalf("second value = %v", decoded.values[1]) + } + if decoded.message != "set auction.lot.create.enabled, deposit.minimum" { + t.Fatalf("message = %q", decoded.message) + } +} + +func TestDecodeBatchSettings_CustomMessage(t *testing.T) { + entries := []api.PDAOBatchSetting{ + { + Contract: protocol.AuctionSettingsContractName, + Setting: protocol.CreateLotEnabledSettingPath, + Value: "false", + }, + } + decoded, err := decodeBatchSettings(entries, "update auction settings") + if err != nil { + t.Fatal(err) + } + if decoded.message != "update auction settings" { + t.Fatalf("message = %q", decoded.message) + } +} + +func TestDecodeBatchSettings_Empty(t *testing.T) { + _, err := decodeBatchSettings(nil, "") + if !errors.Is(err, protocol.ErrEmptyBatchSettings) { + t.Fatalf("got %v, want %v", err, protocol.ErrEmptyBatchSettings) + } +} + +func TestDecodeBatchSettings_Duplicate(t *testing.T) { + entries := []api.PDAOBatchSetting{ + {Contract: protocol.AuctionSettingsContractName, Setting: protocol.CreateLotEnabledSettingPath, Value: "true"}, + {Contract: protocol.AuctionSettingsContractName, Setting: protocol.CreateLotEnabledSettingPath, Value: "false"}, + } + _, err := decodeBatchSettings(entries, "") + if !errors.Is(err, protocol.ErrDuplicateBatchSetting) { + t.Fatalf("got %v, want %v", err, protocol.ErrDuplicateBatchSetting) + } +} + +func TestDecodeBatchSettings_TypeMismatch(t *testing.T) { + entries := []api.PDAOBatchSetting{ + { + Contract: protocol.AuctionSettingsContractName, + Setting: protocol.CreateLotEnabledSettingPath, + Type: protocol.ProposalSettingTypeNameUint256, + Value: "true", + }, + } + _, err := decodeBatchSettings(entries, "") + if err == nil { + t.Fatal("expected type mismatch error") + } +} + +func TestDecodeBatchSettings_AddressListRejected(t *testing.T) { + entries := []api.PDAOBatchSetting{ + { + Contract: protocol.NetworkSettingsContractName, + Setting: protocol.NetworkAllowListedControllersPath, + Value: "0x0000000000000000000000000000000000000001", + }, + } + _, err := decodeBatchSettings(entries, "") + if !errors.Is(err, protocol.ErrUnsupportedBatchSetting) { + t.Fatalf("got %v, want %v", err, protocol.ErrUnsupportedBatchSetting) + } +} diff --git a/shared/services/rocketpool/pdao.go b/shared/services/rocketpool/pdao.go index 7854690ff..1c6eb517e 100644 --- a/shared/services/rocketpool/pdao.go +++ b/shared/services/rocketpool/pdao.go @@ -205,6 +205,53 @@ func (c *Client) PDAOCanProposeSetting(contract string, setting string, value st return response, nil } +// Check whether the node can propose updating multiple PDAO settings +func (c *Client) PDAOCanProposeSettingMulti(settings []api.PDAOBatchSetting, customMessage string) (api.CanProposePDAOSettingMultiResponse, error) { + settingsJSON, err := json.Marshal(settings) + if err != nil { + return api.CanProposePDAOSettingMultiResponse{}, fmt.Errorf("Could not encode multi-setting proposal: %w", err) + } + responseBytes, err := c.callHTTPAPI("POST", "/api/pdao/can-propose-setting-multi", url.Values{ + "settings": {string(settingsJSON)}, + "customMessage": {customMessage}, + }) + if err != nil { + return api.CanProposePDAOSettingMultiResponse{}, fmt.Errorf("Could not get protocol DAO can-propose-setting-multi: %w", err) + } + var response api.CanProposePDAOSettingMultiResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return api.CanProposePDAOSettingMultiResponse{}, fmt.Errorf("Could not decode protocol DAO can-propose-setting-multi response: %w", err) + } + if response.Error != "" { + return api.CanProposePDAOSettingMultiResponse{}, fmt.Errorf("Could not get protocol DAO can-propose-setting-multi: %s", response.Error) + } + return response, nil +} + +// Propose updating multiple PDAO settings +func (c *Client) PDAOProposeSettingMulti(settings []api.PDAOBatchSetting, customMessage string, blockNumber uint32) (api.ProposePDAOSettingMultiResponse, error) { + settingsJSON, err := json.Marshal(settings) + if err != nil { + return api.ProposePDAOSettingMultiResponse{}, fmt.Errorf("Could not encode multi-setting proposal: %w", err) + } + responseBytes, err := c.callHTTPAPI("POST", "/api/pdao/propose-setting-multi", url.Values{ + "settings": {string(settingsJSON)}, + "customMessage": {customMessage}, + "blockNumber": {strconv.FormatUint(uint64(blockNumber), 10)}, + }) + if err != nil { + return api.ProposePDAOSettingMultiResponse{}, fmt.Errorf("Could not get protocol DAO propose-setting-multi: %w", err) + } + var response api.ProposePDAOSettingMultiResponse + if err := json.Unmarshal(responseBytes, &response); err != nil { + return api.ProposePDAOSettingMultiResponse{}, fmt.Errorf("Could not decode protocol DAO propose-setting-multi response: %w", err) + } + if response.Error != "" { + return api.ProposePDAOSettingMultiResponse{}, fmt.Errorf("Could not get protocol DAO propose-setting-multi: %s", response.Error) + } + return response, nil +} + // Propose updating a PDAO setting func (c *Client) PDAOProposeSetting(contract string, setting string, value string, blockNumber uint32) (api.ProposePDAOSettingResponse, error) { responseBytes, err := c.callHTTPAPI("POST", "/api/pdao/propose-setting", url.Values{ diff --git a/shared/types/api/pdao.go b/shared/types/api/pdao.go index 3eddf747a..b5c910d47 100644 --- a/shared/types/api/pdao.go +++ b/shared/types/api/pdao.go @@ -143,8 +143,6 @@ type GetPDAOSettingsResponse struct { IsSmoothingPoolRegistrationEnabled bool `json:"isSmoothingPoolRegistrationEnabled"` IsDepositingEnabled bool `json:"isDepositingEnabled"` 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"` @@ -206,6 +204,33 @@ type ProposePDAOSettingResponse struct { TxHash common.Hash `json:"txHash"` } +type PDAOBatchSetting struct { + Contract string `json:"contract"` + Setting string `json:"setting"` + Type string `json:"type,omitempty"` + Value string `json:"value"` +} + +type CanProposePDAOSettingMultiResponse struct { + Status string `json:"status"` + Error string `json:"error"` + CanPropose bool `json:"canPropose"` + InsufficientRpl bool `json:"proposalCooldownActive"` + StakedRpl *big.Int `json:"stakedRpl"` + LockedRpl *big.Int `json:"lockedRpl"` + ProposalBond *big.Int `json:"proposalBond"` + BlockNumber uint32 `json:"blockNumber"` + GasLimits gaslimit.Limits `json:"gasLimits"` + IsRplLockingDisallowed bool `json:"isRplLockingDisallowed"` +} + +type ProposePDAOSettingMultiResponse struct { + Status string `json:"status"` + Error string `json:"error"` + ProposalId uint64 `json:"proposalId"` + TxHash common.Hash `json:"txHash"` +} + type PDAOGetRewardsPercentagesResponse struct { Status string `json:"status"` Error string `json:"error"`