Skip to content

Commit

Permalink
Add native ETH support to relayer (#1840)
Browse files Browse the repository at this point in the history
* WIP: initial relayer api

* WIP: use ByTxID naming

* Feat: add GetQuoteRequestStatusByTxHash

* WIP: initial impl for PUT /tx/retry

* Cleanup: comments

* Feat: add OriginTxHash to QuoteRequest, RequestForQuote models

* Feat: fetch request by origin tx hash instead of dest

* WIP: initial relayer api suite

* Feat: add /health endpoint

* Feat: add TestGetQuoteRequestByTxHash

* Fix: use /health in TestNewAPIServer

* Feat: add TestGetQuoteRequestByTxID

* Cleanup: reorder funcs

* WIP: add TestPutTxRetry

* Feat: PutTxRetry -> GetTxRetry, remove auth

* WIP: set chain IDs on test request

* Fix: set transaction submitter in suite

* Feat: check for submitted tx in TestGetTxRetry

* Cleanup: add SubmitRelay func to deduplicate code

* Cleanup: lints

* Cleanup: lints

* Fix: bump server start timeout

* Lint: disable depguard

* Fix: check nil tx in TestListenForEvents

* Cleanup: use itoa instead of sprintf

* [goreleaser] Bump timeout on server startup

* Fix: server_test

* [goreleaser] Cleanup: lint

* WIP: add relayer api config

* WIP: add new chain pkg, add APIConfig section for relayer api

* Cleanup: lint

* Cleanup: APIConfig -> RelayerAPIConfig

* Cleanup: APIServer -> QuoterAPIServer

* Fix: build

* Cleanup: lint

* Fix: build

* Cleanup: lint

* Cleanup: lint

* [goreleaser] Remove redundant RelayerAPIConfig section

* Feat: add MinGasToken to relayer config

* WIP: initial gas token bridging logic

* Feat: add TestGenerateQuotesForNativeToken

* Fix: GetMinGasToken doesn't error on unset val

* Feat: inventory manager registers native token metadata

* Feat: add TestETHtoETH integration test

* Fix: TestUSDCtoUSDC

* Cleanup: lint

* Cleanup: isNative -> isGasToken, remove unnecessary lines

* Add `hasSufficientGas` check in quote processing (#1939)

* Cleanup: Commitable -> Committable

* Feat: add gasBalances tracking

* Feat: fallback to fetching from gasBalances map

* Feat: add hasSufficientGas check

* WIP: export HasSufficientGas, add gasMiddleware

* WIP: move HasSufficientGas into inventory manager

* Feat: inventory manager now uses ClientFetcher instead of omni client

* Feat: add mock inventory manager

* Fix: tests

* Feat: add sufficient gas clause to TestShouldProcess

* Cleanup: tests

* Feat: add clause testing 0 quote amount

* [goreleaser] Fix: handle ETH in getDecimals()

* Fix: don't approve ETH

* [goreleaser]

* Fix: eth to eth integration test

* [goreleaser] fix panic

* update quoter/generated files

* fix err assertion

* CI: lower individual timeout to 5m, increase retries

* Revert "CI: lower individual timeout to 5m, increase retries"

This reverts commit f687e5d.

* Fix: Skip TestETHtoETH for now pending anvil fix

* Fix: check CI env var to run integration tests

---------

Co-authored-by: Trajan0x <trajan0x@users.noreply.github.com>
  • Loading branch information
dwasse and trajan0x committed Feb 1, 2024
1 parent 150beda commit 9187ace
Show file tree
Hide file tree
Showing 16 changed files with 673 additions and 116 deletions.
117 changes: 114 additions & 3 deletions services/rfq/e2e/rfq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client"
"github.com/synapsecns/sanguine/services/rfq/api/client"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
"github.com/synapsecns/sanguine/services/rfq/relayer/chain"
"github.com/synapsecns/sanguine/services/rfq/relayer/service"
"github.com/synapsecns/sanguine/services/rfq/testutil"
)
Expand Down Expand Up @@ -61,6 +62,11 @@ const (
func (i *IntegrationSuite) SetupTest() {
i.TestSuite.SetupTest()

// TODO: no need for this when anvil CI issues are fixed
if core.GetEnvBool("CI", false) {
return
}

i.manager = testutil.NewDeployManager(i.T())
// TODO: consider jaeger
i.metrics = metrics.NewNullHandler()
Expand All @@ -70,7 +76,6 @@ func (i *IntegrationSuite) SetupTest() {
// setup the api server
i.setupQuoterAPI()
i.setupRelayer()

}

// getOtherBackend gets the backend that is not the current one. This is a helper
Expand All @@ -84,8 +89,10 @@ func (i *IntegrationSuite) getOtherBackend(backend backends.SimulatedTestBackend
return nil
}

// TODO:
func (i *IntegrationSuite) TestUSDCtoUSDC() {
if core.GetEnvBool("CI", false) {
i.T().Skip("skipping until anvil issues are fixed in CI")
}
// Before we do anything, we're going to mint ourselves some USDC on the destination chain.
// 100k should do.
i.manager.MintToAddress(i.GetTestContext(), i.destBackend, testutil.USDCType, i.relayerWallet.Address(), big.NewInt(100000))
Expand Down Expand Up @@ -136,7 +143,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() {
SendChainGas: true,
DestToken: destUSDC.Address(),
OriginAmount: realWantAmount,
DestAmount: new(big.Int).Sub(realWantAmount, big.NewInt(1000)),
DestAmount: new(big.Int).Sub(realWantAmount, big.NewInt(10_000_000)),
Deadline: new(big.Int).SetInt64(time.Now().Add(time.Hour * 24).Unix()),
})
i.NoError(err)
Expand Down Expand Up @@ -191,3 +198,107 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() {
return false
})
}

func (i *IntegrationSuite) TestETHtoETH() {
if core.GetEnvBool("CI", false) {
i.T().Skip("skipping until anvil issues are fixed in CI")
}
// Send ETH to the relayer on destination
const initialBalance = 10
i.destBackend.FundAccount(i.GetTestContext(), i.relayerWallet.Address(), *big.NewInt(initialBalance))

// let's give the user some money as well
const userWantAmount = 1
i.originBackend.FundAccount(i.GetTestContext(), i.userWallet.Address(), *big.NewInt(userWantAmount))

// non decimal adjusted user want amount
realWantAmount := new(big.Int).Mul(big.NewInt(userWantAmount), big.NewInt(1e18))

// now our friendly user is going to check the quote and send us some ETH on the origin chain.
i.Eventually(func() bool {
// first he's gonna check the quotes.
userAPIClient, err := client.NewAuthenticatedClient(metrics.Get(), i.apiServer, localsigner.NewSigner(i.userWallet.PrivateKey()))
i.NoError(err)

allQuotes, err := userAPIClient.GetAllQuotes()
i.NoError(err)

// let's figure out the amount of ETH we need
for _, quote := range allQuotes {
if common.HexToAddress(quote.DestTokenAddr) == chain.EthAddress {
destAmountBigInt, _ := new(big.Int).SetString(quote.DestAmount, 10)
if destAmountBigInt.Cmp(realWantAmount) > 0 {
// we found our quote!
// now we can move on
return true
}
}
}
return false
})

// now we can send the money
_, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend)
auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr())
auth.TransactOpts.Value = realWantAmount
// we want 499 ETH for 500 requested within a day
tx, err := originFastBridge.Bridge(auth.TransactOpts, fastbridge.IFastBridgeBridgeParams{
DstChainId: uint32(i.destBackend.GetChainID()),
To: i.userWallet.Address(),
OriginToken: chain.EthAddress,
SendChainGas: true,
DestToken: chain.EthAddress,
OriginAmount: realWantAmount,
DestAmount: new(big.Int).Sub(realWantAmount, big.NewInt(1e17)),
Deadline: new(big.Int).SetInt64(time.Now().Add(time.Hour * 24).Unix()),
})
i.NoError(err)
i.originBackend.WaitForConfirmation(i.GetTestContext(), tx)

// TODO: this, but cleaner
anvilClient, err := anvil.Dial(i.GetTestContext(), i.originBackend.RPCAddress())
i.NoError(err)

go func() {
for {
select {
case <-i.GetTestContext().Done():
return
case <-time.After(time.Second * 4):
// increase time by 30 mintutes every second, should be enough to get us a fastish e2e test
// we don't need to worry about deadline since we're only doing this on origin
err = anvilClient.IncreaseTime(i.GetTestContext(), 60*30)
i.NoError(err)

// because can claim works on last block timestamp, we need to do something
err = anvilClient.Mine(i.GetTestContext(), 1)
i.NoError(err)
}
}
}()

// since relayer started w/ 0 ETH, once they're offering the inventory up on origin chain we know the workflow completed
i.Eventually(func() bool {
// first he's gonna check the quotes.
relayerAPIClient, err := client.NewAuthenticatedClient(metrics.Get(), i.apiServer, localsigner.NewSigner(i.relayerWallet.PrivateKey()))
i.NoError(err)

allQuotes, err := relayerAPIClient.GetAllQuotes()
i.NoError(err)

// let's figure out the amount of ETH we need
for _, quote := range allQuotes {
if common.HexToAddress(quote.DestTokenAddr) == chain.EthAddress && quote.DestChainID == originBackendChainID {
// we should now have some ETH on the origin chain since we claimed
// this should be offered up as inventory
destAmountBigInt, _ := new(big.Int).SetString(quote.DestAmount, 10)
if destAmountBigInt.Cmp(realWantAmount) > 0 {
// we found our quote!
// now we can move on
return true
}
}
}
return false
})
}
16 changes: 13 additions & 3 deletions services/rfq/e2e/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/synapsecns/sanguine/services/rfq/api/db/sql"
"github.com/synapsecns/sanguine/services/rfq/api/rest"
"github.com/synapsecns/sanguine/services/rfq/contracts/ierc20"
"github.com/synapsecns/sanguine/services/rfq/relayer/chain"
"github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"
"github.com/synapsecns/sanguine/services/rfq/relayer/service"
"github.com/synapsecns/sanguine/services/rfq/testutil"
Expand Down Expand Up @@ -200,6 +201,7 @@ func (i *IntegrationSuite) setupRelayer() {
Confirmations: 0,
Tokens: map[string]relconfig.TokenConfig{
"ETH": {
Address: chain.EthAddress.String(),
PriceUSD: 2000,
Decimals: 18,
},
Expand All @@ -210,12 +212,13 @@ func (i *IntegrationSuite) setupRelayer() {
Bridge: i.manager.Get(i.GetTestContext(), i.destBackend, testutil.FastBridgeType).Address().String(),
Confirmations: 0,
Tokens: map[string]relconfig.TokenConfig{
"MATIC": {
PriceUSD: 0.5,
"ETH": {
Address: chain.EthAddress.String(),
PriceUSD: 2000,
Decimals: 18,
},
},
NativeToken: "MATIC",
NativeToken: "ETH",
},
},
OmniRPCURL: i.omniServer,
Expand Down Expand Up @@ -276,7 +279,14 @@ func (i *IntegrationSuite) setupRelayer() {
cfg.QuotableTokens[quotableTokenID] = append(cfg.QuotableTokens[quotableTokenID], fmt.Sprintf("%d-%s", otherBackend.GetChainID(), otherToken))
}
}
}

