synapsecns/sanguine

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

Summary

Maintainability
A
0 mins
Test Coverage
package relapi_test

import (
    "fmt"
    "math/big"
    "strconv"
    "sync"
    "testing"

    "github.com/ethereum/go-ethereum/params"
    "github.com/synapsecns/sanguine/services/rfq/testutil"
    "github.com/synapsecns/sanguine/services/rfq/util"

    "github.com/Flaque/filet"
    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/common"
    "github.com/phayes/freeport"
    "github.com/puzpuzpuz/xsync/v2"
    "github.com/stretchr/testify/suite"
    "github.com/synapsecns/sanguine/core/dbcommon"
    "github.com/synapsecns/sanguine/core/metrics"
    "github.com/synapsecns/sanguine/core/testsuite"
    "github.com/synapsecns/sanguine/ethergo/backends"
    "github.com/synapsecns/sanguine/ethergo/backends/geth"
    "github.com/synapsecns/sanguine/ethergo/signer/signer/localsigner"
    "github.com/synapsecns/sanguine/ethergo/signer/wallet"
    "github.com/synapsecns/sanguine/ethergo/submitter"
    submitterConfig "github.com/synapsecns/sanguine/ethergo/submitter/config"
    omniClient "github.com/synapsecns/sanguine/services/omnirpc/client"
    omnirpcHelper "github.com/synapsecns/sanguine/services/omnirpc/testhelper"
    "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
    "github.com/synapsecns/sanguine/services/rfq/relayer/relapi"
    "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"
    "github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
    "github.com/synapsecns/sanguine/services/rfq/relayer/reldb/connect"
    "golang.org/x/sync/errgroup"
)

// RelayerServer suite is the relayer API server test suite.
type RelayerServerSuite struct {
    *testsuite.TestSuite
    deployManager        *testutil.DeployManager
    omniRPCClient        omniClient.RPCClient
    omniRPCTestBackends  []backends.SimulatedTestBackend
    testBackendMux       sync.Mutex
    testBackends         map[uint64]backends.SimulatedTestBackend
    originChainID        uint32
    destChainID          uint32
    fastBridgeAddressMap *xsync.MapOf[uint64, common.Address]
    database             reldb.Service
    cfg                  relconfig.Config
    testWallet           wallet.Wallet
    handler              metrics.Handler
    RelayerAPIServer     *relapi.RelayerAPIServer
    port                 uint16
    wallet               wallet.Wallet
}

var testWithdrawalAddress = common.BigToAddress(big.NewInt(1))

// NewRelayerServerSuite creates a end-to-end test suite.
func NewRelayerServerSuite(tb testing.TB) *RelayerServerSuite {
    tb.Helper()
    return &RelayerServerSuite{
        TestSuite: testsuite.NewTestSuite(tb),
    }
}

