Skip to content

Commit df3eb1d

Browse files
committed
add new command devd q eth_call
1 parent 1c5ef3c commit df3eb1d

File tree

11 files changed

+217
-20
lines changed

11 files changed

+217
-20
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ devd query debug_traceTransaction [0xHash] [--tracer callTracer] [--evm-rpc http
7676
# devd q trace 0xHash
7777
# devd q trace 0xHash --tracer callTracer
7878

79+
devd query eth_call 0xContractAddr 0xCallData [--evm-rpc http://localhost:8545] [--from 0xFromAddr] [--height 5m/0xHex/latest] [--gas 500k/hex] [--gas-prices 20e9/hex] [--value 1e18/hex]
80+
7981
devd query eth_chainId [--evm-rpc http://localhost:8545]
8082
```
8183

cmd/flags/common.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package flags
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
"strings"
7+
8+
"github.com/bcdevtools/devd/v3/cmd/utils"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
const (
13+
FlagHeight = "height"
14+
)
15+
16+
// ReadFlagShortIntOrHexOrNil reads a flag value as decimal or hexadecimal and returns a big.Int.
17+
// If the flag value is empty, it returns nil.
18+
func ReadFlagShortIntOrHexOrNil(cmd *cobra.Command, flag string) (*big.Int, error) {
19+
value, _ := cmd.Flags().GetString(flag)
20+
if value == "" {
21+
return nil, nil
22+
}
23+
24+
return utils.ReadShortIntOrHex(value)
25+
}
26+
27+
// ReadFlagShortIntOrHexOrZero reads a flag value as decimal or hexadecimal and returns an uint64.
28+
// If the flag value is empty, it returns zero.
29+
func ReadFlagShortIntOrHexOrZero(cmd *cobra.Command, flag string) (finalValue uint64, err error) {
30+
var bi *big.Int
31+
defer func() {
32+
if err == nil && bi != nil {
33+
if !bi.IsUint64() {
34+
err = fmt.Errorf("value is out of range: %s", bi.String())
35+
} else {
36+
finalValue = bi.Uint64()
37+
}
38+
}
39+
}()
40+
41+
value, _ := cmd.Flags().GetString(flag)
42+
if value == "" {
43+
return
44+
}
45+
46+
bi, err = utils.ReadShortIntOrHex(value)
47+
return
48+
}
49+
50+
// ReadFlagBlockNumberOrNil reads a flag value as block number and returns a big.Int.
51+
// If the flag value is empty or zero or "latest", it returns nil.
52+
func ReadFlagBlockNumberOrNil(cmd *cobra.Command, flag string) (*big.Int, error) {
53+
heightStr, _ := cmd.Flags().GetString(flag)
54+
heightStr = strings.TrimSpace(strings.ToLower(heightStr))
55+
if heightStr == "" || heightStr == "latest" {
56+
return nil, nil
57+
}
58+
59+
height, err := ReadFlagShortIntOrHexOrNil(cmd, flag)
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
if height.Sign() == 0 {
65+
return nil, nil
66+
}
67+
68+
return height, nil
69+
}

cmd/flags/gas.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package flags
2+
3+
const (
4+
FlagGas = "gas"
5+
FlagGasPrices = "gas-prices"
6+
)

cmd/query/balance.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ Bech32 account address is accepted.`, flagErc20),
138138

139139
// start fetching balances
140140

141-
contextHeight := readContextHeightFromFlag(cmd)
141+
contextHeight, err := flags.ReadFlagBlockNumberOrNil(cmd, flags.FlagHeight)
142+
utils.ExitOnErr(err, "failed to parse block number")
142143

143144
accountAddr := evmAddrs[0]
144145
utils.PrintlnStdErr("INF: Account", accountAddr)
@@ -174,7 +175,7 @@ Bech32 account address is accepted.`, flagErc20),
174175

175176
cmd.Flags().String(flags.FlagEvmRpc, "", flags.FlagEvmRpcDesc)
176177
cmd.Flags().String(flags.FlagCosmosRest, "", flags.FlagCosmosRestDesc)
177-
cmd.Flags().Int64(flagHeight, 0, "query balance at specific height")
178+
cmd.Flags().Int64(flags.FlagHeight, 0, "query balance at specific height")
178179
cmd.Flags().Bool(flagErc20, false, "query balance of ERC-20 contracts of `x/erc20` module and virtual frontier bank contracts")
179180

180181
return cmd

cmd/query/deprecated_aliases.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package query
22

33
import (
4+
"os"
5+
46
"github.com/bcdevtools/devd/v3/cmd/utils"
57
"github.com/bcdevtools/devd/v3/constants"
68
"github.com/spf13/cobra"
7-
"os"
89
)
910

1011
func GetDeprecatedAliasBlockAsCommand() *cobra.Command {

cmd/query/erc20.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ Support bech32 address format`,
2626
evmAddrs, err := utils.GetEvmAddressFromAnyFormatAddress(args...)
2727
utils.ExitOnErr(err, "failed to get evm address from input")
2828

29-
contextHeight := readContextHeightFromFlag(cmd)
29+
contextHeight, err := flags.ReadFlagBlockNumberOrNil(cmd, flags.FlagHeight)
30+
utils.ExitOnErr(err, "failed to parse block number")
3031

3132
var contractAddr, accountAddr common.Address
3233

@@ -95,7 +96,7 @@ Support bech32 address format`,
9596
}
9697

9798
cmd.Flags().String(flags.FlagEvmRpc, "", flags.FlagEvmRpcDesc)
98-
cmd.Flags().Int64(flagHeight, 0, "query balance at specific height")
99+
cmd.Flags().Int64(flags.FlagHeight, 0, "query balance at specific height")
99100

100101
return cmd
101102
}

cmd/query/eth_call.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package query
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"fmt"
7+
"math/big"
8+
"strings"
9+
10+
"github.com/bcdevtools/devd/v3/cmd/flags"
11+
"github.com/bcdevtools/devd/v3/cmd/utils"
12+
"github.com/ethereum/go-ethereum"
13+
"github.com/ethereum/go-ethereum/common"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
func GetQueryEvmRpcEthCallCommand() *cobra.Command {
18+
const flagFrom = "from"
19+
const flagValue = "value"
20+
21+
cmd := &cobra.Command{
22+
Use: "eth_call [contract address] [call data]",
23+
Short: "Call `eth_call` of EVM RPC: executes a new EVM message call immediately without creating a transaction on the block chain",
24+
Args: cobra.ExactArgs(2),
25+
Run: func(cmd *cobra.Command, args []string) {
26+
ethClient, _ := flags.MustGetEthClient(cmd)
27+
28+
contractAddress := common.HexToAddress(args[0])
29+
callData, err := hex.DecodeString(strings.TrimPrefix(strings.ToLower(args[1]), "0x"))
30+
utils.ExitOnErr(err, "failed to decode call data")
31+
32+
contextHeight := func() *big.Int {
33+
height, err := flags.ReadFlagBlockNumberOrNil(cmd, flags.FlagHeight)
34+
utils.ExitOnErr(err, "failed to parse block number")
35+
if height != nil && height.Sign() == 1 {
36+
utils.PrintfStdErr("INF: using block number: %s\n", height.String())
37+
}
38+
return height
39+
}()
40+
41+
result, err := ethClient.CallContract(context.Background(), ethereum.CallMsg{
42+
From: func() common.Address {
43+
from, _ := cmd.Flags().GetString(flagFrom)
44+
if from == "" {
45+
return common.Address{}
46+
}
47+
fromAddr := common.HexToAddress(from)
48+
utils.PrintfStdErr("INF: using from: %s\n", fromAddr)
49+
return fromAddr
50+
}(),
51+
To: &contractAddress,
52+
Gas: func() uint64 {
53+
gas, err := flags.ReadFlagShortIntOrHexOrZero(cmd, flags.FlagGas)
54+
utils.ExitOnErr(err, "failed to parse gas")
55+
if gas > 0 {
56+
utils.PrintfStdErr("INF: using gas: %d\n", gas)
57+
if gas < 21000 {
58+
utils.PrintlnStdErr("WARN: gas is less than 21000, it may not enough for the call")
59+
}
60+
}
61+
return gas
62+
}(),
63+
GasPrice: func() *big.Int {
64+
gasPrice, err := flags.ReadFlagShortIntOrHexOrNil(cmd, flags.FlagGasPrices)
65+
utils.ExitOnErr(err, "failed to parse gas price")
66+
if gasPrice != nil && gasPrice.Sign() == 1 {
67+
utils.PrintfStdErr("INF: using gas-price: %s\n", gasPrice)
68+
}
69+
return gasPrice
70+
}(),
71+
GasFeeCap: nil,
72+
GasTipCap: nil,
73+
Value: func() *big.Int {
74+
value, err := flags.ReadFlagShortIntOrHexOrNil(cmd, flagValue)
75+
utils.ExitOnErr(err, "failed to parse value")
76+
if value != nil && value.Sign() == 1 {
77+
utils.PrintfStdErr("INF: using value: %s\n", value)
78+
}
79+
return value
80+
}(),
81+
Data: callData,
82+
AccessList: nil,
83+
}, contextHeight)
84+
utils.ExitOnErr(err, "failed to call contract")
85+
86+
fmt.Println("0x" + hex.EncodeToString(result))
87+
},
88+
}
89+
90+
cmd.Flags().String(flags.FlagEvmRpc, "", flags.FlagEvmRpcDesc)
91+
cmd.Flags().StringP(flagFrom, "f", "", "the address from which the transaction is sent")
92+
cmd.Flags().StringP(flags.FlagGas, "g", "", "the integer of gas provided for the transaction execution, support short int and hex")
93+
cmd.Flags().StringP(flags.FlagGasPrices, "p", "", "the integer of gasPrice used for each paid gas encoded as hexadecimal, support short int and hex")
94+
cmd.Flags().StringP(flagValue, "v", "", "the integer of value sent with this transaction encoded as hexadecimal, support short int and hex")
95+
cmd.Flags().StringP(flags.FlagHeight, "h", "latest", "the context height of the block to exec, accept \"latest\"/short int/hex")
96+
97+
return cmd
98+
}

cmd/query/eth_chainId.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package query
33
import (
44
"context"
55
"fmt"
6+
67
"github.com/bcdevtools/devd/v3/cmd/flags"
78
"github.com/bcdevtools/devd/v3/cmd/utils"
89
"github.com/spf13/cobra"

cmd/query/root.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
package query
22

33
import (
4-
"math/big"
5-
64
"github.com/spf13/cobra"
75
)
86

9-
const (
10-
flagHeight = "height"
11-
)
12-
137
// Commands registers a sub-tree of commands
148
func Commands() *cobra.Command {
159
cmd := &cobra.Command{
@@ -27,6 +21,7 @@ func Commands() *cobra.Command {
2721
GetQueryEvmRpcEthGetTransactionReceiptCommand(),
2822
GetQueryEvmRpcEthGetBlockByNumberCommand(),
2923
GetQueryEvmRpcEthChainIdCommand(),
24+
GetQueryEvmRpcEthCallCommand(),
3025
GetQueryEvmRpcDebugTraceTransactionCommand(),
3126
// fake command for deprecated alias
3227
GetDeprecatedAliasBlockAsCommand(),
@@ -35,12 +30,3 @@ func Commands() *cobra.Command {
3530

3631
return cmd
3732
}
38-
39-
func readContextHeightFromFlag(cmd *cobra.Command) *big.Int {
40-
height, _ := cmd.Flags().GetInt64(flagHeight)
41-
if height > 0 {
42-
return big.NewInt(height)
43-
}
44-
45-
return nil
46-
}

cmd/utils/input_util.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ func tryReadPipe() (dataFromPipe string, err error) {
8080
return
8181
}
8282

83+
func ReadShortIntOrHex(input string) (out *big.Int, err error) {
84+
normalizedInput := strings.ToLower(strings.TrimSpace(input))
85+
if strings.HasPrefix(normalizedInput, "0x") {
86+
number, ok := new(big.Int).SetString(strings.TrimPrefix(normalizedInput, "0x"), 16)
87+
if !ok {
88+
err = fmt.Errorf("failed to convert hexadecimal to decimal: %s", normalizedInput)
89+
return
90+
}
91+
92+
out = number
93+
return
94+
}
95+
96+
return ReadShortInt(normalizedInput)
97+
}
98+
8399
func ReadShortInt(input string) (out *big.Int, err error) {
84100
normalizedInput := strings.ToLower(strings.TrimSpace(input))
85101

0 commit comments

Comments
 (0)