diff --git a/services/rfq/api/model/request.go b/services/rfq/api/model/request.go index bbc5c1aa88..065da5512f 100644 --- a/services/rfq/api/model/request.go +++ b/services/rfq/api/model/request.go @@ -55,7 +55,6 @@ type QuoteData struct { DestChainID int `json:"dest_chain_id"` OriginTokenAddr string `json:"origin_token_addr"` DestTokenAddr string `json:"dest_token_addr"` - OriginAmount string `json:"origin_amount"` ExpirationWindow int64 `json:"expiration_window"` ZapData string `json:"zap_data"` ZapNative string `json:"zap_native"` diff --git a/services/rfq/relayer/quoter/export_test.go b/services/rfq/relayer/quoter/export_test.go index 9f12c9477d..8111df11b8 100644 --- a/services/rfq/relayer/quoter/export_test.go +++ b/services/rfq/relayer/quoter/export_test.go @@ -22,6 +22,10 @@ func (m *Manager) GetDestAmount(ctx context.Context, quoteAmount *big.Int, token return m.getDestAmount(ctx, quoteAmount, tokenName, input) } +func (m *Manager) GenerateActiveRFQ(ctx context.Context, msg *model.ActiveRFQMessage) (resp *model.ActiveRFQMessage, err error) { + return m.generateActiveRFQ(ctx, msg) +} + func (m *Manager) SetConfig(cfg relconfig.Config) { m.config = cfg } diff --git a/services/rfq/relayer/quoter/quoter.go b/services/rfq/relayer/quoter/quoter.go index 9854fd89e5..7ab49af2d6 100644 --- a/services/rfq/relayer/quoter/quoter.go +++ b/services/rfq/relayer/quoter/quoter.go @@ -21,6 +21,7 @@ import ( "github.com/ipfs/go-log" "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridgev2" "github.com/synapsecns/sanguine/services/rfq/relayer/pricer" "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" @@ -337,7 +338,7 @@ func (m *Manager) SubscribeActiveRFQ(ctx context.Context) (err error) { // getActiveRFQ handles an active RFQ message. // -//nolint:nilnil +//nolint:nilnil,cyclop func (m *Manager) generateActiveRFQ(ctx context.Context, msg *model.ActiveRFQMessage) (resp *model.ActiveRFQMessage, err error) { ctx, span := m.metricsHandler.Tracer().Start(ctx, "generateActiveRFQ", trace.WithAttributes( attribute.String("op", msg.Op), @@ -379,11 +380,38 @@ func (m *Manager) generateActiveRFQ(ctx context.Context, msg *model.ActiveRFQMes DestBalance: inv[rfqRequest.Data.DestChainID][common.HexToAddress(rfqRequest.Data.DestTokenAddr)], OriginAmountExact: originAmountExact, } + if rfqRequest.Data.ZapNative != "" || rfqRequest.Data.ZapData != "" { + zapNative, ok := new(big.Int).SetString(rfqRequest.Data.ZapNative, 10) + if !ok { + return nil, fmt.Errorf("invalid zap native amount: %s", rfqRequest.Data.ZapNative) + } + quoteInput.QuoteRequest = &reldb.QuoteRequest{ + Transaction: fastbridgev2.IFastBridgeV2BridgeTransactionV2{ + ZapNative: zapNative, + ZapData: []byte(rfqRequest.Data.ZapData), + }, + } + } rawQuote, err := m.generateQuote(ctx, quoteInput) if err != nil { return nil, fmt.Errorf("error generating quote: %w", err) } + + // adjust dest amount by fixed fee + destAmountBigInt, ok := new(big.Int).SetString(rawQuote.DestAmount, 10) + if !ok { + return nil, fmt.Errorf("invalid dest amount: %s", rawQuote.DestAmount) + } + fixedFeeBigInt, ok := new(big.Int).SetString(rawQuote.FixedFee, 10) + if !ok { + return nil, fmt.Errorf("invalid fixed fee: %s", rawQuote.FixedFee) + } + destAmountAdj := new(big.Int).Sub(destAmountBigInt, fixedFeeBigInt) + if destAmountAdj.Sign() < 0 { + destAmountAdj = big.NewInt(0) + } + rawQuote.DestAmount = destAmountAdj.String() span.SetAttributes(attribute.String("dest_amount", rawQuote.DestAmount)) rfqResp := model.WsRFQResponse{ diff --git a/services/rfq/relayer/quoter/quoter_test.go b/services/rfq/relayer/quoter/quoter_test.go index e80966e756..c4826448eb 100644 --- a/services/rfq/relayer/quoter/quoter_test.go +++ b/services/rfq/relayer/quoter/quoter_test.go @@ -1,6 +1,7 @@ package quoter_test import ( + "encoding/json" "fmt" "math/big" @@ -8,8 +9,10 @@ import ( "github.com/stretchr/testify/mock" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/testsuite" + clientMocks "github.com/synapsecns/sanguine/ethergo/client/mocks" fetcherMocks "github.com/synapsecns/sanguine/ethergo/submitter/mocks" "github.com/synapsecns/sanguine/services/rfq/api/model" + "github.com/synapsecns/sanguine/services/rfq/api/rest" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridgev2" inventoryMocks "github.com/synapsecns/sanguine/services/rfq/relayer/inventory/mocks" "github.com/synapsecns/sanguine/services/rfq/relayer/pricer" @@ -444,6 +447,92 @@ func (s *QuoterSuite) TestGetOriginAmountActiveQuotes() { s.Equal(expectedAmount, quoteAmount) } +func (s *QuoterSuite) TestGenerateActiveRFQ() { + origin := int(s.origin) + dest := int(s.destination) + originAddr := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48") + destAddr := common.HexToAddress("0x0b2c639c533813f4aa9d7837caf62653d097ff85") + balance := big.NewInt(1_000_000_000_000) + balances := map[int]map[common.Address]*big.Int{ + origin: { + originAddr: balance, + }, + dest: { + destAddr: balance, + }, + } + + currentHeader := big.NewInt(100_000_000_000) // 100 gwei + clientFetcher := new(fetcherMocks.ClientFetcher) + clientMock := new(clientMocks.EVM) + clientMock.On(testsuite.GetFunctionName(clientMock.SuggestGasPrice), mock.Anything).Return(currentHeader, nil) + clientFetcher.On(testsuite.GetFunctionName(clientMock.EstimateGas), mock.Anything, mock.Anything).Return(100_000, nil) + clientFetcher.On(testsuite.GetFunctionName(clientFetcher.GetClient), mock.Anything, mock.Anything).Return(clientMock, nil) + priceFetcher := new(priceMocks.CoingeckoPriceFetcher) + priceFetcher.On(testsuite.GetFunctionName(priceFetcher.GetPrice), mock.Anything, mock.Anything).Return(0., fmt.Errorf("not using mocked price")) + feePricer := pricer.NewFeePricer(s.config, clientFetcher, priceFetcher, metrics.NewNullHandler(), common.HexToAddress("0x123")) + inventoryManager := new(inventoryMocks.Manager) + inventoryManager.On(testsuite.GetFunctionName(inventoryManager.HasSufficientGas), mock.Anything, mock.Anything, mock.Anything).Return(true, nil) + inventoryManager.On(testsuite.GetFunctionName(inventoryManager.GetCommittableBalances), mock.Anything, mock.Anything, mock.Anything).Return(balances, nil) + mgr, err := quoter.NewQuoterManager(s.config, metrics.NewNullHandler(), inventoryManager, nil, feePricer, nil) + s.NoError(err) + + var ok bool + s.manager, ok = mgr.(*quoter.Manager) + s.True(ok) + + req := model.PutRFQRequest{ + UserAddress: "0x123", + IntegratorID: "123", + QuoteTypes: []string{"active"}, + Data: model.QuoteData{ + OriginChainID: origin, + DestChainID: dest, + OriginAmountExact: "100000", + OriginTokenAddr: originAddr.String(), + DestTokenAddr: destAddr.String(), + }, + } + reqBytes, err := json.Marshal(req) + s.NoError(err) + msg := model.ActiveRFQMessage{ + Op: rest.RequestQuoteOp, + Content: json.RawMessage(reqBytes), + } + + respMsg, err := s.manager.GenerateActiveRFQ(s.GetTestContext(), &msg) + s.NoError(err) + var resp model.WsRFQResponse + err = json.Unmarshal(respMsg.Content, &resp) + s.NoError(err) + s.Equal("0", resp.DestAmount) + + req = model.PutRFQRequest{ + UserAddress: "0x123", + IntegratorID: "123", + QuoteTypes: []string{"active"}, + Data: model.QuoteData{ + OriginChainID: origin, + DestChainID: dest, + OriginAmountExact: "500000000000", + OriginTokenAddr: originAddr.String(), + DestTokenAddr: destAddr.String(), + }, + } + reqBytes, err = json.Marshal(req) + s.NoError(err) + msg = model.ActiveRFQMessage{ + Op: rest.RequestQuoteOp, + Content: json.RawMessage(reqBytes), + } + + respMsg, err = s.manager.GenerateActiveRFQ(s.GetTestContext(), &msg) + s.NoError(err) + err = json.Unmarshal(respMsg.Content, &resp) + s.NoError(err) + s.Equal("499899950000", resp.DestAmount) +} + func (s *QuoterSuite) TestGetOriginAmount() { origin := int(s.origin) dest := int(s.destination)