diff --git a/cmd/solizard/embeds/address_book.json b/cmd/solizard/embeds/contract_infos.json similarity index 69% rename from cmd/solizard/embeds/address_book.json rename to cmd/solizard/embeds/contract_infos.json index 99ffd8c..6f60d4e 100644 --- a/cmd/solizard/embeds/address_book.json +++ b/cmd/solizard/embeds/contract_infos.json @@ -1,5 +1,6 @@ -{ - "ERC20": { +[ + { + "name": "ERC20", "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7" } -} \ No newline at end of file +] \ No newline at end of file diff --git a/cmd/solizard/solizard.go b/cmd/solizard/solizard.go index 5189d73..4a1d90b 100644 --- a/cmd/solizard/solizard.go +++ b/cmd/solizard/solizard.go @@ -31,12 +31,12 @@ const SolizardDir = ".solizard" var ( // AbiDir is the directory where all abi files are stored // default is $HOME/.solizard/abis - AbiDir = "abis" - ZeroAddr = common.Address{} - ConfigExist = false - AddrBookExist = false - conf *config.Config - AddrBook config.AddressBook + AbiDir = "abis" + ZeroAddr = common.Address{} + ConfigExist = false + ContractInfoExist = false + Conf *config.Config + ContractInfos []config.ContractInfo ) func init() { @@ -79,8 +79,8 @@ func init() { return err } } - if d.Type().IsRegular() && d.Name() == "address_book.json" { - if err := os.WriteFile(homeDir+"/"+SolizardDir+"/address_book.json", data, 0644); err != nil { + if d.Type().IsRegular() && d.Name() == "contract_infos.json" { + if err := os.WriteFile(homeDir+"/"+SolizardDir+"/contract_infos.json", data, 0644); err != nil { return err } } @@ -97,28 +97,28 @@ func init() { os.Exit(1) } ConfigPath := dir + "/" + "config.toml" - if conf, err = config.ReadConfig(ConfigPath); err != nil { + if Conf, err = config.ReadConfig(ConfigPath); err != nil { fmt.Printf("failed to read config file (reason: %v)\n", err) ConfigExist = false } else { ConfigExist = true } - AddrBookPath := dir + "/" + "address_book.json" - if AddrBook, err = config.ReadAddressBook(AddrBookPath); err != nil { - fmt.Printf("failed to read address book (reason: %v)\n", err) - AddrBookExist = false + contractInfosPath := dir + "/" + "contract_infos.json" + if ContractInfos, err = config.ReadContractInfos(contractInfosPath); err != nil { + fmt.Printf("failed to read contract infos (reason: %v)\n", err) + ContractInfoExist = false } else { - if err = AddrBook.Validate(); err != nil { + if err = config.ValidateContractInfos(ContractInfos); err != nil { fmt.Printf("address book is invalid (reason: %v)\n", err) panic(err) } - AddrBookExist = true + ContractInfoExist = true } } func Run() error { - fmt.Println(`🦎 Welcome to Solizard v1.5.0 🦎`) + fmt.Println(`🦎 Welcome to Solizard v1.6.0 🦎`) mAbi, err := internalabi.LoadABIs(AbiDir) if err != nil { return err @@ -127,7 +127,7 @@ func Run() error { sctx := new(ctx.Context) if ConfigExist { if prompt.MustSelectApplyConfig() { - sctx = ctx.NewCtx(conf) + sctx = ctx.NewCtx(Conf) } } @@ -149,17 +149,20 @@ func Run() error { } INPUT_CONTRACT_ADDRESS: // check if address book exists - useAddrBook := false + useContractInfo := false var contractAddress string - if AddrBookExist { - if value, exists := AddrBook[selectedContractName]; exists { - if yes := prompt.MustSelectAddressBookUsage(value.Address); yes { - contractAddress = value.Address - useAddrBook = true + if ContractInfoExist { + for _, ci := range ContractInfos { + if ci.Name == selectedContractName { + if yes := prompt.MustSelectAddressBookUsage(ci.Address); yes { + contractAddress = ci.Address + useContractInfo = true + break + } } } } - if !useAddrBook { + if !useContractInfo { contractAddress = prompt.MustInputContractAddress() } if err := validation.ValidateContractAddress(sctx, contractAddress); err != nil { @@ -230,7 +233,7 @@ func Run() error { } fmt.Printf("transaction sent (txHash %v).\n", signedTx.Hash().Hex()) // sleep for x seconds to wait for transaction to be mined - waitTime, err := time.ParseDuration(conf.WaitTime) + waitTime, err := time.ParseDuration(Conf.WaitTime) fmt.Printf("waiting for transaction to be mined... (sleep %s\n", waitTime.String()) time.Sleep(waitTime) receipt, err := sctx.EthClient().TransactionReceipt(context.TODO(), signedTx.Hash()) diff --git a/internal/abi/abi.go b/internal/abi/abi.go index 09dda05..f250738 100644 --- a/internal/abi/abi.go +++ b/internal/abi/abi.go @@ -80,31 +80,52 @@ func GetMethodsByType(contractABI abi.ABI, rw MethodType) map[string]abi.Method func ParseArrayOrSliceInput(input string, typ abi.Type) interface{} { // parse the input string for array or slice type // example input format: "[1,2,3]" or "1,2,3" + // 1. trim whitespace + input = strings.ReplaceAll(input, " ", "") + // 2. remove brackets if present input = strings.Trim(input, "[]") + // 3. split the input string by comma parts := strings.Split(input, ",") - values := make([]interface{}, len(parts)) - - for i, part := range parts { - var value interface{} + var addresses []common.Address + var integers []*big.Int + var booleans []bool + var strings []string + var bytes [][]byte + var hashes []common.Hash + for _, part := range parts { switch typ.Elem.T { case abi.IntTy, abi.UintTy: - value, _ = new(big.Int).SetString(part, 10) + value, _ := new(big.Int).SetString(part, 10) + integers = append(integers, value) case abi.BoolTy: - value = part == "true" + booleans = append(booleans, part == "true") case abi.StringTy: - value = part + strings = append(strings, part) case abi.AddressTy: - value = common.HexToAddress(part) + v := common.HexToAddress(part) + addresses = append(addresses, v) case abi.FixedBytesTy, abi.BytesTy: - value = common.Hex2Bytes(part) + bytes = append(bytes, common.Hex2Bytes(part)) case abi.HashTy: - value = common.HexToHash(part) + hashes = append(hashes, common.HexToHash(part)) default: panic("unsupported array or slice element type: " + typ.Elem.String()) } - values[i] = value } - return values + if len(addresses) > 0 { + return addresses + } else if len(integers) > 0 { + return integers + } else if len(booleans) > 0 { + return booleans + } else if len(strings) > 0 { + return strings + } else if len(bytes) > 0 { + return bytes + } else if len(hashes) > 0 { + return hashes + } + return nil } func ParseTupleInput(input string, typ abi.Type) interface{} { diff --git a/internal/config/address_book.go b/internal/config/address_book.go deleted file mode 100644 index e80c44f..0000000 --- a/internal/config/address_book.go +++ /dev/null @@ -1,44 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/ethereum/go-ethereum/common" -) - -type AddressBook map[string]struct { - Address string `json:"address"` -} - -// ReadAddressBook reads the address book from the given path -func ReadAddressBook(path string) (AddressBook, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - - var ab AddressBook - if err = json.Unmarshal(data, &ab); err != nil { - return nil, err - } - return ab, nil -} - -func (ab AddressBook) Validate() error { - // check if the address book is empty - if len(ab) == 0 { - return fmt.Errorf("address book is empty") - } - for name, entry := range ab { - if entry.Address == "" { - return fmt.Errorf("address book entry %s has empty address", name) - } - if !common.IsHexAddress(entry.Address) { - return fmt.Errorf("address book entry %s has invalid address", name) - } - } - - return nil -} diff --git a/internal/config/contract_info.go b/internal/config/contract_info.go new file mode 100644 index 0000000..9fac978 --- /dev/null +++ b/internal/config/contract_info.go @@ -0,0 +1,49 @@ +package config + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/common" +) + +type ContractInfo struct { + Name string `json:"name"` + Address string `json:"address"` +} + +func ReadContractInfos(path string) ([]ContractInfo, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var ci []ContractInfo + if err = json.Unmarshal(data, &ci); err != nil { + return nil, err + } + return ci, nil +} + +func (ci ContractInfo) Validate() error { + if ci.Name == "" { + return fmt.Errorf("contract name is empty") + } + if ci.Address == "" { + return fmt.Errorf("contract address is empty") + } + if !common.IsHexAddress(ci.Address) { + return fmt.Errorf("contract address is invalid") + } + return nil +} + +func ValidateContractInfos(cis []ContractInfo) error { + for _, c := range cis { + if err := c.Validate(); err != nil { + return err + } + } + return nil +}