Skip to content

Commit b1ca9f8

Browse files
committed
query x/erc20 module
1 parent ae80f98 commit b1ca9f8

File tree

6 files changed

+186
-48
lines changed

6 files changed

+186
-48
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ Lazy RPC setting
1212
```bash
1313
export DEVD_EVM_RPC='https://api.securerpc.com/v1'
1414
```
15-
_By setting this environment variable, you don't need to pass --rpc flag everytime for non-localhost EVM Json-RPC_
15+
_By setting this environment variable, you don't need to pass `--rpc` flag everytime for non-localhost EVM Json-RPC_
16+
___
17+
Lazy Rest API setting
18+
```bash
19+
export DEVD_COSMOS_REST='https://cosmos.example.com:1317'
20+
```
21+
_By setting this environment variable, you don't need to pass `--rest` flag everytime for non-localhost Rest API
1622

1723
#### Query account balance
1824

cmd/query/balance.go

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/bcdevtools/devd/v2/cmd/utils"
77
"github.com/ethereum/go-ethereum"
88
"github.com/ethereum/go-ethereum/common"
9+
"github.com/ethereum/go-ethereum/ethclient"
910
"github.com/spf13/cobra"
1011
"math/big"
1112
)
@@ -23,18 +24,21 @@ func GetQueryBalanceCommand() *cobra.Command {
2324
return
2425
}
2526

26-
//fetchErc20ModuleAndVfbc := cmd.Flags().Changed(flagErc20)
27-
2827
ethClient8545, _ := mustGetEthClient(cmd, false)
29-
var bz []byte
28+
var restApiEndpoint string
29+
30+
fetchErc20ModuleAndVfbc := cmd.Flags().Changed(flagErc20)
31+
if fetchErc20ModuleAndVfbc {
32+
restApiEndpoint = mustGetRest(cmd)
33+
}
3034

3135
contextHeight := readContextHeightFromFlag(cmd)
3236

3337
accountAddr := evmAddrs[0]
3438
fmt.Println("Account", accountAddr)
3539

3640
printRow := func(colType, colContract, colSymbol, colBalance, colRaw, colDecimals, colHigh, colLow, extra string) {
37-
fmt.Printf("%-6s | %42s | %10s | %28s | %27s | %8s | %9s | %18s | %-1s\n", colType, colContract, colSymbol, colBalance, colRaw, colDecimals, colHigh, colLow, extra)
41+
fmt.Printf("%-7s | %42s | %10s | %28s | %27s | %8s | %9s | %18s | %-1s\n", colType, colContract, colSymbol, colBalance, colRaw, colDecimals, colHigh, colLow, extra)
3842
}
3943

