synapsecns/sanguine

View on GitHub
services/rfq/relayer/relapi/server_test.go

Summary

Maintainability
A
3 hrs
Test Coverage
package relapi_test

import (
    "context"
    "encoding/json"
    "fmt"
    "math/big"
    "net/http"
    "testing"
    "time"

    "github.com/stretchr/testify/assert"
    "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/synapsecns/sanguine/core/retry"
    submitterdb "github.com/synapsecns/sanguine/ethergo/submitter/db"
    "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
    "github.com/synapsecns/sanguine/services/rfq/relayer/relapi"
    "github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
)

func (c *RelayerServerSuite) TestNewQuoterAPIServer() {
    // Start the API server in a separate goroutine and wait for it to initialize.
    c.startQuoterAPIServer()
    client := &http.Client{}
    req, err := http.NewRequestWithContext(c.GetTestContext(), http.MethodGet, fmt.Sprintf("http://localhost:%d/health", c.port), nil)
    c.Require().NoError(err)
    resp, err := client.Do(req)
    c.Require().NoError(err)
    defer func() {
        if resp != nil && resp.Body != nil {
            err = resp.Body.Close()
            c.Require().NoError(err)
        }
    }()
    c.Equal(http.StatusOK, resp.StatusCode)
    c.GetTestContext().Done()
}

func (c *RelayerServerSuite) TestGetQuoteRequestByTxHash() {
    c.startQuoterAPIServer()

    // Insert quote request to db
    quoteRequest := c.getTestQuoteRequest(reldb.Seen)
    err := c.database.StoreQuoteRequest(c.GetTestContext(), quoteRequest)
    c.Require().NoError(err)

    // Fetch the quote request by tx hash
    client := &http.Client{}
    req, err := http.NewRequestWithContext(c.GetTestContext(), http.MethodGet, fmt.Sprintf("http://localhost:%d/request/by_tx_hash?hash=%s", c.port, quoteRequest.OriginTxHash), nil)
    c.Require().NoError(err)
    resp, err := client.Do(req)
    c.Require().NoError(err)
    defer func() {
        if resp != nil && resp.Body != nil {
            err = resp.Body.Close()
            c.Require().NoError(err)
        }
    }()
    c.Equal(http.StatusOK, resp.StatusCode)

    // Compare to expected result
    var result relapi.GetQuoteRequestResponse
    err = json.NewDecoder(resp.Body).Decode(&result)
    c.Require().NoError(err)
    expectedResult := relapi.GetQuoteRequestResponse{
        Status:          quoteRequest.Status.String(),
        TxID:            hexutil.Encode(quoteRequest.TransactionID[:]),
        QuoteRequestRaw: result.QuoteRequestRaw,
        OriginTxHash:    quoteRequest.OriginTxHash.String(),
        DestTxHash:      quoteRequest.DestTxHash.String(),
        OriginChainID:   quoteRequest.Transaction.OriginChainId,
        DestChainID:     quoteRequest.Transaction.DestChainId,
        OriginToken:     quoteRequest.Transaction.OriginToken.String(),
        DestToken:       quoteRequest.Transaction.DestToken.String(),
        Sender:          quoteRequest.Sender.String(),
    }
    c.Equal(expectedResult, result)
    c.GetTestContext().Done()
}

func (c *RelayerServerSuite) TestGetQuoteRequestByTxID() {
    c.startQuoterAPIServer()

    // Insert quote request to db
    quoteRequest := c.getTestQuoteRequest(reldb.Seen)
    err := c.database.StoreQuoteRequest(c.GetTestContext(), quoteRequest)
    c.Require().NoError(err)

    // Fetch the quote request by tx hash
    client := &http.Client{}
    txIDStr := hexutil.Encode(quoteRequest.TransactionID[:])
    req, err := http.NewRequestWithContext(c.GetTestContext(), http.MethodGet, fmt.Sprintf("http://localhost:%d/request/by_tx_id?id=%s", c.port, txIDStr), nil)
    c.Require().NoError(err)
    resp, err := client.Do(req)
    c.Require().NoError(err)
    defer func() {
        if resp != nil && resp.Body != nil {
            err = resp.Body.Close()
            c.Require().NoError(err)
        }
    }()
    c.Equal(http.StatusOK, resp.StatusCode)

    // Compare to expected result
    var result relapi.GetQuoteRequestResponse
    err = json.NewDecoder(resp.Body).Decode(&result)
    c.Require().NoError(err)
    expectedResult := relapi.GetQuoteRequestResponse{
        Status:          quoteRequest.Status.String(),
        TxID:            hexutil.Encode(quoteRequest.TransactionID[:]),
        QuoteRequestRaw: result.QuoteRequestRaw,
        OriginTxHash:    quoteRequest.OriginTxHash.String(),
        DestTxHash:      quoteRequest.DestTxHash.String(),
        OriginChainID:   quoteRequest.Transaction.OriginChainId,
        DestChainID:     quoteRequest.Transaction.DestChainId,
        OriginToken:     quoteRequest.Transaction.OriginToken.String(),
        DestToken:       quoteRequest.Transaction.DestToken.String(),
        Sender:          quoteRequest.Sender.String(),
    }
    c.Equal(expectedResult, result)
    c.GetTestContext().Done()
}

