Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ func (c *Client) callQueryMethod(method Method) (io.ReadCloser, int, error) {
// doRequestWithRetry sends the HTTP request with retry according to the configured retry options.
func (c *Client) doRequestWithRetry(req *http.Request) (io.ReadCloser, int, error) {
attempt := 0

for {
attempt++
resp, err := c.HTTPClient.Do(req)
Expand Down
8 changes: 3 additions & 5 deletions methods_check_by_address.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,9 @@ var (

// CheckContractAddress represents the contract address and associated chain IDs and statuses.
type CheckContractAddress struct {
Address common.Address `json:"address"` // The contract address.
ChainIds []struct {
ChainID string `json:"chainId"` // The chain ID.
Status string `json:"status"` // The status of the contract.
} `json:"chainIds"` // The associated chain IDs and statuses.
Address common.Address `json:"address"` // The contract address.
Status string `json:"status"` // The status of the contract.
ChainIDs []string `json:"chainIds"` // The chain ID.
}

// CheckContractByAddresses retrieves the available verified contract addresses for the given chain ID.
Expand Down
34 changes: 6 additions & 28 deletions methods_check_by_address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,9 @@ func TestCheckContractByAddresses(t *testing.T) {
// Simulate a successful response with sample contract addresses
contractAddresses := []*CheckContractAddress{
{
Address: common.HexToAddress("0x1234567890abcdef"),
ChainIds: []struct {
ChainID string `json:"chainId"`
Status string `json:"status"`
}{
{
ChainID: "1",
Status: "verified",
},
{
ChainID: "2",
Status: "not verified",
},
},
Address: common.HexToAddress("0x1234567890abcdef"),
Status: "verified",
ChainIDs: []string{"1", "2"},
},
}

Expand Down Expand Up @@ -59,20 +48,9 @@ func TestCheckContractByAddresses(t *testing.T) {

expectedContractAddresses := []*CheckContractAddress{
{
Address: common.HexToAddress("0x1234567890abcdef"),
ChainIds: []struct {
ChainID string `json:"chainId"`
Status string `json:"status"`
}{
{
ChainID: "1",
Status: "verified",
},
{
ChainID: "2",
Status: "not verified",
},
},
Address: common.HexToAddress("0x1234567890abcdef"),
Status: "verified",
ChainIDs: []string{"1", "2"},
},
}

Expand Down
23 changes: 21 additions & 2 deletions methods_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package sourcify
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

"github.com/ethereum/go-ethereum/common"
)
Expand Down Expand Up @@ -104,12 +106,29 @@ func GetContractSourceCode(client *Client, chainId int, contract common.Address,
// You'll have memory leaks if you don't do this!
defer response.Close()

body, readBodyErr := io.ReadAll(response)
if readBodyErr != nil {
return nil, fmt.Errorf("failure to read body: %s", readBodyErr)
}

if statusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", statusCode)
}

var toReturn *SourceCodes
if err := json.NewDecoder(response).Decode(&toReturn); err != nil {
toReturn := &SourceCodes{}

if err := json.Unmarshal(body, &toReturn); err != nil {
// Sometimes, response will not be a JSON object, but an array.
// In this case, we'll get an error, but we can still return the code.
// This is a workaround for the Sourcify API.
// Ugly, but it works.
if strings.Contains(err.Error(), "cannot unmarshal array into Go value") {
toReturn.Status = "unknown"
if err := json.Unmarshal(body, &toReturn.Code); err != nil {
return nil, err
}
return toReturn, nil
}
return nil, err
}

Expand Down
23 changes: 21 additions & 2 deletions methods_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package sourcify
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

"github.com/ethereum/go-ethereum/common"
)
Expand Down Expand Up @@ -98,12 +100,29 @@ func GetContractFiles(client *Client, chainId int, contract common.Address, matc
// You'll have memory leaks if you don't do this!
defer response.Close()

body, readBodyErr := io.ReadAll(response)
if readBodyErr != nil {
return nil, fmt.Errorf("failure to read body: %s", readBodyErr)
}

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 {
toReturn := &FileTree{}

if err := json.Unmarshal(body, &toReturn); err != nil {
// Sometimes, response will not be a JSON object, but an array.
// In this case, we'll get an error, but we can still return the code.
// This is a workaround for the Sourcify API.
// Ugly, but it works.
if strings.Contains(err.Error(), "cannot unmarshal array into Go value") {
toReturn.Status = "unknown"
if err := json.Unmarshal(body, &toReturn.Files); err != nil {
return nil, err
}
return toReturn, nil
}
return nil, err
}

Expand Down
130 changes: 130 additions & 0 deletions sourcify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package sourcify

import (
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

// SourcifySuite is a struct that embeds suite.Suite and contains fields necessary for
// executing tests. This includes a Client to interact with the sourcify service, a list of
// addresses to check, a list of chain IDs to use in testing, and specific instances
// of an address and a chain ID.
type SourcifySuite struct {
suite.Suite
client *Client
Addresses []string
ChainIDs []int
SpecificChainID int
SpecificAddress common.Address
}

// SetupTest initializes the test suite before each test. It creates a new Client with
// specified options and initializes the addresses and chain IDs to be used in the tests.
func (suite *SourcifySuite) SetupTest() {
suite.client = NewClient(
WithBaseURL("https://sourcify.dev/server"),
WithRetryOptions(
WithMaxRetries(3),
WithDelay(2*time.Second),
),
//WithRateLimit(20, 1*time.Second),
)
suite.Addresses = []string{"0x054B2223509D430269a31De4AE2f335890be5C8F"}
suite.ChainIDs = []int{1, 56, 137}
suite.SpecificChainID = 56 // Add the specific chain ID for the two methods
suite.SpecificAddress = common.HexToAddress("0x054B2223509D430269a31De4AE2f335890be5C8F") // Add the specific address for the two methods
}

// TestGetContractMetadata tests the GetContractMetadata function. It asserts that no error
// is returned and the metadata is not nil.
func (suite *SourcifySuite) TestGetContractMetadata() {
// Act
metadata, err := GetContractMetadata(suite.client, suite.SpecificChainID, suite.SpecificAddress, MethodMatchTypeFull)

// Assert
assert := assert.New(suite.T())
assert.NoError(err, "GetContractMetadata should not return an error")
assert.NotNil(metadata, "metadata should not be nil")
}

// TestGetContractSourceCode tests the GetContractSourceCode function. It asserts that no error
// is returned, the source code is not nil, and the length of the source code is 2.
func (suite *SourcifySuite) TestGetContractSourceCode() {
sourceCode, err := GetContractSourceCode(suite.client, suite.SpecificChainID, suite.SpecificAddress, MethodMatchTypeFull)

assert := assert.New(suite.T())
assert.NoError(err, "Expected GetContractSourceCode to run without error")
assert.NotNil(sourceCode, "source code should not be nil")
assert.Equal(len(sourceCode.Code), 2, "Expected source code to have 2 files")
}

// TestGetChains tests the GetChains function. It asserts that no error is returned, the chains
// are not nil, and the length of the chains is at least 98.
func (suite *SourcifySuite) TestGetChains() {
chains, err := GetChains(suite.client)

assert := assert.New(suite.T())
assert.NoError(err, "Expected GetChains to run without error")
assert.NotNil(chains, "source code should not be nil")
assert.GreaterOrEqual(len(chains), 98, "Expected source code to have at least 98 chains")
}

// TestGetAvailableContractAddresses tests the GetAvailableContractAddresses function. It asserts
// that no error is returned, the addresses are not nil, and the length of the full and partial addresses
// are at least 1000 each.
func (suite *SourcifySuite) TestGetAvailableContractAddresses() {
// Act
addresses, err := GetAvailableContractAddresses(suite.client, suite.SpecificChainID)

// Assert
assert := assert.New(suite.T())
assert.NoError(err, "GetAvailableContractAddresses should not return an error")
assert.NotNil(addresses, "addresses should not be nil")
assert.GreaterOrEqual(len(addresses.Full), 1000, "Expected source code to have at least 1000 addresses")
assert.GreaterOrEqual(len(addresses.Partial), 1000, "Expected source code to have at least 1000 addresses")
}

// TestCheckContractByAddresses tests the CheckContractByAddresses function. It asserts that no error
// is returned and the checks are not nil.
func (suite *SourcifySuite) TestCheckContractByAddresses() {
// Act
checks, err := CheckContractByAddresses(suite.client, suite.Addresses, suite.ChainIDs, MethodMatchTypeFull)

// Assert
assert := assert.New(suite.T())
assert.NoError(err, "CheckContractByAddresses should not return an error")
assert.NotNil(checks, "checks should not be nil")
}

// TestGetHealth tests the GetHealth function. It asserts that no error is returned and the status
// returned is true.
func (suite *SourcifySuite) TestGetHealth() {
// Act
status, err := GetHealth(suite.client)

// Assert
assert := assert.New(suite.T())
assert.NoError(err, "GetHealth should not return an error")
assert.True(status, "status should be true")
}

// TestGetContractFiles tests the GetContractFiles function. It asserts that no error is returned,
// the tree is not nil, and the length of the files is 2.
func (suite *SourcifySuite) TestGetContractFiles() {
tree, err := GetContractFiles(suite.client, suite.SpecificChainID, suite.SpecificAddress, MethodMatchTypeFull)

assert := assert.New(suite.T())
assert.NoError(err, "Expected GetContractSourceCode to run without error")
assert.NotNil(tree, "tree code should not be nil")
assert.Equal(len(tree.Files), 2, "Expected tree to have 2 files")
}

// TestGetContractFiles tests the GetContractFiles function. It asserts that no error is returned,
// the tree is not nil, and the length of the files is 2.
func TestSourcifySuite(t *testing.T) {
suite.Run(t, new(SourcifySuite))
}