4044
printRow("Type", "Contract", "Symbol", "Balance", "Raw", "Decimals", "High", "Low", "Extra")
@@ -50,55 +54,94 @@ func GetQueryBalanceCommand() *cobra.Command {
5054
for i := 1; i < len(evmAddrs); i++ {
5155
contractAddr := evmAddrs[i]
5256

53-
bz, err = ethClient8545.CallContract(context.Background(), ethereum.CallMsg{
54-
To: &contractAddr,
55-
Data: []byte{0x95, 0xd8, 0x9b, 0x41}, // symbol()
56-
}, contextHeight)
57-
if err != nil {
58-
utils.PrintlnStdErr("ERR: failed to get symbol for contract", contractAddr, ":", err)
59-
continue
60-
}
61-
62-
contractSymbol, err := utils.AbiDecodeString(bz)
57+
tokenBalance, tokenBalanceDisplay, contractSymbol, contractDecimals, balancePartHigh, balancePartLow, err := fetchBalanceForErc20Contract(contractAddr, contextHeight, ethClient8545, accountAddr, "contract")
6358
if err != nil {
64-
utils.PrintlnStdErr("ERR: failed to decode symbol for contract", contractAddr, ":", err)
6559
continue
6660
}
6761

68-
bz, err = ethClient8545.CallContract(context.Background(), ethereum.CallMsg{
69-
To: &contractAddr,
70-
Data: []byte{0x31, 0x3c, 0xe5, 0x67}, // decimals()
71-
}, contextHeight)
72-
if err != nil {
73-
utils.PrintlnStdErr("ERR: failed to get decimals for contract", contractAddr, ":", err)
74-
continue
75-
}
76-
77-
contractDecimals := new(big.Int).SetBytes(bz)
62+
printRow("Input", contractAddr.String(), contractSymbol, tokenBalanceDisplay, tokenBalance.String(), contractDecimals.String(), balancePartHigh.String(), balancePartLow.String(), "")
63+
}
7864

79-
var tokenBalance *big.Int
80-
bz, err = ethClient8545.CallContract(context.Background(), ethereum.CallMsg{
81-
To: &contractAddr,
82-
Data: append([]byte{0x70, 0xa0, 0x82, 0x31}, common.BytesToHash(accountAddr.Bytes()).Bytes()...), // balanceOf(address)
83-
}, contextHeight)
65+
if fetchErc20ModuleAndVfbc && restApiEndpoint != "" {
66+
erc20TokenPairs, err := fetchErc20ModuleTokenPairsFromRest(restApiEndpoint)
8467
if err != nil {
85-
utils.PrintlnStdErr("ERR: failed to get contract token", contractAddr, "balance for", accountAddr, ":", err)
86-
continue
68+
utils.PrintlnStdErr("ERR:", err)
69+
return
70+
} else {
71+
for _, erc20TokenPair := range erc20TokenPairs {
72+
contractAddr := common.HexToAddress(erc20TokenPair.Erc20Address)
73+
74+
tokenBalance, tokenBalanceDisplay, contractSymbol, contractDecimals, balancePartHigh, balancePartLow, err := fetchBalanceForErc20Contract(contractAddr, contextHeight, ethClient8545, accountAddr, "contract")
75+
if err != nil {
76+
continue
77+
}
78+
79+
if tokenBalance.Sign() == 0 {
80+
continue
81+
}
82+
83+
printRow("x/erc20", contractAddr.String(), contractSymbol, tokenBalanceDisplay, tokenBalance.String(), contractDecimals.String(), balancePartHigh.String(), balancePartLow.String(), erc20TokenPair.Denom)
84+
}
8785
}
88-
89-
tokenBalance = new(big.Int).SetBytes(bz)
90-
91-
display, high, low, err := utils.ConvertNumberIntoDisplayWithExponent(tokenBalance, int(contractDecimals.Int64()))
92-
utils.ExitOnErr(err, "failed to convert number into display with exponent")
93-
94-
printRow("Input", contractAddr.String(), contractSymbol, display, tokenBalance.String(), contractDecimals.String(), high.String(), low.String(), "")
9586
}
9687
},
9788
}
9889

9990
cmd.Flags().String(flagRpc, "", flagEvmRpcDesc)
91+
cmd.Flags().String(flagRest, "", flagCosmosRestDesc)
10092
cmd.Flags().Int64(flagHeight, 0, "query balance at specific height")
101-
cmd.Flags().String(flagErc20, "", "query balance of ERC-20 contracts of `x/erc20` module and virtual frontier bank contracts")
93+
cmd.Flags().Bool(flagErc20, false, "query balance of ERC-20 contracts of `x/erc20` module and virtual frontier bank contracts")
10294