func (c *RelayerServerSuite) SetupTest() {
    c.TestSuite.SetupTest()

    testOmnirpc := omnirpcHelper.NewOmnirpcServer(c.GetTestContext(), c.T(), c.omniRPCTestBackends...)
    omniRPCClient := omniClient.NewOmnirpcClient(testOmnirpc, c.handler, omniClient.WithCaptureReqRes())
    c.omniRPCClient = omniRPCClient

    arbFastBridgeAddress, ok := c.fastBridgeAddressMap.Load(42161)
    c.True(ok)
    ethFastBridgeAddress, ok := c.fastBridgeAddressMap.Load(1)
    c.True(ok)
    port, err := freeport.GetFreePort()
    c.port = uint16(port)
    c.Require().NoError(err)

    c.originChainID = 1
    c.destChainID = 42161

    testConfig := relconfig.Config{
        Chains: map[int]relconfig.ChainConfig{
            int(c.originChainID): {
                RFQAddress: ethFastBridgeAddress.Hex(),
            },
            int(c.destChainID): {
                RFQAddress: arbFastBridgeAddress.Hex(),
            },
        },
        RelayerAPIPort: strconv.Itoa(port),
        Database: relconfig.DatabaseConfig{
            Type: "sqlite",
            DSN:  filet.TmpFile(c.T(), "", "").Name(),
        },
        EnableAPIWithdrawals: true,
        WithdrawalWhitelist: []string{
            testWithdrawalAddress.String(),
        },
        QuotableTokens: map[string][]string{
            // gas tokens.
            fmt.Sprintf("%d-%s", c.originChainID, util.EthAddress): {
                // not used for this test
            },
            fmt.Sprintf("%d-%s", c.destChainID, util.EthAddress): {
                // not used for this test
            },
            c.getMockTokenID(c.testBackends[uint64(c.originChainID)]): {
                // not used for this test
            },
            c.getMockTokenID(c.testBackends[uint64(c.destChainID)]): {
                // not used for this test
            },
        },
    }
    c.cfg = testConfig

    c.wallet, err = wallet.FromRandom()
    c.Require().NoError(err)
    signer := localsigner.NewSigner(c.wallet.PrivateKey())
    submitterCfg := &submitterConfig.Config{}
    ts := submitter.NewTransactionSubmitter(c.handler, signer, omniRPCClient, c.database.SubmitterDB(), submitterCfg)

    var wg sync.WaitGroup
    wg.Add(len(c.testBackends) * 2)
    go func() {
        // small potential for a race condition if submitter hasn't started by the time our test starts
        _ = ts.Start(c.GetTestContext())
    }()

    for _, backend := range c.testBackends {
        go func() {
            defer wg.Done()
            backend.FundAccount(c.GetTestContext(), c.wallet.Address(), *big.NewInt(params.Ether))
        }()

        go func() {
            defer wg.Done()
            mockMetadata, mockToken := c.deployManager.GetMockERC20(c.GetTestContext(), backend)
            auth := backend.GetTxContext(c.GetTestContext(), mockMetadata.OwnerPtr()).TransactOpts

            tx, err := mockToken.Mint(auth, c.wallet.Address(), big.NewInt(1000000000000000000))
            c.Require().NoError(err)
            backend.WaitForConfirmation(c.GetTestContext(), tx)
        }()
    }

    server, err := relapi.NewRelayerAPI(c.GetTestContext(), c.cfg, c.handler, c.omniRPCClient, c.database, ts)
    c.Require().NoError(err)
    c.RelayerAPIServer = server
    wg.Wait()
}

func (c *RelayerServerSuite) getMockTokenID(backend backends.SimulatedTestBackend) string {
    erc20Metadata, _ := c.deployManager.GetMockERC20(c.GetTestContext(), backend)
    return fmt.Sprintf("%d-%s", backend.GetChainID(), erc20Metadata.Address().Hex())
}

