Skip to content

Commit

Permalink
feat(validation): enhance input validation in FetchABI function
Browse files Browse the repository at this point in the history
- Add address length check (must be 42 characters)
- Add rpcURL emptiness check
- Move input validation from main.go to abi_fetcher.go
- Improve error handling for invalid inputs
- Maintain existing functionality while adding new validations
  • Loading branch information
portdeveloper committed Aug 13, 2024
1 parent 8486e67 commit 9e04aba
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 42 deletions.
24 changes: 22 additions & 2 deletions abi_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strconv"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -25,6 +26,18 @@ func NewABIFetcher(storage *ABIStorage, etherscanAPIs map[int]ChainAPI) *ABIFetc
}

func (af *ABIFetcher) FetchABI(c *gin.Context, chainId string, address string, rpcURL string) (gin.H, error) {
if _, err := strconv.Atoi(chainId); err != nil {
return nil, &InvalidInputError{message: "Invalid chainId: must be a number"}
}

if len(address) != 42 {
return nil, &InvalidInputError{message: "Invalid address: must be 42 characters long (including '0x' prefix)"}
}

if rpcURL == "" {
return nil, &InvalidInputError{message: "Invalid rpcURL: cannot be empty"}
}

if item, ok := af.storage.Get(chainId + "-" + address); ok {
return af.createResponse(item), nil
}
Expand All @@ -33,9 +46,13 @@ func (af *ABIFetcher) FetchABI(c *gin.Context, chainId string, address string, r
if err != nil {
return nil, &InvalidInputError{message: "Failed to connect to Ethereum node: " + err.Error()}
}
defer client.Close()

if err := af.validateContract(c.Request.Context(), client, address); err != nil {
return nil, err
if _, ok := err.(*InvalidInputError); ok {
return nil, err
}
return nil, fmt.Errorf("failed to validate contract: %v", err)
}

proxyInfo, err := DetectProxyTarget(c.Request.Context(), client, common.HexToAddress(address))
Expand Down Expand Up @@ -63,10 +80,13 @@ func (af *ABIFetcher) FetchABI(c *gin.Context, chainId string, address string, r
func (af *ABIFetcher) validateContract(ctx context.Context, client *ethclient.Client, address string) error {
code, err := client.CodeAt(ctx, common.HexToAddress(address), nil)
if err != nil {
if _, ok := err.(*url.Error); ok {
return &InvalidInputError{message: "Invalid RPC URL or network error: " + err.Error()}
}
return fmt.Errorf("failed to check contract code: %v", err)
}
if len(code) == 0 {
return fmt.Errorf("the provided address is not a contract")
return &ContractNotFoundError{address: address}
}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type ContractNotFoundError struct {
}

func (e *ContractNotFoundError) Error() string {
return "Contract not found at address: " + e.address
return "The address: " + e.address + " is not a contract"
}

type EtherscanAPIError struct {
Expand Down
51 changes: 12 additions & 39 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@ package main

import (
"errors"
"fmt"
"log"
"net/http"
"strconv"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
Expand All @@ -17,6 +13,7 @@ import (
var (
storage *ABIStorage
etherscanAPIs map[int]ChainAPI
abiFetcher *ABIFetcher
)

var ErrABINotFound = errors.New("ABI not found")
Expand All @@ -35,6 +32,8 @@ func init() {
etherscanAPIs[11155111] = &GenericEtherscanAPI{BaseURL: "https://api-sepolia.etherscan.io/api", EnvKey: "SEPOLIA_API_KEY"}
etherscanAPIs[10] = &GenericEtherscanAPI{BaseURL: "https://api-optimistic.etherscan.io/api", EnvKey: "OPTIMISM_API_KEY"}
etherscanAPIs[56] = &GenericEtherscanAPI{BaseURL: "https://api.bscscan.com/api", EnvKey: "BSC_API_KEY"}

abiFetcher = NewABIFetcher(storage, etherscanAPIs)
}

func main() {
Expand All @@ -60,46 +59,20 @@ func healthCheck(c *gin.Context) {
func getABI(c *gin.Context) {
chainId := c.Param("chainId")
address := c.Param("address")
rpcURL := strings.TrimPrefix(c.Param("rpcUrl"), "/")

if err := validateInput(chainId, address, rpcURL); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
rpcURL := c.Param("rpcUrl")[1:] // Remove the leading slash

abiFetcher := NewABIFetcher(storage, etherscanAPIs)
response, err := abiFetcher.FetchABI(c, chainId, address, rpcURL)
if err != nil {
status, errorMessage := handleError(err)
c.JSON(status, gin.H{"error": errorMessage})
switch e := err.(type) {
case *InvalidInputError:
c.JSON(http.StatusBadRequest, gin.H{"error": e.Error()})
case *ContractNotFoundError:
c.JSON(http.StatusNotFound, gin.H{"error": e.Error()})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error: " + err.Error()})
}
return
}

c.JSON(http.StatusOK, response)
}

func validateInput(chainId, address, rpcURL string) error {
if _, err := strconv.Atoi(chainId); err != nil {
return fmt.Errorf("invalid chainId: must be a number")
}
if !common.IsHexAddress(address) || len(address) != 42 {
return fmt.Errorf("invalid address: must be a valid 42-character Ethereum address (including '0x' prefix)")
}
if rpcURL == "" {
return fmt.Errorf("invalid rpcURL: cannot be empty")
}
return nil
}

func handleError(err error) (int, string) {
switch err.(type) {
case *InvalidInputError:
return http.StatusBadRequest, err.Error()
case *ContractNotFoundError:
return http.StatusNotFound, err.Error()
case *EtherscanAPIError:
return http.StatusServiceUnavailable, "External API error: " + err.Error()
default:
return http.StatusInternalServerError, "Internal server error"
}
}

0 comments on commit 9e04aba

Please sign in to comment.