10395
return cmd
10496
}
97+
98+
func fetchBalanceForErc20Contract(contractAddr common.Address, contextHeight *big.Int, ethClient8545 *ethclient.Client, accountAddr common.Address, contractType string) (
99+
tokenBalance *big.Int, tokenBalanceDisplay, contractSymbol string,
100+
contractDecimals, balancePartHigh, balancePartLow *big.Int,
101+
err error,
102+
) {
103+
bz, err := ethClient8545.CallContract(context.Background(), ethereum.CallMsg{
104+
To: &contractAddr,
105+
Data: []byte{0x95, 0xd8, 0x9b, 0x41}, // symbol()
106+
}, contextHeight)
107+
if err != nil {
108+
utils.PrintlnStdErr("ERR: failed to get symbol for", contractType, contractAddr, ":", err)
109+
return
110+
}
111+
112+
contractSymbol, err = utils.AbiDecodeString(bz)
113+
if err != nil {
114+
utils.PrintlnStdErr("ERR: failed to decode symbol for", contractType, contractAddr, ":", err)
115+
return
116+
}
117+
118+
bz, err = ethClient8545.CallContract(context.Background(), ethereum.CallMsg{
119+
To: &contractAddr,
120+
Data: []byte{0x31, 0x3c, 0xe5, 0x67}, // decimals()
121+
}, contextHeight)
122+
if err != nil {
123+
utils.PrintlnStdErr("ERR: failed to get decimals for", contractType, contractAddr, ":", err)
124+
return
125+
}
126+
127+
contractDecimals = new(big.Int).SetBytes(bz)
128+
129+
bz, err = ethClient8545.CallContract(context.Background(), ethereum.CallMsg{
130+
To: &contractAddr,
131+
Data: append([]byte{0x70, 0xa0, 0x82, 0x31}, common.BytesToHash(accountAddr.Bytes()).Bytes()...), // balanceOf(address)
132+
}, contextHeight)
133+
if err != nil {
134+
utils.PrintlnStdErr("ERR: failed to get", contractType, "token", contractAddr, "balance for", accountAddr, ":", err)
135+
return
136+
}
137+
138+
tokenBalance = new(big.Int).SetBytes(bz)
139+
140+
tokenBalanceDisplay, balancePartHigh, balancePartLow, err = utils.ConvertNumberIntoDisplayWithExponent(tokenBalance, int(contractDecimals.Int64()))
141+
if err != nil {
142+
utils.PrintlnStdErr("ERR: failed to convert number", tokenBalance.String(), "decimals", contractDecimals.String(), "into display with exponent for", contractType, "token balance:", err)
143+
return
144+
}
145+
146+
return
147+
}

cmd/query/cosmos.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package query
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/bcdevtools/devd/v2/cmd/utils"
7+
"github.com/bcdevtools/devd/v2/constants"
8+
"github.com/pkg/errors"
9+
"github.com/spf13/cobra"
10+
"io"
11+
"net/http"
12+
"os"
13+
"strings"
14+
)
15+
16+
func mustGetRest(cmd *cobra.Command) (rest string) {
17+
var inputSource string
18+
19+
if restFromFlagRest, _ := cmd.Flags().GetString(flagRest); len(restFromFlagRest) > 0 {
20+
rest = restFromFlagRest
21+
inputSource = "flag"
22+
} else if restFromEnv := os.Getenv(constants.ENV_COSMOS_REST); len(restFromEnv) > 0 {
23+
rest = restFromEnv
24+
inputSource = "environment variable"
25+
} else {
26+
rest = constants.DEFAULT_COSMOS_REST
27+
inputSource = "default"
28+
}
29+
30+
rest = strings.TrimSuffix(rest, "/")
31+
32+
fmt.Println("Connecting to", rest, fmt.Sprintf("(from %s)", inputSource))
33+
34+
// pre-flight check to ensure the connection is working
35+
_, err := http.Get(rest)
36+
if err != nil && strings.Contains(err.Error(), "connection refused") {
37+
utils.PrintlnStdErr("ERR: failed to connect to Rest API, please check the connection and try again.")
38+
utils.PrintfStdErr("ERR: if you are using a custom Rest API endpoint, please provide it via flag '--%s <your_custom>' or setting environment variable 'export %s=<your_custom>'.\n", flagRest, constants.ENV_COSMOS_REST)
39+
os.Exit(1)
40+
}
41+
42+
return
43+
}
44+
45+
func fetchErc20ModuleTokenPairsFromRest(rest string) (erc20ModuleTokenPairs []Erc20ModuleTokenPair, err error) {
46+
var resp *http.Response
47+
resp, err = http.Get(rest + "/evmos/erc20/v1/token_pairs")
48+
if err != nil {
49+
err = errors.Wrap(err, "failed to fetch ERC-20 module token pairs")
50+
return
51+
}
52+
53+
if resp.StatusCode != http.StatusOK {
54+
return nil, fmt.Errorf("failed to fetch ERC-20 module token pairs! Status code: %d", resp.StatusCode)
55+
}
56+
57+
type responseStruct struct {
58+
TokenPairs []Erc20ModuleTokenPair `json:"token_pairs"`
59+
}
60+
61+
defer func() {
62+
_ = resp.Body.Close()
63+
}()
64+
65+
bz, err := io.ReadAll(resp.Body)
66+
if err != nil {
67+
return nil, errors.Wrap(err, "failed to read response body of ERC-20 module token pairs")
68+
}
69+
70+
var response responseStruct
71+
err = json.Unmarshal(bz, &response)
72+
if err != nil {
73+
return nil, errors.Wrap(err, "failed to unmarshal response body of ERC-20 module token pairs")
74+
}
75+
76+
erc20ModuleTokenPairs = response.TokenPairs
77+
return
78+
}
79+
80+
type Erc20ModuleTokenPair struct {
81+
Erc20Address string `json:"erc20_address"`
82+
Denom string `json:"denom"`
83+
Enabled bool `json:"enabled"`
84+
}

cmd/query/evm.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@ func getEvmAddressFromAnyFormatAddress(addrs ...string) (evmAddrs []common.Addre
9595
return
9696
}
9797

98-
func mustGetEthClient(cmd *cobra.Command, fallbackDeprecatedFlagHost bool) (*ethclient.Client, string) {
99-
var rpc, inputSource string
98+
func mustGetEthClient(cmd *cobra.Command, fallbackDeprecatedFlagHost bool) (ethClient8545 *ethclient.Client, rpc string) {
99+
var inputSource string
100+
var err error
100101

101102
if rpcFromFlagRpc, _ := cmd.Flags().GetString(flagRpc); len(rpcFromFlagRpc) > 0 {
102103
rpc = rpcFromFlagRpc
@@ -115,7 +116,7 @@ func mustGetEthClient(cmd *cobra.Command, fallbackDeprecatedFlagHost bool) (*eth
115116

116117
fmt.Println("Connecting to", rpc, fmt.Sprintf("(from %s)", inputSource))
117118

118-
ethClient8545, err := ethclient.Dial(rpc)
119+
ethClient8545, err = ethclient.Dial(rpc)
119120
utils.ExitOnErr(err, "failed to connect to EVM Json-RPC")
120121

121122
// pre-flight check to ensure the connection is working
@@ -126,7 +127,7 @@ func mustGetEthClient(cmd *cobra.Command, fallbackDeprecatedFlagHost bool) (*eth
126127
os.Exit(1)
127128
}
128129

129-
return ethClient8545, rpc
130+
return
130131
}
131132

132133
func readContextHeightFromFlag(cmd *cobra.Command) *big.Int {

cmd/query/root.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
const (
99
flagRpc = "rpc"
10+
flagRest = "rest"
1011
flagFull = "full"
1112
flagTracer = "tracer"
1213
flagHeight = "height"
@@ -15,7 +16,8 @@ const (
1516
)
1617

1718
const (
18-
flagEvmRpcDesc = "EVM Json-RPC endpoint, default is " + constants.DEFAULT_EVM_RPC + ", can be set by environment variable " + constants.ENV_EVM_RPC
19+
flagEvmRpcDesc = "EVM Json-RPC endpoint, default is " + constants.DEFAULT_EVM_RPC + ", can be set by environment variable " + constants.ENV_EVM_RPC
20+
flagCosmosRestDesc = "Cosmos Rest API endpoint, default is " + constants.DEFAULT_COSMOS_REST + ", can be set by environment variable " + constants.ENV_COSMOS_REST
1921
)
2022

2123
// Commands registers a sub-tree of commands

constants/constants.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ const (
99

1010
//goland:noinspection GoSnakeCaseUsage
1111
const (
12-
ENV_EVM_RPC = "DEVD_EVM_RPC"
12+
ENV_EVM_RPC = "DEVD_EVM_RPC"
13+
ENV_COSMOS_REST = "DEVD_COSMOS_REST"
1314

14-
DEFAULT_EVM_RPC = "http://localhost:8545"
15+
DEFAULT_EVM_RPC = "http://localhost:8545"
16+
DEFAULT_COSMOS_REST = "http://localhost:1317"
1517
)

0 commit comments

Comments
 (0)