diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70ed2ba4f..acf59c390 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,7 +179,7 @@ jobs: sudo apt-get install ethereum geth --version fi - - name: Run loadtest againt ${{ matrix.tool }} + - name: Run loadtest against ${{ matrix.tool }} run: | ${{ matrix.tool }} --version make ${{ matrix.tool }} & diff --git a/cmd/cdk/cdk.go b/cmd/cdk/cdk.go index e66ea12a0..5c4166098 100644 --- a/cmd/cdk/cdk.go +++ b/cmd/cdk/cdk.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" "github.com/0xPolygon/polygon-cli/custommarshaller" + "github.com/0xPolygon/polygon-cli/flag" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -44,7 +44,6 @@ import ( ) const ( - ArgRpcURL = "rpc-url" ArgForkID = "fork-id" ArgRollupManagerAddress = "rollup-manager-address" @@ -55,7 +54,6 @@ const ( ArgBridgeAddress = "bridge-address" ArgGERAddress = "ger-address" - defaultRPCURL = "http://localhost:8545" defaultForkId = "12" // forks @@ -106,11 +104,12 @@ var CDKCmd = &cobra.Command{ Use: "cdk", Short: "Utilities for interacting with CDK networks.", Long: "Basic utility commands for interacting with the cdk contracts.", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - rpcURL := flag_loader.GetRpcUrlFlagValue(cmd) - if rpcURL != nil { - cdkInputArgs.rpcURL = *rpcURL + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + cdkInputArgs.rpcURL, err = flag.GetRPCURL(cmd) + if err != nil { + return err } + return nil }, Args: cobra.NoArgs, } @@ -636,7 +635,7 @@ func mustPrintLogs(logs []types.Log, contractInstance reflect.Value, contractABI func init() { // cdk f := CDKCmd.PersistentFlags() - f.StringVar(&cdkInputArgs.rpcURL, ArgRpcURL, defaultRPCURL, "RPC URL of network containing CDK contracts") + f.StringVar(&cdkInputArgs.rpcURL, flag.RPCURL, flag.DefaultRPCURL, "RPC URL of network containing CDK contracts") f.StringVar(&cdkInputArgs.forkID, ArgForkID, defaultForkId, "fork ID of CDK networks") f.StringVar(&cdkInputArgs.rollupManagerAddress, ArgRollupManagerAddress, "", "address of rollup contract") diff --git a/cmd/contract/cmd.go b/cmd/contract/cmd.go index 3392f4dde..f913c4dcb 100644 --- a/cmd/contract/cmd.go +++ b/cmd/contract/cmd.go @@ -8,7 +8,7 @@ import ( "math/big" "strings" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" + "github.com/0xPolygon/polygon-cli/flag" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" @@ -17,9 +17,7 @@ import ( ) const ( - ArgRpcURL = "rpc-url" - ArgAddress = "address" - defaultRPCURL = "http://localhost:8545" + ArgAddress = "address" ) var ( @@ -45,9 +43,12 @@ var Cmd = &cobra.Command{ Use: "contract", Short: "Interact with smart contracts and fetch contract information from the blockchain.", Long: usage, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - rpcURL := flag_loader.GetRpcUrlFlagValue(cmd) - inputArgs.rpcURL = *rpcURL + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + inputArgs.rpcURL, err = flag.GetRPCURL(cmd) + if err != nil { + return err + } + return nil }, RunE: func(cmd *cobra.Command, args []string) error { return contract(cmd) @@ -170,8 +171,7 @@ func fetchContractCreationTx(ctx context.Context, client *ethclient.Client, cont func init() { f := Cmd.Flags() - f.StringVar(&inputArgs.rpcURL, ArgRpcURL, defaultRPCURL, "RPC URL of network containing contract") + f.StringVar(&inputArgs.rpcURL, flag.RPCURL, flag.DefaultRPCURL, "RPC URL of network containing contract") f.StringVar(&inputArgs.address, ArgAddress, "", "contract address") - - _ = Cmd.MarkFlagRequired(ArgAddress) + flag.MarkFlagsRequired(Cmd, ArgAddress) } diff --git a/cmd/dumpblocks/dumpblocks.go b/cmd/dumpblocks/dumpblocks.go index cdde90229..a8cc9f364 100644 --- a/cmd/dumpblocks/dumpblocks.go +++ b/cmd/dumpblocks/dumpblocks.go @@ -12,7 +12,7 @@ import ( _ "embed" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" + "github.com/0xPolygon/polygon-cli/flag" "github.com/0xPolygon/polygon-cli/proto/gen/pb" "github.com/0xPolygon/polygon-cli/rpctypes" "github.com/0xPolygon/polygon-cli/util" @@ -56,11 +56,11 @@ var DumpblocksCmd = &cobra.Command{ Short: "Export a range of blocks from a JSON-RPC endpoint.", Long: usage, Args: cobra.MinimumNArgs(2), - PersistentPreRun: func(cmd *cobra.Command, args []string) { - rpcUrlFlagValue := flag_loader.GetRpcUrlFlagValue(cmd) - inputDumpblocks.RpcUrl = *rpcUrlFlagValue - }, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + inputDumpblocks.RpcUrl, err = flag.GetRPCURL(cmd) + if err != nil { + return err + } return checkFlags(args) }, RunE: func(cmd *cobra.Command, args []string) error { @@ -143,7 +143,7 @@ var DumpblocksCmd = &cobra.Command{ func init() { f := DumpblocksCmd.Flags() - f.StringVarP(&inputDumpblocks.RpcUrl, "rpc-url", "r", "http://localhost:8545", "the RPC endpoint URL") + f.StringVarP(&inputDumpblocks.RpcUrl, flag.RPCURL, "r", flag.DefaultRPCURL, "the RPC endpoint URL") f.UintVarP(&inputDumpblocks.Threads, "concurrency", "c", 1, "how many go routines to leverage") f.BoolVarP(&inputDumpblocks.ShouldDumpBlocks, "dump-blocks", "B", true, "dump blocks to output") f.BoolVar(&inputDumpblocks.ShouldDumpReceipts, "dump-receipts", true, "dump receipts to output") @@ -154,11 +154,6 @@ func init() { } func checkFlags(args []string) error { - // Check rpc url flag. - if err := util.ValidateUrl(inputDumpblocks.RpcUrl); err != nil { - return err - } - // Parse start and end blocks start, err := strconv.ParseInt(args[0], 10, 64) if err != nil { diff --git a/cmd/ecrecover/ecrecover.go b/cmd/ecrecover/ecrecover.go index a495e5a5a..1bb50a901 100644 --- a/cmd/ecrecover/ecrecover.go +++ b/cmd/ecrecover/ecrecover.go @@ -7,7 +7,7 @@ import ( "math/big" "os" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" + "github.com/0xPolygon/polygon-cli/flag" "github.com/0xPolygon/polygon-cli/util" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -32,12 +32,12 @@ var EcRecoverCmd = &cobra.Command{ Short: "Recovers and returns the public key of the signature.", Long: usage, Args: cobra.NoArgs, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - rpcUrlFlagValue := flag_loader.GetRpcUrlFlagValue(cmd) - rpcUrl = *rpcUrlFlagValue - }, - PreRunE: func(cmd *cobra.Command, args []string) error { - return checkFlags() + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + rpcUrl, err = flag.GetRPCURL(cmd) + if err != nil { + return err + } + return nil }, Run: func(cmd *cobra.Command, args []string) { ctx := cmd.Context() @@ -128,21 +128,9 @@ var EcRecoverCmd = &cobra.Command{ func init() { f := EcRecoverCmd.Flags() - f.StringVarP(&rpcUrl, "rpc-url", "r", "", "the RPC endpoint URL") + f.StringVarP(&rpcUrl, flag.RPCURL, "r", "", "the RPC endpoint URL") f.Uint64VarP(&blockNumber, "block-number", "b", 0, "block number to check the extra data for (default: latest)") f.StringVarP(&filePath, "file", "f", "", "path to a file containing block information in JSON format") f.StringVarP(&txData, "tx", "t", "", "transaction data in hex format") - - // The sources of decoding are mutually exclusive EcRecoverCmd.MarkFlagsMutuallyExclusive("file", "block-number", "tx") } - -func checkFlags() error { - var err error - if rpcUrl != "" { - if err = util.ValidateUrl(rpcUrl); err != nil { - return err - } - } - return err -} diff --git a/cmd/fixnoncegap/fixnoncegap.go b/cmd/fixnoncegap/fixnoncegap.go index 0e9b56578..d60d23b87 100644 --- a/cmd/fixnoncegap/fixnoncegap.go +++ b/cmd/fixnoncegap/fixnoncegap.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" + "github.com/0xPolygon/polygon-cli/flag" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -25,18 +25,17 @@ var FixNonceGapCmd = &cobra.Command{ Short: "Send txs to fix the nonce gap for a specific account.", Long: fixNonceGapUsage, Args: cobra.NoArgs, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - var err error - rpcURL := flag_loader.GetRpcUrlFlagValue(cmd) - inputFixNonceGapArgs.rpcURL = *rpcURL - privateKey, err := flag_loader.GetRequiredPrivateKeyFlagValue(cmd) + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + inputFixNonceGapArgs.rpcURL, err = flag.GetRPCURL(cmd) if err != nil { return err } - inputFixNonceGapArgs.privateKey = *privateKey - return nil + inputFixNonceGapArgs.privateKey, err = flag.GetRequiredPrivateKey(cmd) + if err != nil { + return err + } + return prepareRpcClient(cmd, args) }, - PreRunE: prepareRpcClient, RunE: fixNonceGap, SilenceUsage: true, } @@ -55,10 +54,8 @@ type fixNonceGapArgs struct { var inputFixNonceGapArgs = fixNonceGapArgs{} const ( - ArgPrivateKey = "private-key" - ArgRpcURL = "rpc-url" - ArgReplace = "replace" - ArgMaxNonce = "max-nonce" + ArgReplace = "replace" + ArgMaxNonce = "max-nonce" ) //go:embed FixNonceGapUsage.md @@ -236,8 +233,8 @@ func fixNonceGap(cmd *cobra.Command, args []string) error { func init() { f := FixNonceGapCmd.Flags() - f.StringVarP(&inputFixNonceGapArgs.rpcURL, ArgRpcURL, "r", "http://localhost:8545", "the RPC endpoint URL") - f.StringVar(&inputFixNonceGapArgs.privateKey, ArgPrivateKey, "", "private key to be used when sending txs to fix nonce gap") + f.StringVarP(&inputFixNonceGapArgs.rpcURL, flag.RPCURL, "r", flag.DefaultRPCURL, "the RPC endpoint URL") + f.StringVar(&inputFixNonceGapArgs.privateKey, flag.PrivateKey, "", "private key to be used when sending txs to fix nonce gap") f.BoolVar(&inputFixNonceGapArgs.replace, ArgReplace, false, "replace the existing txs in the pool") f.Uint64Var(&inputFixNonceGapArgs.maxNonce, ArgMaxNonce, 0, "override max nonce value instead of getting it from the pool") } diff --git a/cmd/flag_loader/flag_loader.go b/cmd/flag_loader/flag_loader.go deleted file mode 100644 index 30a9fea08..000000000 --- a/cmd/flag_loader/flag_loader.go +++ /dev/null @@ -1,78 +0,0 @@ -package flag_loader - -import ( - "fmt" - "math/big" - "os" - - "github.com/spf13/cobra" -) - -const ( - rpcUrlFlagName, rpcUrlEnvVar = "rpc-url", "ETH_RPC_URL" - privateKeyFlagName, privateKeyEnvVar = "private-key", "PRIVATE_KEY" -) - -type BigIntValue struct { - Val *big.Int -} - -func (b *BigIntValue) String() string { - // Return the decimal representation - return b.Val.String() -} - -func (b *BigIntValue) Set(s string) error { - // Parse the string in base 10 - if _, ok := b.Val.SetString(s, 10); !ok { - return fmt.Errorf("invalid big integer: %q", s) - } - return nil -} - -func (b *BigIntValue) Type() string { - return "big.Int" -} - -func GetRpcUrlFlagValue(cmd *cobra.Command) *string { - v, _ := getFlagValue(cmd, rpcUrlFlagName, rpcUrlEnvVar, false) - return v -} - -func GetRequiredRpcUrlFlagValue(cmd *cobra.Command) (*string, error) { - return getFlagValue(cmd, rpcUrlFlagName, rpcUrlEnvVar, true) -} - -func GetPrivateKeyFlagValue(cmd *cobra.Command) *string { - v, _ := getFlagValue(cmd, privateKeyFlagName, privateKeyEnvVar, false) - return v -} - -func GetRequiredPrivateKeyFlagValue(cmd *cobra.Command) (*string, error) { - return getFlagValue(cmd, privateKeyFlagName, privateKeyEnvVar, true) -} - -func getFlagValue(cmd *cobra.Command, flagName, envVarName string, required bool) (*string, error) { - flag := cmd.Flag(flagName) - var flagValue string - if flag.Changed { - flagValue = flag.Value.String() - } - flagDefaultValue := flag.DefValue - - envVarValue := os.Getenv(envVarName) - - value := flagDefaultValue - if envVarValue != "" { - value = envVarValue - } - if flag.Changed { - value = flagValue - } - - if required && (!flag.Changed && envVarValue == "") { - return nil, fmt.Errorf("required flag(s) \"%s\" not set", flagName) - } - - return &value, nil -} diff --git a/cmd/flag_loader/flag_loader_test.go b/cmd/flag_loader/flag_loader_test.go deleted file mode 100644 index c5765d27f..000000000 --- a/cmd/flag_loader/flag_loader_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package flag_loader - -import ( - "fmt" - "os" - "strconv" - "testing" - - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" -) - -func TestValuePriority(t *testing.T) { - type testCase struct { - defaultValue *int - envVarValue *int - flagValue *int - required bool - - expectedValue *int - expectedError error - } - - testCases := []testCase{ - { - defaultValue: ptr(1), - envVarValue: ptr(2), - flagValue: ptr(3), - expectedValue: ptr(3), - required: true, - expectedError: nil, - }, - { - defaultValue: ptr(1), - envVarValue: ptr(2), - flagValue: ptr(1), - expectedValue: ptr(1), - required: true, - expectedError: nil, - }, - { - defaultValue: ptr(1), - envVarValue: ptr(2), - flagValue: nil, - expectedValue: ptr(2), - required: true, - expectedError: nil, - }, - { - defaultValue: ptr(1), - envVarValue: nil, - flagValue: ptr(3), - expectedValue: ptr(3), - required: true, - expectedError: nil, - }, - { - defaultValue: nil, - envVarValue: ptr(2), - flagValue: ptr(3), - expectedValue: ptr(3), - required: true, - expectedError: nil, - }, - { - defaultValue: nil, - envVarValue: nil, - flagValue: ptr(3), - expectedValue: ptr(3), - required: true, - expectedError: nil, - }, - { - defaultValue: ptr(1), - envVarValue: nil, - flagValue: nil, - expectedValue: ptr(1), - required: false, - expectedError: nil, - }, - { - defaultValue: nil, - envVarValue: ptr(2), - flagValue: nil, - expectedValue: ptr(2), - required: true, - expectedError: nil, - }, - { - defaultValue: nil, - envVarValue: nil, - flagValue: nil, - expectedValue: ptr(0), - required: false, - expectedError: nil, - }, - { - defaultValue: nil, - envVarValue: nil, - flagValue: nil, - expectedValue: nil, - required: true, - expectedError: fmt.Errorf("required flag(s) \"flag\" not set"), - }, - } - - for _, tc := range testCases { - var value *int - cmd := &cobra.Command{ - Use: "test", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - valueStr, err := getFlagValue(cmd, "flag", "FLAG", tc.required) - if tc.expectedError != nil { - assert.EqualError(t, err, tc.expectedError.Error()) - return - } - assert.NoError(t, err) - valueInt, err := strconv.Atoi(*valueStr) - assert.NoError(t, err) - value = &valueInt - }, - Run: func(cmd *cobra.Command, args []string) { - if tc.expectedValue != nil { - assert.Equal(t, *tc.expectedValue, *value) - } else { - assert.Nil(t, value) - } - }, - } - if tc.defaultValue != nil { - cmd.Flags().Int("flag", *tc.defaultValue, "flag") - } else { - cmd.Flags().Int("flag", 0, "flag") - } - - os.Unsetenv("FLAG") - if tc.envVarValue != nil { - v := strconv.Itoa(*tc.envVarValue) - os.Setenv("FLAG", v) - } - - if tc.flagValue != nil { - v := strconv.Itoa(*tc.flagValue) - cmd.SetArgs([]string{"--flag", v}) - } - - err := cmd.Execute() - assert.Nil(t, err) - } - -} - -func ptr[T any](v T) *T { - return &v -} diff --git a/cmd/fund/cmd.go b/cmd/fund/cmd.go index 62f426e6b..ef8537f78 100644 --- a/cmd/fund/cmd.go +++ b/cmd/fund/cmd.go @@ -6,8 +6,7 @@ import ( _ "embed" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" - "github.com/0xPolygon/polygon-cli/util" + "github.com/0xPolygon/polygon-cli/flag" "github.com/spf13/cobra" ) @@ -48,13 +47,15 @@ var FundCmd = &cobra.Command{ Use: "fund", Short: "Bulk fund crypto wallets automatically.", Long: usage, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - rpcUrl := flag_loader.GetRpcUrlFlagValue(cmd) - params.RpcUrl = *rpcUrl - privateKey := flag_loader.GetPrivateKeyFlagValue(cmd) - params.PrivateKey = *privateKey - }, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + params.RpcUrl, err = flag.GetRPCURL(cmd) + if err != nil { + return err + } + params.PrivateKey, err = flag.GetPrivateKey(cmd) + if err != nil { + return err + } return checkFlags() }, RunE: func(cmd *cobra.Command, args []string) error { @@ -63,21 +64,20 @@ var FundCmd = &cobra.Command{ } func init() { - p := new(cmdFundParams) f := FundCmd.Flags() - f.StringVarP(&p.RpcUrl, "rpc-url", "r", "http://localhost:8545", "RPC endpoint URL") - f.StringVar(&p.PrivateKey, "private-key", defaultPrivateKey, "hex encoded private key to use for sending transactions") + f.StringVarP(¶ms.RpcUrl, flag.RPCURL, "r", flag.DefaultRPCURL, "RPC endpoint URL") + f.StringVar(¶ms.PrivateKey, flag.PrivateKey, defaultPrivateKey, "hex encoded private key to use for sending transactions") // Wallet parameters. - f.Uint64VarP(&p.WalletsNumber, "number", "n", 10, "number of wallets to fund") - f.BoolVar(&p.UseHDDerivation, "hd-derivation", true, "derive wallets to fund from private key in deterministic way") - f.StringSliceVar(&p.WalletAddresses, "addresses", nil, "comma-separated list of wallet addresses to fund") - p.FundingAmountInWei = defaultFundingInWei - f.Var(&flag_loader.BigIntValue{Val: p.FundingAmountInWei}, "eth-amount", "amount of wei to send to each wallet") - f.StringVar(&p.KeyFile, "key-file", "", "file containing accounts private keys, one per line") + f.Uint64VarP(¶ms.WalletsNumber, "number", "n", 10, "number of wallets to fund") + f.BoolVar(¶ms.UseHDDerivation, "hd-derivation", true, "derive wallets to fund from private key in deterministic way") + f.StringSliceVar(¶ms.WalletAddresses, "addresses", nil, "comma-separated list of wallet addresses to fund") + params.FundingAmountInWei = defaultFundingInWei + f.Var(&flag.BigIntValue{Val: params.FundingAmountInWei}, "eth-amount", "amount of wei to send to each wallet") + f.StringVar(¶ms.KeyFile, "key-file", "", "file containing accounts private keys, one per line") - f.StringVarP(&p.OutputFile, "file", "f", "wallets.json", "output JSON file path for storing addresses and private keys of funded wallets") + f.StringVarP(¶ms.OutputFile, "file", "f", "wallets.json", "output JSON file path for storing addresses and private keys of funded wallets") // Marking flags as mutually exclusive FundCmd.MarkFlagsMutuallyExclusive("addresses", "number") @@ -86,41 +86,18 @@ func init() { FundCmd.MarkFlagsMutuallyExclusive("key-file", "number") FundCmd.MarkFlagsMutuallyExclusive("key-file", "hd-derivation") - // Funder contract parameters. - f.StringVar(&p.FunderAddress, "contract-address", "", "address of pre-deployed Funder contract") + // Require at least one method to specify target accounts + FundCmd.MarkFlagsOneRequired("addresses", "key-file", "number") - params = *p + // Funder contract parameters. + f.StringVar(¶ms.FunderAddress, "contract-address", "", "address of pre-deployed Funder contract") } func checkFlags() error { - // Check rpc url flag. - if params.RpcUrl == "" { - panic("RPC URL is empty") - } - if err := util.ValidateUrl(params.RpcUrl); err != nil { - return err - } - - // Check private key flag. - if params.PrivateKey == "" { - return errors.New("the private key is empty") - } - - // Check that exactly one method is used to specify target accounts - hasAddresses := len(params.WalletAddresses) > 0 - hasKeyFile := params.KeyFile != "" - hasNumberFlag := params.WalletsNumber > 0 - if !hasAddresses && !hasKeyFile && !hasNumberFlag { - return errors.New("must specify target accounts via --addresses, --key-file, or --number") - } - + // Validate funding amount minValue := big.NewInt(1000000000) if params.FundingAmountInWei != nil && params.FundingAmountInWei.Cmp(minValue) <= 0 { return errors.New("the funding amount must be greater than 1000000000") } - if params.OutputFile == "" { - return errors.New("the output file is not specified") - } - return nil } diff --git a/cmd/loadtest/app.go b/cmd/loadtest/app.go index a8fa3db1b..73cdaa9ca 100644 --- a/cmd/loadtest/app.go +++ b/cmd/loadtest/app.go @@ -9,9 +9,8 @@ import ( "sync" "time" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" + "github.com/0xPolygon/polygon-cli/flag" "github.com/0xPolygon/polygon-cli/rpctypes" - "github.com/0xPolygon/polygon-cli/util" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -172,17 +171,15 @@ var LoadtestCmd = &cobra.Command{ Short: "Run a generic load test against an Eth/EVM style JSON-RPC endpoint.", Long: loadTestUsage, Args: cobra.NoArgs, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - rpcUrl := flag_loader.GetRpcUrlFlagValue(cmd) - privateKey := flag_loader.GetPrivateKeyFlagValue(cmd) - if rpcUrl != nil { - inputLoadTestParams.RPCUrl = *rpcUrl + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + inputLoadTestParams.RPCUrl, err = flag.GetRPCURL(cmd) + if err != nil { + return err } - if privateKey != nil { - inputLoadTestParams.PrivateKey = *privateKey + inputLoadTestParams.PrivateKey, err = flag.GetPrivateKey(cmd) + if err != nil { + return err } - }, - PreRunE: func(cmd *cobra.Command, args []string) error { zerolog.DurationFieldUnit = time.Second zerolog.DurationFieldInteger = true @@ -196,14 +193,6 @@ var LoadtestCmd = &cobra.Command{ func checkLoadtestFlags() error { ltp := inputLoadTestParams - // Check `rpc-url` flag. - if ltp.RPCUrl == "" { - return fmt.Errorf("RPC URL is empty") - } - if err := util.ValidateUrl(ltp.RPCUrl); err != nil { - return err - } - if ltp.AdaptiveBackoffFactor <= 0.0 { return fmt.Errorf("the backoff factor needs to be non-zero positive. Given: %f", ltp.AdaptiveBackoffFactor) } @@ -240,11 +229,11 @@ func initFlags() { // Persistent flags. pf := LoadtestCmd.PersistentFlags() - pf.StringVarP(<p.RPCUrl, "rpc-url", "r", "http://localhost:8545", "the RPC endpoint URL") + pf.StringVarP(<p.RPCUrl, flag.RPCURL, "r", flag.DefaultRPCURL, "the RPC endpoint URL") pf.Int64VarP(<p.Requests, "requests", "n", 1, "number of requests to perform for the benchmarking session (default of 1 leads to non-representative results)") pf.Int64VarP(<p.Concurrency, "concurrency", "c", 1, "number of requests to perform concurrently (default: one at a time)") pf.Int64VarP(<p.TimeLimit, "time-limit", "t", -1, "maximum seconds to spend benchmarking (default: no limit)") - pf.StringVar(<p.PrivateKey, "private-key", codeQualityPrivateKey, "hex encoded private key to use for sending transactions") + pf.StringVar(<p.PrivateKey, flag.PrivateKey, codeQualityPrivateKey, "hex encoded private key to use for sending transactions") pf.Uint64Var(<p.ChainID, "chain-id", 0, "chain ID for the transactions") pf.StringVar(<p.ToAddress, "to-address", "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", "recipient address for transactions") pf.BoolVar(<p.RandomRecipients, "random-recipients", false, "send to random addresses instead of fixed address in transfer tests") @@ -276,7 +265,7 @@ func initFlags() { f.Uint64Var(<p.BlobFeeCap, "blob-fee-cap", 100000, "blob fee cap, or maximum blob fee per chunk, in Gwei") f.Uint64Var(<p.SendingAccountsCount, "sending-accounts-count", 0, "number of sending accounts to use (avoids pool account queue)") ltp.AccountFundingAmount = defaultAccountFundingAmount - f.Var(&flag_loader.BigIntValue{Val: ltp.AccountFundingAmount}, "account-funding-amount", "amount in wei to fund sending accounts (set to 0 to disable)") + f.Var(&flag.BigIntValue{Val: ltp.AccountFundingAmount}, "account-funding-amount", "amount in wei to fund sending accounts (set to 0 to disable)") f.BoolVar(<p.PreFundSendingAccounts, "pre-fund-sending-accounts", false, "fund all sending accounts at start instead of on first use") f.BoolVar(<p.RefundRemainingFunds, "refund-remaining-funds", false, "refund remaining balance to funding account after completion") f.StringVar(<p.SendingAccountsFile, "sending-accounts-file", "", "file with sending account private keys, one per line (avoids pool queue and preserves accounts across runs)") diff --git a/cmd/monitor/cmd.go b/cmd/monitor/cmd.go index 01822f10d..9d205e1c4 100644 --- a/cmd/monitor/cmd.go +++ b/cmd/monitor/cmd.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" + "github.com/0xPolygon/polygon-cli/flag" "github.com/0xPolygon/polygon-cli/util" "github.com/spf13/cobra" ) @@ -57,21 +57,25 @@ var MonitorCmd = &cobra.Command{ Long: usage, Args: cobra.NoArgs, SilenceUsage: true, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - rpcUrlFlagValue := flag_loader.GetRpcUrlFlagValue(cmd) - rpcUrl = *rpcUrlFlagValue + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + rpcUrl, err = flag.GetRPCURL(cmd) + if err != nil { + return err + } + // By default, hide logs from `polycli monitor`. verbosityFlag := cmd.Flag("verbosity") if verbosityFlag != nil && !verbosityFlag.Changed { util.SetLogLevel(util.Silent) } + prettyFlag := cmd.Flag("pretty-logs") if prettyFlag != nil && prettyFlag.Value.String() == "true" { - return util.SetLogMode(util.Console) + if err = util.SetLogMode(util.Console); err != nil { + return err + } } - return nil - }, - PreRunE: func(cmd *cobra.Command, args []string) error { + return checkFlags() }, RunE: func(cmd *cobra.Command, args []string) error { @@ -81,7 +85,7 @@ var MonitorCmd = &cobra.Command{ func init() { f := MonitorCmd.Flags() - f.StringVarP(&rpcUrl, "rpc-url", "r", "http://localhost:8545", "the RPC endpoint URL") + f.StringVarP(&rpcUrl, flag.RPCURL, "r", flag.DefaultRPCURL, "the RPC endpoint URL") f.StringVarP(&batchSizeValue, "batch-size", "b", "auto", "number of requests per batch") f.IntVarP(&subBatchSize, "sub-batch-size", "s", 50, "number of requests per sub-batch") f.IntVarP(&blockCacheLimit, "cache-limit", "c", 200, "number of cached blocks for the LRU block data structure (Min 100)") @@ -89,10 +93,6 @@ func init() { } func checkFlags() (err error) { - if err = util.ValidateUrl(rpcUrl); err != nil { - return - } - interval, err = time.ParseDuration(intervalStr) if err != nil { return err diff --git a/cmd/monitorv2/monitorv2.go b/cmd/monitorv2/monitorv2.go index 14faeeb04..885e591b3 100644 --- a/cmd/monitorv2/monitorv2.go +++ b/cmd/monitorv2/monitorv2.go @@ -8,6 +8,7 @@ import ( "github.com/0xPolygon/polygon-cli/chainstore" "github.com/0xPolygon/polygon-cli/cmd/monitorv2/renderer" + "github.com/0xPolygon/polygon-cli/flag" "github.com/0xPolygon/polygon-cli/indexer" "github.com/0xPolygon/polygon-cli/util" "github.com/rs/zerolog/log" @@ -30,12 +31,18 @@ var MonitorV2Cmd = &cobra.Command{ Use: "monitorv2", Short: "Monitor v2 command stub.", Long: usage, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, args []string) (err error) { // Set default verbosity to Error level (300) if not explicitly set by user verbosityFlag := cmd.Flag("verbosity") if verbosityFlag != nil && !verbosityFlag.Changed { util.SetLogLevel(300) // Error level } + + rpcURL, err = flag.GetRequiredRPCURL(cmd) + if err != nil { + return err + } + return nil }, RunE: func(cmd *cobra.Command, args []string) error { @@ -92,7 +99,7 @@ var MonitorV2Cmd = &cobra.Command{ } func init() { - MonitorV2Cmd.Flags().StringVar(&rpcURL, "rpc-url", "", "RPC endpoint URL (required)") + MonitorV2Cmd.Flags().StringVar(&rpcURL, flag.RPCURL, "", "RPC endpoint URL (required)") MonitorV2Cmd.Flags().StringVar(&rendererType, "renderer", "tui", "renderer type (json, tview, tui)") MonitorV2Cmd.Flags().StringVar(&pprofAddr, "pprof", "", "pprof server address (e.g. 127.0.0.1:6060)") } diff --git a/cmd/nodekey/nodekey.go b/cmd/nodekey/nodekey.go index c83e4433e..bc9676b8b 100644 --- a/cmd/nodekey/nodekey.go +++ b/cmd/nodekey/nodekey.go @@ -15,7 +15,7 @@ import ( _ "embed" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" + "github.com/0xPolygon/polygon-cli/flag" gethcrypto "github.com/ethereum/go-ethereum/crypto" gethenode "github.com/ethereum/go-ethereum/p2p/enode" libp2pcrypto "github.com/libp2p/go-libp2p/core/crypto" @@ -66,11 +66,12 @@ var NodekeyCmd = &cobra.Command{ Short: "Generate node keys for different blockchain clients and protocols.", Long: usage, Args: cobra.NoArgs, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - privateKey := flag_loader.GetPrivateKeyFlagValue(cmd) - inputNodeKeyPrivateKey = *privateKey - }, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + inputNodeKeyPrivateKey, err = flag.GetPrivateKey(cmd) + if err != nil { + return err + } + switch inputNodeKeyProtocol { case "devp2p": invalidFlags := []string{"seed", "marshal-protobuf"} @@ -268,9 +269,9 @@ func generateLibp2pNodeKey(keyType int, seed bool) (nodeKeyOut, error) { func init() { f := NodekeyCmd.Flags() - f.StringVar(&inputNodeKeyPrivateKey, "private-key", "", "use the provided private key (in hex format)") + f.StringVar(&inputNodeKeyPrivateKey, flag.PrivateKey, "", "use the provided private key (in hex format)") f.StringVarP(&inputNodeKeyFile, "file", "f", "", "a file with the private nodekey (in hex format)") - NodekeyCmd.MarkFlagsMutuallyExclusive("private-key", "file") + NodekeyCmd.MarkFlagsMutuallyExclusive(flag.PrivateKey, "file") f.StringVar(&inputNodeKeyProtocol, "protocol", "devp2p", "devp2p|libp2p|pex|seed-libp2p") f.StringVar(&inputNodeKeyType, "key-type", "ed25519", "ed25519|secp256k1|ecdsa|rsa") diff --git a/cmd/p2p/nodelist/nodelist.go b/cmd/p2p/nodelist/nodelist.go index 94db5967f..533cd2abc 100644 --- a/cmd/p2p/nodelist/nodelist.go +++ b/cmd/p2p/nodelist/nodelist.go @@ -4,8 +4,8 @@ import ( "encoding/json" "os" + "github.com/0xPolygon/polygon-cli/flag" "github.com/0xPolygon/polygon-cli/p2p/database" - "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -61,7 +61,5 @@ func init() { f.IntVarP(&inputNodeListParams.Limit, "limit", "l", 100, "number of unique nodes to return") f.StringVarP(&inputNodeListParams.ProjectID, "project-id", "p", "", "GCP project ID") f.StringVarP(&inputNodeListParams.DatabaseID, "database-id", "d", "", "datastore database ID") - if err := NodeListCmd.MarkFlagRequired("project-id"); err != nil { - log.Error().Err(err).Msg("Failed to mark project-id as required flag") - } + flag.MarkFlagsRequired(NodeListCmd, "project-id") } diff --git a/cmd/p2p/query/query.go b/cmd/p2p/query/query.go index 1bd967cef..02395d208 100644 --- a/cmd/p2p/query/query.go +++ b/cmd/p2p/query/query.go @@ -9,6 +9,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/0xPolygon/polygon-cli/flag" "github.com/0xPolygon/polygon-cli/p2p" ) @@ -132,7 +133,5 @@ func init() { f.StringVarP(&inputQueryParams.KeyFile, "key-file", "k", "", "private key file (cannot be set with --key)") f.StringVar(&inputQueryParams.PrivateKey, "key", "", "hex-encoded private key (cannot be set with --key-file)") QueryCmd.MarkFlagsMutuallyExclusive("key-file", "key") - if err := QueryCmd.MarkFlagRequired("start-block"); err != nil { - log.Error().Err(err).Msg("Failed to mark start-block as required flag") - } + flag.MarkFlagsRequired(QueryCmd, "start-block") } diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index a9fa5de0d..4fc85b846 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -28,6 +28,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/0xPolygon/polygon-cli/flag" "github.com/0xPolygon/polygon-cli/p2p" "github.com/0xPolygon/polygon-cli/p2p/database" "github.com/0xPolygon/polygon-cli/rpctypes" @@ -419,15 +420,11 @@ func init() { f := SensorCmd.Flags() f.StringVarP(&inputSensorParams.Bootnodes, "bootnodes", "b", "", "comma separated nodes used for bootstrapping") f.Uint64VarP(&inputSensorParams.NetworkID, "network-id", "n", 0, "filter discovered nodes by this network ID") - if err := SensorCmd.MarkFlagRequired("network-id"); err != nil { - log.Error().Err(err).Msg("Failed to mark network-id as required persistent flag") - } + flag.MarkFlagsRequired(SensorCmd, "network-id") f.StringVarP(&inputSensorParams.ProjectID, "project-id", "p", "", "GCP project ID") f.StringVarP(&inputSensorParams.DatabaseID, "database-id", "d", "", "datastore database ID") f.StringVarP(&inputSensorParams.SensorID, "sensor-id", "s", "", "sensor ID when writing block/tx events") - if err := SensorCmd.MarkFlagRequired("sensor-id"); err != nil { - log.Error().Err(err).Msg("Failed to mark sensor-id as required persistent flag") - } + flag.MarkFlagsRequired(SensorCmd, "sensor-id") f.IntVarP(&inputSensorParams.MaxPeers, "max-peers", "m", 2000, "maximum number of peers to connect to") f.IntVarP(&inputSensorParams.MaxDatabaseConcurrency, "max-db-concurrency", "D", 10000, `maximum number of concurrent database operations to perform (increasing this diff --git a/cmd/publish/publish.go b/cmd/publish/publish.go index 58d81afc5..b1911f0a8 100644 --- a/cmd/publish/publish.go +++ b/cmd/publish/publish.go @@ -5,7 +5,7 @@ import ( _ "embed" "time" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" + "github.com/0xPolygon/polygon-cli/flag" "github.com/ethereum/go-ethereum/ethclient" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -13,10 +13,7 @@ import ( ) const ( - ArgRpcURL = "rpc-url" ArgForkID = "fork-id" - - defaultRPCURL = "http://localhost:8545" ) //go:embed publish.md @@ -26,9 +23,12 @@ var Cmd = &cobra.Command{ Use: "publish", Short: "Publish transactions to the network with high-throughput.", Long: cmdUsage, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - rpcURL := flag_loader.GetRpcUrlFlagValue(cmd) - publishInputArgs.rpcURL = *rpcURL + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + publishInputArgs.rpcURL, err = flag.GetRPCURL(cmd) + if err != nil { + return err + } + return nil }, RunE: publish, } @@ -138,7 +138,7 @@ func publish(cmd *cobra.Command, args []string) error { func init() { f := Cmd.Flags() - f.StringVar(&publishInputArgs.rpcURL, ArgRpcURL, defaultRPCURL, "RPC URL of network") + f.StringVar(&publishInputArgs.rpcURL, flag.RPCURL, flag.DefaultRPCURL, "RPC URL of network") f.Uint64VarP(&publishInputArgs.concurrency, "concurrency", "c", 1, "number of txs to send concurrently (default: one at a time)") f.Uint64Var(&publishInputArgs.jobQueueSize, "job-queue-size", 100, "number of jobs we can put in the job queue for workers to process") f.StringVar(&publishInputArgs.inputFileName, "file", "", "provide a filename with transactions to publish") diff --git a/cmd/root.go b/cmd/root.go index 25fb19269..77f3c1d4b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,31 +2,22 @@ package cmd import ( "fmt" - "github.com/0xPolygon/polygon-cli/cmd/parsebatchl2data" "os" - "github.com/0xPolygon/polygon-cli/cmd/contract" - "github.com/0xPolygon/polygon-cli/cmd/foldtrace" - "github.com/0xPolygon/polygon-cli/cmd/publish" - "github.com/0xPolygon/polygon-cli/util" - - "github.com/0xPolygon/polygon-cli/cmd/cdk" - "github.com/0xPolygon/polygon-cli/cmd/fixnoncegap" - "github.com/0xPolygon/polygon-cli/cmd/retest" - "github.com/0xPolygon/polygon-cli/cmd/ulxly" - - "github.com/0xPolygon/polygon-cli/cmd/fork" - "github.com/0xPolygon/polygon-cli/cmd/p2p" - "github.com/0xPolygon/polygon-cli/cmd/parseethwallet" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/0xPolygon/polygon-cli/cmd/abi" + "github.com/0xPolygon/polygon-cli/cmd/cdk" + "github.com/0xPolygon/polygon-cli/cmd/contract" "github.com/0xPolygon/polygon-cli/cmd/dbbench" "github.com/0xPolygon/polygon-cli/cmd/dockerlogger" "github.com/0xPolygon/polygon-cli/cmd/dumpblocks" "github.com/0xPolygon/polygon-cli/cmd/ecrecover" "github.com/0xPolygon/polygon-cli/cmd/enr" + "github.com/0xPolygon/polygon-cli/cmd/fixnoncegap" + "github.com/0xPolygon/polygon-cli/cmd/foldtrace" + "github.com/0xPolygon/polygon-cli/cmd/fork" "github.com/0xPolygon/polygon-cli/cmd/fund" "github.com/0xPolygon/polygon-cli/cmd/hash" "github.com/0xPolygon/polygon-cli/cmd/loadtest" @@ -35,11 +26,18 @@ import ( "github.com/0xPolygon/polygon-cli/cmd/monitor" "github.com/0xPolygon/polygon-cli/cmd/monitorv2" "github.com/0xPolygon/polygon-cli/cmd/nodekey" + "github.com/0xPolygon/polygon-cli/cmd/p2p" + "github.com/0xPolygon/polygon-cli/cmd/parsebatchl2data" + "github.com/0xPolygon/polygon-cli/cmd/parseethwallet" + "github.com/0xPolygon/polygon-cli/cmd/publish" + "github.com/0xPolygon/polygon-cli/cmd/retest" "github.com/0xPolygon/polygon-cli/cmd/rpcfuzz" "github.com/0xPolygon/polygon-cli/cmd/signer" + "github.com/0xPolygon/polygon-cli/cmd/ulxly" "github.com/0xPolygon/polygon-cli/cmd/version" "github.com/0xPolygon/polygon-cli/cmd/wallet" "github.com/0xPolygon/polygon-cli/cmd/wrapcontract" + "github.com/0xPolygon/polygon-cli/util" ) var ( diff --git a/cmd/rpcfuzz/cmd.go b/cmd/rpcfuzz/cmd.go index e7d159e2f..7fd59ded3 100644 --- a/cmd/rpcfuzz/cmd.go +++ b/cmd/rpcfuzz/cmd.go @@ -6,9 +6,8 @@ import ( "regexp" "strings" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" "github.com/0xPolygon/polygon-cli/cmd/rpcfuzz/argfuzz" - "github.com/0xPolygon/polygon-cli/util" + "github.com/0xPolygon/polygon-cli/flag" "github.com/ethereum/go-ethereum/crypto" fuzz "github.com/google/gofuzz" "github.com/rs/zerolog/log" @@ -42,13 +41,15 @@ var RPCFuzzCmd = &cobra.Command{ Short: "Continually run a variety of RPC calls and fuzzers.", Long: usage, Args: cobra.NoArgs, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - rpcUrlPtr := flag_loader.GetRpcUrlFlagValue(cmd) - rpcUrl = *rpcUrlPtr - privateKeyPtr := flag_loader.GetPrivateKeyFlagValue(cmd) - testPrivateHexKey = *privateKeyPtr - }, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + rpcUrl, err = flag.GetRequiredRPCURL(cmd) + if err != nil { + return err + } + testPrivateHexKey, err = flag.GetPrivateKey(cmd) + if err != nil { + return err + } return checkFlags() }, RunE: func(cmd *cobra.Command, args []string) error { @@ -59,8 +60,8 @@ var RPCFuzzCmd = &cobra.Command{ func init() { f := RPCFuzzCmd.Flags() - f.StringVarP(&rpcUrl, "rpc-url", "r", "http://localhost:8545", "RPC endpoint URL") - f.StringVar(&testPrivateHexKey, "private-key", codeQualityPrivateKey, "hex encoded private key to use for sending transactions") + f.StringVarP(&rpcUrl, flag.RPCURL, "r", flag.DefaultRPCURL, "RPC endpoint URL") + f.StringVar(&testPrivateHexKey, flag.PrivateKey, codeQualityPrivateKey, "hex encoded private key to use for sending transactions") f.StringVar(&testContractAddress, "contract-address", "", "address of contract to use for testing (if not specified, contract will be deployed automatically)") f.StringVar(&testNamespaces, "namespaces", fmt.Sprintf("eth,web3,net,debug,%s", rpcTestRawHTTPNamespace), "comma separated list of RPC namespaces to test") f.BoolVar(&testFuzz, "fuzz", false, "flag to indicate whether to fuzz input or not") @@ -86,14 +87,6 @@ func init() { } func checkFlags() (err error) { - // Check rpc-url flag. - if rpcUrl == "" { - panic("RPC URL is empty") - } - if err = util.ValidateUrl(rpcUrl); err != nil { - return - } - // Ensure only one streamer type is selected streamerCount := 0 if streamJSON { diff --git a/cmd/signer/signer.go b/cmd/signer/signer.go index 2d0e5ad0c..80bcf9cfb 100644 --- a/cmd/signer/signer.go +++ b/cmd/signer/signer.go @@ -24,7 +24,7 @@ import ( kms "cloud.google.com/go/kms/apiv1" "cloud.google.com/go/kms/apiv1/kmspb" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" + "github.com/0xPolygon/polygon-cli/flag" "github.com/0xPolygon/polygon-cli/gethkeystore" accounts2 "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -79,9 +79,12 @@ var SignerCmd = &cobra.Command{ Use: "signer", Short: "Utilities for security signing transactions.", Long: signerUsage, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - privateKey := flag_loader.GetPrivateKeyFlagValue(cmd) - inputSignerOpts.privateKey = *privateKey + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + inputSignerOpts.privateKey, err = flag.GetRequiredPrivateKey(cmd) + if err != nil { + return err + } + return nil }, Args: cobra.NoArgs, } @@ -112,7 +115,7 @@ var SignCmd = &cobra.Command{ for _, a := range accounts { accountStrings += a.Address.String() + " " } - return fmt.Errorf("the account with address <%s> could not be found in list [%s]", inputSignerOpts.keyID, accountStrings) + return fmt.Errorf("account with address %s not found in list [%s]", inputSignerOpts.keyID, accountStrings) } password, err := getKeystorePassword() if err != nil { @@ -238,9 +241,6 @@ var ImportCmd = &cobra.Command{ if err := sanityCheck(cmd, args); err != nil { return err } - if err := cmd.MarkFlagRequired("private-key"); err != nil { - return err - } return nil }, RunE: func(cmd *cobra.Command, args []string) error { @@ -270,7 +270,7 @@ var ImportCmd = &cobra.Command{ func getTxDataToSign() (*ethtypes.Transaction, error) { if inputSignerOpts.dataFile == "" { - return nil, fmt.Errorf("no datafile was specified to sign") + return nil, fmt.Errorf("datafile not specified") } dataToSign, err := os.ReadFile(inputSignerOpts.dataFile) if err != nil { @@ -561,7 +561,7 @@ func wrapKeyForGCPKMS(ctx context.Context, client *kms.KeyManagementClient) ([]b PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)}, //nolint:staticcheck }) if err != nil { - return nil, fmt.Errorf("unable to marshal private key %w", err) + return nil, fmt.Errorf("unable to marshal private key: %w", err) } keyBytes, err := asn1.Marshal(privKey) if err != nil { @@ -676,13 +676,13 @@ func (g *GCPKMS) Sign(ctx context.Context, tx *ethtypes.Transaction) error { // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: // https://cloud.google.com/kms/docs/data-integrity-guidelines if !result.VerifiedDigestCrc32C { - return fmt.Errorf("AsymmetricSign: request corrupted in-transit") + return fmt.Errorf("asymmetric sign: request corrupted in-transit") } if result.Name != req.Name { - return fmt.Errorf("AsymmetricSign: request corrupted in-transit") + return fmt.Errorf("asymmetric sign: request corrupted in-transit") } if int64(crc32c(result.Signature)) != result.SignatureCrc32C.Value { - return fmt.Errorf("AsymmetricSign: response corrupted in-transit") + return fmt.Errorf("asymmetric sign: response corrupted in-transit") } gcpPubKey, err := getPublicKeyByName(ctx, client, name) @@ -782,7 +782,7 @@ func sanityCheck(cmd *cobra.Command, args []string) error { keyStoreMethods += 1 } if keyStoreMethods > 1 { - return fmt.Errorf("Multiple conflicting keystore sources were specified") + return fmt.Errorf("multiple conflicting keystore sources were specified") } pwErr := passwordValidation(inputSignerOpts.unsafePassword) if inputSignerOpts.unsafePassword != "" && pwErr != nil { @@ -791,18 +791,18 @@ func sanityCheck(cmd *cobra.Command, args []string) error { if inputSignerOpts.kms == "GCP" { if inputSignerOpts.gcpProjectID == "" { - return fmt.Errorf("a GCP project id must be specified") + return fmt.Errorf("GCP project id must be specified") } if inputSignerOpts.gcpRegion == "" { - return fmt.Errorf("a location is required") + return fmt.Errorf("location is required") } if inputSignerOpts.gcpKeyRingID == "" { - return fmt.Errorf("a GCP Keyring ID is needed") + return fmt.Errorf("GCP keyring ID is required") } if inputSignerOpts.keyID == "" && cmd.Name() != "list" { - return fmt.Errorf("a key id is required") + return fmt.Errorf("key id is required") } } @@ -811,7 +811,7 @@ func sanityCheck(cmd *cobra.Command, args []string) error { func passwordValidation(inputPw string) error { if len(inputPw) < 6 { - return fmt.Errorf("Password only had %d character. 8 or more required", len(inputPw)) + return fmt.Errorf("password only had %d characters, 8 or more required", len(inputPw)) } return nil } @@ -847,7 +847,7 @@ func init() { f := SignerCmd.Flags() f.StringVar(&inputSignerOpts.keystore, "keystore", "", "use keystore in given folder or file") - f.StringVar(&inputSignerOpts.privateKey, "private-key", "", "use provided hex encoded private key") + f.StringVar(&inputSignerOpts.privateKey, flag.PrivateKey, "", "use provided hex encoded private key") f.StringVar(&inputSignerOpts.kms, "kms", "", "AWS or GCP if key is stored in cloud") f.StringVar(&inputSignerOpts.keyID, "key-id", "", "ID of key to be used for signing") f.StringVar(&inputSignerOpts.unsafePassword, "unsafe-password", "", "non-interactively specified password for unlocking keystore") diff --git a/cmd/ulxly/ulxly.go b/cmd/ulxly/ulxly.go index 4e062d0c2..074929e46 100644 --- a/cmd/ulxly/ulxly.go +++ b/cmd/ulxly/ulxly.go @@ -24,10 +24,10 @@ import ( "github.com/0xPolygon/polygon-cli/bindings/tokens" "github.com/0xPolygon/polygon-cli/bindings/ulxly" "github.com/0xPolygon/polygon-cli/bindings/ulxly/polygonrollupmanager" - "github.com/0xPolygon/polygon-cli/cmd/flag_loader" "github.com/0xPolygon/polygon-cli/cmd/ulxly/bridge_service" bridge_service_factory "github.com/0xPolygon/polygon-cli/cmd/ulxly/bridge_service/factory" smcerror "github.com/0xPolygon/polygon-cli/errors" + "github.com/0xPolygon/polygon-cli/flag" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -1957,23 +1957,15 @@ var ULxLyCmd = &cobra.Command{ var ulxlyBridgeAndClaimCmd = &cobra.Command{ Args: cobra.NoArgs, Hidden: true, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - var err error - rpcURL, err := flag_loader.GetRequiredRpcUrlFlagValue(cmd) + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + inputUlxlyArgs.rpcURL, err = flag.GetRequiredRPCURL(cmd) if err != nil { return err } - if rpcURL != nil { - inputUlxlyArgs.rpcURL = *rpcURL - } - - privateKey, err := flag_loader.GetRequiredPrivateKeyFlagValue(cmd) + inputUlxlyArgs.privateKey, err = flag.GetRequiredPrivateKey(cmd) if err != nil { return err } - if privateKey != nil { - inputUlxlyArgs.privateKey = *privateKey - } return nil }, } @@ -2063,9 +2055,9 @@ var ( const ( ArgGasLimit = "gas-limit" ArgChainID = "chain-id" - ArgPrivateKey = "private-key" + ArgPrivateKey = flag.PrivateKey ArgValue = "value" - ArgRPCURL = "rpc-url" + ArgRPCURL = flag.RPCURL ArgBridgeAddress = "bridge-address" ArgRollupManagerAddress = "rollup-manager-address" ArgDestNetwork = "destination-network" @@ -2105,7 +2097,7 @@ var ( bridgeServices map[uint32]bridge_service.BridgeService = make(map[uint32]bridge_service.BridgeService) ) -func prepInputs(cmd *cobra.Command, args []string) error { +func prepInputs(cmd *cobra.Command, args []string) (err error) { if inputUlxlyArgs.dryRun && inputUlxlyArgs.gasLimit == 0 { inputUlxlyArgs.gasLimit = uint64(10_000_000) } @@ -2167,13 +2159,6 @@ func prepInputs(cmd *cobra.Command, args []string) error { return nil } -func fatalIfError(err error) { - if err == nil { - return - } - log.Fatal().Err(err).Msg("Unexpected error occurred") -} - type FileOptions struct { FileName string } @@ -2230,9 +2215,7 @@ func (o *GetEvent) AddFlags(cmd *cobra.Command) { f.Uint64VarP(&o.ToBlock, ArgToBlock, "t", 0, "end of the range of blocks to retrieve") f.Uint64VarP(&o.FilterSize, ArgFilterSize, "i", 1000, "batch size for individual filter queries") f.BoolVarP(&o.Insecure, ArgInsecure, "", false, "skip TLS certificate verification") - fatalIfError(cmd.MarkFlagRequired(ArgFromBlock)) - fatalIfError(cmd.MarkFlagRequired(ArgToBlock)) - fatalIfError(cmd.MarkFlagRequired(ArgRPCURL)) + flag.MarkFlagsRequired(cmd, ArgFromBlock, ArgToBlock, ArgRPCURL) } type GetSmcOptions struct { @@ -2471,7 +2454,7 @@ or if it's actually an intermediate hash.`, fBridgeAndClaim.BoolVar(&inputUlxlyArgs.dryRun, ArgDryRun, false, "do all of the transaction steps but do not send the transaction") fBridgeAndClaim.BoolVar(&inputUlxlyArgs.insecure, ArgInsecure, false, "skip TLS certificate verification") fBridgeAndClaim.BoolVar(&inputUlxlyArgs.legacy, ArgLegacy, true, "force usage of legacy bridge service") - fatalIfError(ulxlyBridgeAndClaimCmd.MarkPersistentFlagRequired(ArgBridgeAddress)) + flag.MarkPersistentFlagsRequired(ulxlyBridgeAndClaimCmd, ArgBridgeAddress) // bridge specific args fBridge := ulxlyBridgeCmd.PersistentFlags() @@ -2481,7 +2464,7 @@ or if it's actually an intermediate hash.`, fBridge.StringVar(&inputUlxlyArgs.tokenAddress, ArgTokenAddress, "0x0000000000000000000000000000000000000000", "address of ERC20 token to use") fBridge.StringVar(&inputUlxlyArgs.callData, ArgCallData, "0x", "call data to be passed directly with bridge-message or as an ERC20 Permit") fBridge.StringVar(&inputUlxlyArgs.callDataFile, ArgCallDataFile, "", "a file containing hex encoded call data") - fatalIfError(ulxlyBridgeCmd.MarkPersistentFlagRequired(ArgDestNetwork)) + flag.MarkPersistentFlagsRequired(ulxlyBridgeCmd, ArgDestNetwork) // Claim specific args fClaim := ulxlyClaimCmd.PersistentFlags() @@ -2491,9 +2474,7 @@ or if it's actually an intermediate hash.`, fClaim.StringVar(&inputUlxlyArgs.globalIndex, ArgGlobalIndex, "", "an override of the global index value") fClaim.DurationVar(&inputUlxlyArgs.wait, ArgWait, time.Duration(0), "retry claiming until deposit is ready, up to specified duration (available for claim asset and claim message)") fClaim.StringVar(&inputUlxlyArgs.proofGER, ArgProofGER, "", "if specified and using legacy mode, the proof will be generated against this GER") - fatalIfError(ulxlyClaimCmd.MarkPersistentFlagRequired(ArgDepositCount)) - fatalIfError(ulxlyClaimCmd.MarkPersistentFlagRequired(ArgDepositNetwork)) - fatalIfError(ulxlyClaimCmd.MarkPersistentFlagRequired(ArgBridgeServiceURL)) + flag.MarkPersistentFlagsRequired(ulxlyClaimCmd, ArgDepositCount, ArgDepositNetwork, ArgBridgeServiceURL) // Claim Everything Helper Command fClaimEverything := claimEverythingCommand.Flags() @@ -2501,8 +2482,7 @@ or if it's actually an intermediate hash.`, fClaimEverything.IntVar(&inputUlxlyArgs.bridgeLimit, ArgBridgeLimit, 25, "limit the number or responses returned by the bridge service when claiming") fClaimEverything.IntVar(&inputUlxlyArgs.bridgeOffset, ArgBridgeOffset, 0, "offset to specify for pagination of underlying bridge service deposits") fClaimEverything.UintVar(&inputUlxlyArgs.concurrency, ArgConcurrency, 1, "worker pool size for claims") - - fatalIfError(claimEverythingCommand.MarkFlagRequired(ArgBridgeMappings)) + flag.MarkFlagsRequired(claimEverythingCommand, ArgBridgeMappings) // Top Level ULxLyCmd.AddCommand(ulxlyBridgeAndClaimCmd) diff --git a/flag/bigint.go b/flag/bigint.go new file mode 100644 index 000000000..09ad027f6 --- /dev/null +++ b/flag/bigint.go @@ -0,0 +1,30 @@ +package flag + +import ( + "fmt" + "math/big" +) + +// BigIntValue is a custom flag type for big.Int values. +// It implements the pflag.Value interface to enable using *big.Int with Cobra flags. +type BigIntValue struct { + Val *big.Int +} + +// String returns the decimal string representation of the big.Int value. +func (b *BigIntValue) String() string { + return b.Val.String() +} + +// Set parses a decimal string and sets the big.Int value. +func (b *BigIntValue) Set(s string) error { + if _, ok := b.Val.SetString(s, 10); !ok { + return fmt.Errorf("invalid big integer: %q", s) + } + return nil +} + +// Type returns the type string for this flag value. +func (b *BigIntValue) Type() string { + return "big.Int" +} diff --git a/flag/flag.go b/flag/flag.go new file mode 100644 index 000000000..24d87a237 --- /dev/null +++ b/flag/flag.go @@ -0,0 +1,135 @@ +// Package flag provides utilities for managing command flags with environment variable fallback support. +// It implements a priority system: flag value > environment variable > default value. +package flag + +import ( + "fmt" + "os" + + "github.com/0xPolygon/polygon-cli/util" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +const ( + // RPCURL is the flag name for RPC URL + RPCURL = "rpc-url" + // RPCURLEnvVar is the environment variable name for RPC URL + RPCURLEnvVar = "ETH_RPC_URL" + // DefaultRPCURL is the default RPC URL when no flag or env var is set + DefaultRPCURL = "http://localhost:8545" + // PrivateKey is the flag name for private key + PrivateKey = "private-key" + // PrivateKeyEnvVar is the environment variable name for private key + PrivateKeyEnvVar = "PRIVATE_KEY" +) + +// GetRPCURL retrieves the RPC URL from the command flag or environment variable. +// Returns the flag value if set, otherwise the environment variable value, otherwise empty string. +// Validates the URL format if a non-empty value is provided and returns an error if validation fails. +// Returns empty string and nil error if no value is set. +func GetRPCURL(cmd *cobra.Command) (string, error) { + value, err := getValue(cmd, RPCURL, RPCURLEnvVar, false) + if err != nil || value == "" { + return value, err + } + + if err := util.ValidateUrl(value); err != nil { + return "", err + } + + return value, nil +} + +// GetRequiredRPCURL retrieves the RPC URL from the command flag or environment variable. +// Returns the flag value if set, otherwise the environment variable value. +// Validates the URL format and returns an error if the value is not set, empty, or invalid. +func GetRequiredRPCURL(cmd *cobra.Command) (string, error) { + value, err := getValue(cmd, RPCURL, RPCURLEnvVar, true) + if err != nil { + return "", err + } + + if err := util.ValidateUrl(value); err != nil { + return "", err + } + + return value, nil +} + +// GetPrivateKey retrieves the private key from the command flag or environment variable. +// Returns the flag value if set, otherwise the environment variable value, otherwise the default. +// Returns empty string and nil error if none are set. +func GetPrivateKey(cmd *cobra.Command) (string, error) { + return getValue(cmd, PrivateKey, PrivateKeyEnvVar, false) +} + +// GetRequiredPrivateKey retrieves the private key from the command flag or environment variable. +// Returns an error if the value is not set or empty. +func GetRequiredPrivateKey(cmd *cobra.Command) (string, error) { + return getValue(cmd, PrivateKey, PrivateKeyEnvVar, true) +} + +// getValue retrieves a flag value with environment variable fallback support. +// It implements a priority system where flag values take precedence over environment variables, +// which take precedence over default values. +// +// Parameters: +// - cmd: The cobra command to retrieve the flag from +// - flagName: The name of the flag to retrieve +// - envVarName: The environment variable name to check as fallback +// - required: Whether the value is required (returns error if empty) +// +// Returns the resolved value and an error if required validation fails. +func getValue(cmd *cobra.Command, flagName, envVarName string, required bool) (string, error) { + flag := cmd.Flag(flagName) + if flag == nil { + return "", fmt.Errorf("flag %q not found", flagName) + } + + // Priority: flag > env var > default + value := flag.DefValue + + envVarValue := os.Getenv(envVarName) + if envVarValue != "" { + value = envVarValue + } + + if flag.Changed { + value = flag.Value.String() + } + + if required && value == "" { + return "", fmt.Errorf("required flag(s) %q not set", flagName) + } + + return value, nil +} + +// MarkFlagsRequired marks one or more regular flags as required and logs a fatal error if marking fails. +// This helper ensures consistent error handling across all commands when marking flags as required. +func MarkFlagsRequired(cmd *cobra.Command, flagNames ...string) { + for _, flagName := range flagNames { + if err := cmd.MarkFlagRequired(flagName); err != nil { + log.Fatal(). + Err(err). + Str("flag", flagName). + Str("command", cmd.Name()). + Msg("Failed to mark flag as required") + } + } +} + +// MarkPersistentFlagsRequired marks one or more persistent flags as required and logs a fatal error if marking fails. +// This helper ensures consistent error handling across all commands when marking persistent flags as required. +func MarkPersistentFlagsRequired(cmd *cobra.Command, flagNames ...string) { + for _, flagName := range flagNames { + if err := cmd.MarkPersistentFlagRequired(flagName); err != nil { + log.Fatal(). + Err(err). + Str("flag", flagName). + Str("command", cmd.Name()). + Msg("Failed to mark persistent flag as required") + } + } +} diff --git a/flag/flag_test.go b/flag/flag_test.go new file mode 100644 index 000000000..bc12e7147 --- /dev/null +++ b/flag/flag_test.go @@ -0,0 +1,301 @@ +package flag + +import ( + "fmt" + "os" + "strconv" + "testing" + + "github.com/0xPolygon/polygon-cli/util" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +// TestValuePriority tests the priority system for flag value resolution. +// It verifies that flag values take precedence over environment variables, +// which take precedence over default values. It also tests the required +// flag validation logic. +func TestValuePriority(t *testing.T) { + type testCase struct { + defaultValue *int + envVarValue *int + flagValue *int + required bool + + expectedValue *int + expectedError error + } + + testCases := []testCase{ + // Test case: All three sources set - flag should win + { + defaultValue: ptr(1), + envVarValue: ptr(2), + flagValue: ptr(3), + expectedValue: ptr(3), + required: true, + expectedError: nil, + }, + // Test case: Flag set to same value as default - flag should still win + { + defaultValue: ptr(1), + envVarValue: ptr(2), + flagValue: ptr(1), + expectedValue: ptr(1), + required: true, + expectedError: nil, + }, + // Test case: Default and env var set - env var should win + { + defaultValue: ptr(1), + envVarValue: ptr(2), + flagValue: nil, + expectedValue: ptr(2), + required: true, + expectedError: nil, + }, + // Test case: Default and flag set - flag should win + { + defaultValue: ptr(1), + envVarValue: nil, + flagValue: ptr(3), + expectedValue: ptr(3), + required: true, + expectedError: nil, + }, + // Test case: Env var and flag set - flag should win + { + defaultValue: nil, + envVarValue: ptr(2), + flagValue: ptr(3), + expectedValue: ptr(3), + required: true, + expectedError: nil, + }, + // Test case: Only flag set + { + defaultValue: nil, + envVarValue: nil, + flagValue: ptr(3), + expectedValue: ptr(3), + required: true, + expectedError: nil, + }, + // Test case: Only default set (non-required) + { + defaultValue: ptr(1), + envVarValue: nil, + flagValue: nil, + expectedValue: ptr(1), + required: false, + expectedError: nil, + }, + // Test case: Only default set (required) - default should satisfy requirement + { + defaultValue: ptr(1), + envVarValue: nil, + flagValue: nil, + expectedValue: ptr(1), + required: true, + expectedError: nil, + }, + // Test case: Only env var set + { + defaultValue: nil, + envVarValue: ptr(2), + flagValue: nil, + expectedValue: ptr(2), + required: true, + expectedError: nil, + }, + // Test case: Nothing set (non-required) - should return empty + { + defaultValue: nil, + envVarValue: nil, + flagValue: nil, + expectedValue: nil, + required: false, + expectedError: nil, + }, + // Test case: Nothing set (required) - should return error + { + defaultValue: nil, + envVarValue: nil, + flagValue: nil, + expectedValue: nil, + required: true, + expectedError: fmt.Errorf("required flag(s) \"flag\" not set"), + }, + } + + for _, tc := range testCases { + var value *int + cmd := &cobra.Command{ + Use: "test", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + valueStr, err := getValue(cmd, "flag", "FLAG", tc.required) + if tc.expectedError != nil { + assert.EqualError(t, err, tc.expectedError.Error()) + return + } + assert.NoError(t, err) + if valueStr != "" { + valueInt, err := strconv.Atoi(valueStr) + assert.NoError(t, err) + value = &valueInt + } + }, + Run: func(cmd *cobra.Command, args []string) { + if tc.expectedValue != nil { + assert.NotNil(t, value) + if value != nil { + assert.Equal(t, *tc.expectedValue, *value) + } + } else { + assert.Nil(t, value) + } + }, + } + if tc.defaultValue != nil { + cmd.Flags().Int("flag", *tc.defaultValue, "flag") + } else { + cmd.Flags().String("flag", "", "flag") + } + + os.Unsetenv("FLAG") + if tc.envVarValue != nil { + v := strconv.Itoa(*tc.envVarValue) + os.Setenv("FLAG", v) + } + + if tc.flagValue != nil { + v := strconv.Itoa(*tc.flagValue) + cmd.SetArgs([]string{"--flag", v}) + } + + err := cmd.Execute() + assert.Nil(t, err) + } + +} + +// ptr is a helper function to create a pointer to a value. +// This is useful for test cases where we need to distinguish between +// nil (not set) and a zero value (explicitly set to 0). +func ptr[T any](v T) *T { + return &v +} + +// TestValidateURL tests the URL validation function. +func TestValidateURL(t *testing.T) { + testCases := []struct { + name string + url string + expectError bool + }{ + { + name: "Valid HTTP URL", + url: "http://localhost:8545", + expectError: false, + }, + { + name: "Valid HTTPS URL", + url: "https://eth-mainnet.example.com", + expectError: false, + }, + { + name: "Valid WS URL", + url: "ws://localhost:8546", + expectError: false, + }, + { + name: "Valid WSS URL", + url: "wss://eth-mainnet.example.com", + expectError: false, + }, + { + name: "URL with path", + url: "https://example.com/rpc/v1", + expectError: false, + }, + { + name: "URL with port", + url: "http://localhost:8545", + expectError: false, + }, + { + name: "Empty URL", + url: "", + expectError: true, + }, + { + name: "URL without scheme", + url: "localhost:8545", + expectError: true, + }, + { + name: "URL without host", + url: "http://", + expectError: false, // util.ValidateUrl only checks scheme, not host + }, + { + name: "Invalid URL format", + url: "ht!tp://invalid", + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := util.ValidateUrl(tc.url) + if tc.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +// TestGetRPCURLValidation tests that GetRPCURL validates URLs. +func TestGetRPCURLValidation(t *testing.T) { + testCases := []struct { + name string + flagValue string + expectError bool + }{ + { + name: "Valid RPC URL", + flagValue: "http://localhost:8545", + expectError: false, + }, + { + name: "Invalid RPC URL - no scheme", + flagValue: "localhost:8545", + expectError: true, + }, + { + name: "Invalid RPC URL - no host", + flagValue: "http://", + expectError: false, // util.ValidateUrl only checks scheme, not host + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cmd := &cobra.Command{Use: "test"} + cmd.Flags().String(RPCURL, DefaultRPCURL, "test rpc url") + if tc.flagValue != "" { + cmd.SetArgs([]string{"--" + RPCURL, tc.flagValue}) + _ = cmd.Execute() + } + + _, err := GetRPCURL(cmd) + if tc.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +}