From 54084822256cce3c23e6845fb0e5e878a9b0de46 Mon Sep 17 00:00:00 2001 From: 0x19 Date: Tue, 20 Jun 2023 23:05:07 +0200 Subject: [PATCH] Adding tree methods including example and unit test --- examples/get_files.go | 39 +++++++++++++++++++++++ methods_tree.go | 62 ++++++++++++++++++++++++++++++++++++ methods_tree_test.go | 74 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 examples/get_files.go create mode 100644 methods_tree_test.go diff --git a/examples/get_files.go b/examples/get_files.go new file mode 100644 index 0000000..688ff6d --- /dev/null +++ b/examples/get_files.go @@ -0,0 +1,39 @@ +package examples + +import ( + "fmt" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/txpull/sourcify-go" +) + +func Example_GetFiles() { + // Create a custom HTTP client with timeout + httpClient := &http.Client{ + Timeout: 30 * time.Second, + } + + // Create a new Sourcify client with custom options + client := sourcify.NewClient( + sourcify.WithHTTPClient(httpClient), + sourcify.WithBaseURL("https://sourcify.dev/server"), + sourcify.WithRetryOptions( + sourcify.WithMaxRetries(3), + sourcify.WithDelay(2*time.Second), + ), + ) + + // Get files for the Binance Smart Chain with the address of the R3T contract + files, err := sourcify.GetContractFiles(client, 56, common.HexToAddress("0x054B2223509D430269a31De4AE2f335890be5C8F"), sourcify.MethodMatchTypeAny) + if err != nil { + panic(err) + } + + fmt.Printf("Status: %+v\n", files.Status) + + for _, file := range files.Files { + fmt.Printf("Path: %+v\n", file) + } +} diff --git a/methods_tree.go b/methods_tree.go index 38cfb64..8c2a034 100644 --- a/methods_tree.go +++ b/methods_tree.go @@ -1,5 +1,13 @@ package sourcify +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/ethereum/go-ethereum/common" +) + var ( // MethodGetFileTreeFullOrPartialMatch represents the API endpoint for getting the file tree with full or partial match in the Sourcify service. // It includes the name, the HTTP method, the URI, and the parameters necessary for the request. @@ -47,3 +55,57 @@ var ( }, } ) + +// FileTree represents the file tree response from the Sourcify service. +type FileTree struct { + Status string `json:"status"` + Files []string `json:"files"` +} + +// GetContractFiles retrieves the repository URLs for every file in the source tree for the given chain ID and contract address. +// The matchType parameter determines whether to search for full matches, partial matches, or any matches. +// It returns the FileTree object containing the status and file URLs, or an error if any. +func GetContractFiles(client *Client, chainId int, contract common.Address, matchType MethodMatchType) (*FileTree, error) { + var method Method + + switch matchType { + case MethodMatchTypeFull: + method = MethodGetFileTreeFullMatch + case MethodMatchTypePartial: + method = MethodGetFileTreeFullOrPartialMatch + case MethodMatchTypeAny: + method = MethodGetFileTreeFullOrPartialMatch + default: + return nil, fmt.Errorf("invalid match type: %s", matchType) + } + + method.SetParams( + MethodParam{Key: ":chain", Value: chainId}, + MethodParam{Key: ":address", Value: contract.Hex()}, + ) + + if err := method.Verify(); err != nil { + return nil, err + } + + response, statusCode, err := client.CallMethod(method) + if err != nil { + return nil, err + } + + // Close the io.ReadCloser interface. + // This is important as CallMethod is NOT closing the response body! + // You'll have memory leaks if you don't do this! + defer response.Close() + + if statusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", statusCode) + } + + var toReturn *FileTree + if err := json.NewDecoder(response).Decode(&toReturn); err != nil { + return nil, err + } + + return toReturn, nil +} diff --git a/methods_tree_test.go b/methods_tree_test.go new file mode 100644 index 0000000..e9312bf --- /dev/null +++ b/methods_tree_test.go @@ -0,0 +1,74 @@ +package sourcify + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func TestGetContractFiles(t *testing.T) { + // Create a mock HTTP server + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/files/tree/any/1/0x0000000000000000000000001234567890aBcdEF" { + // Simulate a successful response with sample file tree + fileTree := &FileTree{ + Status: "success", + Files: []string{"/path/to/file1.sol", "/path/to/file2.sol"}, + } + + err := json.NewEncoder(w).Encode(fileTree) + if err != nil { + t.Errorf("failed to encode mock file tree: %v", err) + } + } else { + http.NotFound(w, r) + } + })) + defer mockServer.Close() + + // Create a client for the mock server + client := NewClient(WithBaseURL(mockServer.URL)) + + // Define test data + chainID := 1 + contractAddress := common.HexToAddress("0x1234567890abcdef") + matchType := MethodMatchTypeAny + + // Call the function to get contract files + fileTree, err := GetContractFiles(client, chainID, contractAddress, matchType) + + // Verify the results + assert.NoError(t, err, "GetContractFiles returned an error") + + expectedFileTree := &FileTree{ + Status: "success", + Files: []string{"/path/to/file1.sol", "/path/to/file2.sol"}, + } + + assert.Equal(t, expectedFileTree, fileTree, "GetContractFiles returned unexpected file tree") +} + +func TestGetContractFiles_Error(t *testing.T) { + // Create a mock HTTP server that always returns 404 Not Found + mockServer := httptest.NewServer(http.NotFoundHandler()) + defer mockServer.Close() + + // Create a client for the mock server + client := NewClient(WithBaseURL(mockServer.URL)) + + // Define test data + chainID := 1 + contractAddress := common.HexToAddress("0x1234567890abcdef") + matchType := MethodMatchTypeAny + + // Call the function to get contract files + fileTree, err := GetContractFiles(client, chainID, contractAddress, matchType) + + // Verify the results + assert.Error(t, err, "GetContractFiles should return an error") + assert.Nil(t, fileTree, "File tree should be nil") +}