func (c *RelayerServerSuite) TestGetTxRetry() {
    c.startQuoterAPIServer()

    // Insert quote request to db
    quoteRequest := c.getTestQuoteRequest(reldb.Seen)
    err := c.database.StoreQuoteRequest(c.GetTestContext(), quoteRequest)
    c.Require().NoError(err)

    // Send a retry request
    client := &http.Client{}
    req, err := http.NewRequestWithContext(c.GetTestContext(), http.MethodGet, fmt.Sprintf("http://localhost:%d/retry?hash=%s", c.port, quoteRequest.OriginTxHash), nil)
    c.Require().NoError(err)
    resp, err := client.Do(req)
    c.Require().NoError(err)
    defer func() {
        if resp != nil && resp.Body != nil {
            err = resp.Body.Close()
            c.Require().NoError(err)
        }
    }()
    c.Equal(http.StatusOK, resp.StatusCode)

    // Compare to expected result
    var result relapi.GetTxRetryResponse
    err = json.NewDecoder(resp.Body).Decode(&result)
    c.Require().NoError(err)
    expectedResult := relapi.GetTxRetryResponse{
        TxID:      hexutil.Encode(quoteRequest.TransactionID[:]),
        ChainID:   quoteRequest.Transaction.DestChainId,
        Nonce:     uint64(quoteRequest.Transaction.Nonce.Int64()),
        GasAmount: "0",
    }
    c.Equal(expectedResult, result)
    c.GetTestContext().Done()

    // Verify that a transaction was submitted
    status, err := c.database.SubmitterDB().GetNonceStatus(c.GetTestContext(), c.wallet.Address(), big.NewInt(int64(quoteRequest.Transaction.DestChainId)), result.Nonce)
    c.Require().NoError(err)
    c.Equal(status, submitterdb.Stored)
}

// startQuoterAPIServer starts the API server and waits for it to initialize.
func (c *RelayerServerSuite) startQuoterAPIServer() {
    go func() {
        err := c.RelayerAPIServer.Run(c.GetTestContext())
        c.Require().NoError(err)
    }()

    // Wait for the server to start
    err := retry.WithBackoff(c.GetTestContext(), func(ctx context.Context) error {
        client := &http.Client{}
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://localhost:%d/health", c.port), nil)
        c.Require().NoError(err)
        resp, err := client.Do(req)
        defer func() {
            if resp != nil && resp.Body != nil {
                closeErr := resp.Body.Close()
                c.NoError(closeErr)
            }
        }()
        if err != nil {
            return fmt.Errorf("server not ready: %w", err)
        }
        return nil
    }, retry.WithMaxTotalTime(60*time.Second))
    c.Require().NoError(err)
}

func (c *RelayerServerSuite) getTestQuoteRequest(status reldb.QuoteRequestStatus) reldb.QuoteRequest {
    txIDRaw := hexutil.Encode(crypto.Keccak256([]byte("test")))
    var txID [32]byte
    copy(txID[:], txIDRaw)
    return reldb.QuoteRequest{
        OriginTokenDecimals: 6,
        DestTokenDecimals:   6,
        TransactionID:       txID,
        Status:              status,
        Transaction: fastbridge.IFastBridgeBridgeTransaction{
            OriginChainId: c.originChainID,
            DestChainId:   c.destChainID,
            OriginAmount:  big.NewInt(100),
            DestAmount:    big.NewInt(100),
            Deadline:      big.NewInt(time.Now().Unix()),
            Nonce:         big.NewInt(0),
        },
        OriginTxHash: common.HexToHash("0x0000000"),
        DestTxHash:   common.HexToHash("0x0000001"),
    }
}

func TestTokenIDExists(t *testing.T) {
    cfg := relconfig.Config{
        QuotableTokens: map[string][]string{
            fmt.Sprintf("1%s0x1234567890abcdef1234567890abcdef12345678", relconfig.TokenIDDelimiter): {},
            fmt.Sprintf("1%s0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", relconfig.TokenIDDelimiter): {},
        },
    }

    tests := []struct {
        name         string
        tokenAddress common.Address
        chainID      int
        expected     bool
    }{
        {
            name:         "Valid token address",
            tokenAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"),
            chainID:      1,
            expected:     true,
        },
        {
            name:         "Invalid token address",
            tokenAddress: common.HexToAddress("0x0000000000000000000000000000000000000000"),
            chainID:      1,
            expected:     false,
        },
        {
            name:         "Valid token address, different chain ID",
            tokenAddress: common.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"),
            chainID:      2,
            expected:     false,
        },
        {
            name:         "Edge case: empty token address",
            tokenAddress: common.Address{},
            chainID:      1,
            expected:     false,
        },
    }

    for i := range tests {
        tt := tests[i]
        t.Run(tt.name, func(t *testing.T) {
            result := relapi.TokenIDExists(cfg, tt.tokenAddress, tt.chainID)
            assert.Equal(t, tt.expected, result)
        })
    }
}

func TestToAddressIsWhitelisted(t *testing.T) {
    cfg := relconfig.Config{
        WithdrawalWhitelist: []string{
            "0x1111111111111111111111111111111111111111",
            "0x2222222222222222222222222222222222222222",
        },
    }

    tests := []struct {
        name      string
        toAddress common.Address
        expected  bool
    }{
        {
            name:      "Address is whitelisted",
            toAddress: common.HexToAddress("0x1111111111111111111111111111111111111111"),
            expected:  true,
        },
        {
            name:      "Address is not whitelisted",
            toAddress: common.HexToAddress("0x3333333333333333333333333333333333333333"),
            expected:  false,
        },
    }

    for i := range tests {
        tt := tests[i]
        t.Run(tt.name, func(t *testing.T) {
            result := relapi.ToAddressIsWhitelisted(cfg, tt.toAddress)
            assert.Equal(t, tt.expected, result)
        })
    }
}