func (c *RelayerServerSuite) SetupSuite() {
    c.TestSuite.SetupSuite()

    c.deployManager = testutil.NewDeployManager(c.T())

    // let's create 2 mock chains
    chainIDs := []uint64{1, 42161}

    c.testBackends = make(map[uint64]backends.SimulatedTestBackend)

    testWallet, err := wallet.FromRandom()
    c.Require().NoError(err)
    c.testWallet = testWallet

    g, _ := errgroup.WithContext(c.GetSuiteContext())
    for _, chainID := range chainIDs {
        // Setup Anvil backend for the suite to have RPC support
        //nolint: copyloopvar
        chainID := chainID // capture loop variable
        g.Go(func() error {
            backend := geth.NewEmbeddedBackendForChainID(c.GetSuiteContext(), c.T(), new(big.Int).SetUint64(chainID))

            backend.FundAccount(c.GetSuiteContext(), c.testWallet.Address(), *big.NewInt(params.Ether))

            // add the backend to the list of backends
            c.testBackendMux.Lock()
            defer c.testBackendMux.Unlock()
            c.testBackends[chainID] = backend
            c.omniRPCTestBackends = append(c.omniRPCTestBackends, backend)

            return nil
        })
    }

    // wait for all backends to be ready
    if err := g.Wait(); err != nil {
        c.T().Fatal(err)
    }

    c.fastBridgeAddressMap = xsync.NewIntegerMapOf[uint64, common.Address]()

    g, _ = errgroup.WithContext(c.GetSuiteContext())
    for _, backend := range c.testBackends {
        backend := backend
        // TODO: functionalize me
        g.Go(func() error {
            chainID, err := backend.ChainID(c.GetSuiteContext())
            if err != nil {
                return fmt.Errorf("could not get chain id: %w", err)
            }

            fastBridgeMetadata, _ := c.deployManager.GetFastBridge(c.GetSuiteContext(), backend)

            // Save the contracts to the map
            c.fastBridgeAddressMap.Store(chainID.Uint64(), fastBridgeMetadata.Address())

            fastBridgeInstance, err := fastbridge.NewFastBridge(fastBridgeMetadata.Address(), backend)
            c.Require().NoError(err)

            relayerRole, err := fastBridgeInstance.RELAYERROLE(&bind.CallOpts{Context: c.GetSuiteContext()})
            c.NoError(err)

            auth := backend.GetTxContext(c.GetSuiteContext(), fastBridgeMetadata.OwnerPtr()).TransactOpts

            tx, err := fastBridgeInstance.GrantRole(auth, relayerRole, c.testWallet.Address())
            c.Require().NoError(err)
            backend.WaitForConfirmation(c.GetSuiteContext(), tx)

            return nil
        })

        g.Go(func() error {
            mockERC20Metadata, mockERC20 := c.deployManager.GetMockERC20(c.GetSuiteContext(), backend)
            if err != nil {
                return fmt.Errorf("could not get mock ERC20: %w", err)
            }

            auth := backend.GetTxContext(c.GetSuiteContext(), mockERC20Metadata.OwnerPtr()).TransactOpts

            mintTx, err := mockERC20.Mint(auth, c.testWallet.Address(), big.NewInt(1000000000000000000))
            c.Require().NoError(err)

            backend.WaitForConfirmation(c.GetSuiteContext(), mintTx)
            return nil
        })
    }

    dbType, err := dbcommon.DBTypeFromString("sqlite")
    c.Require().NoError(err)
    metricsHandler := metrics.NewNullHandler()
    c.handler = metricsHandler
    // TODO use temp file / in memory sqlite3 to not create in directory files
    testDB, _ := connect.Connect(c.GetSuiteContext(), dbType, filet.TmpDir(c.T(), ""), metricsHandler)
    c.database = testDB
    // setup config

    // wait for all backends to be ready
    if err := g.Wait(); err != nil {
        c.T().Fatal(err)
    }
}

// TestConfigSuite runs the integration test suite.
func TestRelayerServerSuite(t *testing.T) {
    suite.Run(t, NewRelayerServerSuite(t))
}

type RelayerClientSuite struct {
    *testsuite.TestSuite
    underlying *RelayerServerSuite
    Client     relapi.RelayerClient
}

// NewRelayerClientSuite creates a new relayer client suite.
func NewRelayerClientSuite(tb testing.TB) *RelayerClientSuite {
    tb.Helper()
    underlying := NewRelayerServerSuite(tb)

    return &RelayerClientSuite{
        TestSuite:  underlying.TestSuite,
        underlying: underlying,
    }
}

func (c *RelayerClientSuite) SetupSuite() {
    c.underlying.SetupSuite()
}

func (c *RelayerClientSuite) SetupTest() {
    c.underlying.SetupTest()
    c.underlying.startQuoterAPIServer()
    c.Client = relapi.NewRelayerClient(c.underlying.handler, fmt.Sprintf("http://localhost:%d", c.underlying.port))
}

// TestConfigSuite runs the integration test suite.
func TestRelayerClientSuite(t *testing.T) {
    suite.Run(t, NewRelayerClientSuite(t))
}