// Add ETH as quotable token from origin to destination
cfg.QuotableTokens[fmt.Sprintf("%d-%s", originBackendChainID, chain.EthAddress)] = []string{
fmt.Sprintf("%d-%s", destBackendChainID, chain.EthAddress),
}
cfg.QuotableTokens[fmt.Sprintf("%d-%s", destBackendChainID, chain.EthAddress)] = []string{
fmt.Sprintf("%d-%s", originBackendChainID, chain.EthAddress),
}

// TODO: good chance we wanna leave actually starting this up to the indiividual test.
Expand Down
5 changes: 4 additions & 1 deletion services/rfq/relayer/chain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ func (c Chain) SubmitRelay(ctx context.Context, request reldb.QuoteRequest) (uin
gasAmount := big.NewInt(0)
var err error

if request.Transaction.SendChainGas {
// Check to see if ETH should be sent to destination
if IsGasToken(request.Transaction.DestToken) {
gasAmount = request.Transaction.DestAmount
} else if request.Transaction.SendChainGas {
gasAmount, err = c.Bridge.ChainGasAmount(&bind.CallOpts{Context: ctx})
if err != nil {
return 0, nil, fmt.Errorf("could not get chain gas amount: %w", err)
Expand Down
11 changes: 11 additions & 0 deletions services/rfq/relayer/chain/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package chain

import "github.com/ethereum/go-ethereum/common"

// EthAddress is the address of a chain's native gas token.
var EthAddress = common.HexToAddress("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE")

// IsGasToken returns true if the given token is the gas token.
func IsGasToken(token common.Address) bool {
return token == EthAddress
}
Loading

0 comments on commit 9187ace

Please sign in to comment.