package quoter_test import ( "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/mock" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/testsuite" fetcherMocks "github.com/synapsecns/sanguine/ethergo/submitter/mocks" "github.com/synapsecns/sanguine/services/rfq/api/model" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" inventoryMocks "github.com/synapsecns/sanguine/services/rfq/relayer/inventory/mocks" "github.com/synapsecns/sanguine/services/rfq/relayer/pricer" priceMocks "github.com/synapsecns/sanguine/services/rfq/relayer/pricer/mocks" "github.com/synapsecns/sanguine/services/rfq/relayer/quoter" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" "github.com/synapsecns/sanguine/services/rfq/util" ) func (s *QuoterSuite) TestGenerateQuotes() { // Generate quotes for USDC on the destination chain. balance := big.NewInt(1000_000_000) // 1000 USDC inv := map[int]map[common.Address]*big.Int{} quotes, err := s.manager.GenerateQuotes(s.GetTestContext(), int(s.destination), common.HexToAddress("0x0b2c639c533813f4aa9d7837caf62653d097ff85"), balance, inv) s.Require().NoError(err) // Verify the quotes are generated as expected. expectedQuotes := []model.PutRelayerQuoteRequest{ { OriginChainID: int(s.origin), OriginTokenAddr: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", DestChainID: int(s.destination), DestTokenAddr: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", DestAmount: balance.String(), MaxOriginAmount: balance.String(), FixedFee: "100050000", OriginFastBridgeAddress: common.HexToAddress("0x123").Hex(), DestFastBridgeAddress: common.HexToAddress("0x456").Hex(), }, } s.Equal(expectedQuotes, quotes) } func (s *QuoterSuite) TestGenerateQuotesForNativeToken() { // Generate quotes for ETH on the destination chain. balance, _ := new(big.Int).SetString("1000000000000000000", 10) // 1 ETH inv := map[int]map[common.Address]*big.Int{} quotes, err := s.manager.GenerateQuotes(s.GetTestContext(), int(s.destinationEth), util.EthAddress, balance, inv) s.Require().NoError(err) minGasToken, err := s.config.GetMinGasToken(int(s.destination)) s.NoError(err) expectedQuoteAmount := new(big.Int).Sub(balance, minGasToken) // Verify the quotes are generated as expected. expectedQuotes := []model.PutRelayerQuoteRequest{ { OriginChainID: int(s.origin), OriginTokenAddr: util.EthAddress.String(), DestChainID: int(s.destinationEth), DestTokenAddr: util.EthAddress.String(), DestAmount: expectedQuoteAmount.String(), MaxOriginAmount: expectedQuoteAmount.String(), FixedFee: "150000000000000000", // (500k gas + 1m gas) * 100 gwei OriginFastBridgeAddress: common.HexToAddress("0x123").Hex(), DestFastBridgeAddress: common.HexToAddress("0x789").Hex(), }, } s.Equal(expectedQuotes, quotes) // Set MinGasToken and make sure it is accounted for in the DestAmount. s.config.BaseChainConfig.MinGasToken = "100000000000000000" // 0.1 ETH s.manager.SetConfig(s.config) quotes, err = s.manager.GenerateQuotes(s.GetTestContext(), int(s.destinationEth), util.EthAddress, balance, inv) s.Require().NoError(err) minGasToken, err = s.config.GetMinGasToken(int(s.destination)) s.NoError(err) expectedQuoteAmount = new(big.Int).Sub(balance, minGasToken) // Verify the quotes are generated as expected. expectedQuotes = []model.PutRelayerQuoteRequest{ { OriginChainID: int(s.origin), OriginTokenAddr: util.EthAddress.String(), DestChainID: int(s.destinationEth), DestTokenAddr: util.EthAddress.String(), DestAmount: expectedQuoteAmount.String(), MaxOriginAmount: expectedQuoteAmount.String(), FixedFee: "150000000000000000", // (500k gas + 1m gas) * 100 gwei OriginFastBridgeAddress: common.HexToAddress("0x123").Hex(), DestFastBridgeAddress: common.HexToAddress("0x789").Hex(), }, } s.Equal(expectedQuotes, quotes) // Set MinGasToken to balance and make sure no quotes are generated. s.config.BaseChainConfig.MinGasToken = "1000000000000000001" // 0.1 ETH s.manager.SetConfig(s.config) quotes, err = s.manager.GenerateQuotes(s.GetTestContext(), int(s.destinationEth), util.EthAddress, balance, inv) s.NoError(err) s.Equal(quotes[0].DestAmount, "0") s.Equal(quotes[0].MaxOriginAmount, "0") } func (s *QuoterSuite) TestShouldProcess() { // Should process a valid quote. balance := big.NewInt(1000_000_000) // 1000 USDC fee := big.NewInt(100_050_000) // 100.05 USDC quote := reldb.QuoteRequest{ BlockNumber: 1, OriginTokenDecimals: 6, DestTokenDecimals: 6, Transaction: fastbridge.IFastBridgeBridgeTransaction{ OriginChainId: s.origin, DestChainId: s.destination, OriginToken: common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), DestToken: common.HexToAddress("0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"), OriginAmount: balance, DestAmount: new(big.Int).Sub(balance, fee), }, } s.True(s.manager.ShouldProcess(s.GetTestContext(), quote)) // Set different numbers of decimals for origin / dest tokens; should never process this. badQuote := quote badQuote.DestTokenDecimals = 18 s.False(s.manager.ShouldProcess(s.GetTestContext(), badQuote)) // Toggle relayPaused s.manager.SetRelayPaused(true) s.False(s.manager.ShouldProcess(s.GetTestContext(), quote)) s.manager.SetRelayPaused(false) s.True(s.manager.ShouldProcess(s.GetTestContext(), quote)) // Set max relay amount originTokenCfg := s.config.Chains[int(s.origin)].Tokens["USDC"] originTokenCfg.MaxRelayAmount = "900" // less than balance s.config.Chains[int(s.origin)].Tokens["USDC"] = originTokenCfg s.manager.SetConfig(s.config) s.False(s.manager.ShouldProcess(s.GetTestContext(), quote)) } func (s *QuoterSuite) TestIsProfitable() { // Set fee to breakeven; i.e. destAmount = originAmount - fee. balance := big.NewInt(1000_000_000) // 1000 USDC fee := big.NewInt(100_050_000) // 100.05 USDC quote := reldb.QuoteRequest{ BlockNumber: 1, OriginTokenDecimals: 6, DestTokenDecimals: 6, Transaction: fastbridge.IFastBridgeBridgeTransaction{ OriginChainId: s.origin, DestChainId: s.destination, OriginToken: common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), DestToken: common.HexToAddress("0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"), OriginAmount: balance, DestAmount: new(big.Int).Sub(balance, fee), }, } s.True(s.manager.IsProfitable(s.GetTestContext(), quote)) // Set fee to greater than breakeven; i.e. destAmount > originAmount - fee. quote.Transaction.DestAmount = new(big.Int).Sub(balance, new(big.Int).Mul(fee, big.NewInt(2))) s.True(s.manager.IsProfitable(s.GetTestContext(), quote)) // Set fee to less than breakeven; i.e. destAmount < originAmount - fee. quote.Transaction.DestAmount = balance s.False(s.manager.IsProfitable(s.GetTestContext(), quote)) origin := int(s.origin) dest := int(s.destination) setQuoteOffsets := func(originOffset, destOffset float64) { originTokenCfg := s.config.Chains[origin].Tokens["USDC"] originTokenCfg.QuoteOffsetBps = originOffset s.config.Chains[origin].Tokens["USDC"] = originTokenCfg destTokenCfg := s.config.Chains[dest].Tokens["USDC"] destTokenCfg.QuoteOffsetBps = destOffset s.config.Chains[dest].Tokens["USDC"] = destTokenCfg s.manager.SetConfig(s.config) } quote.Transaction.DestAmount = new(big.Int).Sub(balance, fee) // Set dest offset to 20%; we send a token that is more valuable -> not profitable setQuoteOffsets(0, 2000) s.False(s.manager.IsProfitable(s.GetTestContext(), quote)) // Set dest offset to -20%; we send a token that is less valuable -> profitable setQuoteOffsets(0, -2000) s.True(s.manager.IsProfitable(s.GetTestContext(), quote)) // Set origin offset to 20%; we get a token that is more valuable -> not profitable setQuoteOffsets(2000, 0) s.True(s.manager.IsProfitable(s.GetTestContext(), quote)) // Set origin offset to -20%; we send a token that is less valuable -> not profitable setQuoteOffsets(-2000, 0) s.False(s.manager.IsProfitable(s.GetTestContext(), quote)) } func (s *QuoterSuite) TestGetOriginAmountActiveQuotes() { origin := int(s.origin) dest := int(s.destination) address := common.HexToAddress("0x0b2c639c533813f4aa9d7837caf62653d097ff85") originAddr := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48") balance := big.NewInt(1000_000_000) // 1000 USDC depositAmount := big.NewInt(50_000_000) // 50 USDC type quoteParams struct { quotePct float64 quoteOffset float64 minQuoteAmount string maxBalance string maxQuoteAmount string } setQuoteParams := func(params quoteParams) { s.config.BaseChainConfig.QuotePct = ¶ms.quotePct destTokenCfg := s.config.Chains[dest].Tokens["USDC"] destTokenCfg.MinQuoteAmount = params.minQuoteAmount destTokenCfg.MaxRelayAmount = params.maxQuoteAmount originTokenCfg := s.config.Chains[origin].Tokens["USDC"] originTokenCfg.QuoteOffsetBps = params.quoteOffset originTokenCfg.MaxBalance = ¶ms.maxBalance originTokenCfg.MaxRelayAmount = params.maxQuoteAmount s.config.Chains[dest].Tokens["USDC"] = destTokenCfg s.config.Chains[origin].Tokens["USDC"] = originTokenCfg s.manager.SetConfig(s.config) } input := quoter.QuoteInput{ OriginChainID: origin, DestChainID: dest, OriginTokenAddr: originAddr, DestTokenAddr: address, OriginBalance: balance, DestBalance: balance, OriginAmountExact: depositAmount, } // Set default quote params; should return the depositAmount. quoteAmount, err := s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount := depositAmount s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 50 with MinQuoteAmount of 0; should still be 100% of deposit amount // IE: we're only willing to use 50% of our balance, but deposit amount is well below that. setQuoteParams(quoteParams{ quotePct: 50, quoteOffset: 0, minQuoteAmount: "0", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = depositAmount s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 1 with MinQuoteAmount of 0; // should return zero even though we can partially cover -- because deposit amount is all-or-nothing. // IE: we're only willing to use 1% of our balance, which does not cover deposit amount. setQuoteParams(quoteParams{ quotePct: 1, quoteOffset: 0, minQuoteAmount: "0", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(0) s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 5 with MinQuoteAmount of 0; // should the deposit amount because 5% of 1K is just enough to cover. setQuoteParams(quoteParams{ quotePct: 5, quoteOffset: 0, minQuoteAmount: "0", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = depositAmount s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 5 with MinQuoteAmount of 0; // should return the deposit amount because 5% of 1K balance is just enough to cover. setQuoteParams(quoteParams{ quotePct: 5, quoteOffset: 0, minQuoteAmount: "500", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = depositAmount s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 25 with MinQuoteAmount of 500; // minQuoteAmt ceiling = 500 // quotePct 25% * 1000 = 250 // output s/b depositAmount becase it does not exceed either setQuoteParams(quoteParams{ quotePct: 25, quoteOffset: 0, minQuoteAmount: "500", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = depositAmount s.Equal(expectedAmount, quoteAmount) // test minQuoteAmount ceiling *overriding* quotePct ceiling // output s/b depositAmount even though quotePct amount wont cover it, due to override setQuoteParams(quoteParams{ quotePct: 1, quoteOffset: 0, minQuoteAmount: "500", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = depositAmount s.Equal(expectedAmount, quoteAmount) // test quotePct ceiling *overriding* minQuoteAmount ceiling // output s/b depositAmount even though minQuoteAmount amount wont cover it, due to override setQuoteParams(quoteParams{ quotePct: 25, quoteOffset: 0, minQuoteAmount: "5", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = depositAmount s.Equal(expectedAmount, quoteAmount) // test depositAmount below both the quotePct ceiling and minQuoteAmount ceiling // output s/b 0 as neither ceiling setting accommodates the deposit amount setQuoteParams(quoteParams{ quotePct: 1, quoteOffset: 0, minQuoteAmount: "5", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(0) s.Equal(expectedAmount, quoteAmount) // Ceiling params much higher than deposit amount, should still return deposit amount exactly setQuoteParams(quoteParams{ quotePct: 100, quoteOffset: 0, minQuoteAmount: "0", maxBalance: "0", maxQuoteAmount: "500", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = depositAmount s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 25 with MinQuoteAmount of 1500 and MaxBalance of 1200; // effective ceiling is 200 due to maxBalance only allowing 200 more units on origin. // Since depositAmt is below this, output s/b deposit amount setQuoteParams(quoteParams{ quotePct: 25, quoteOffset: 0, minQuoteAmount: "1500", maxBalance: "1200", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = depositAmount s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 25 with MinQuoteAmount of 1500 and MaxBalance of 1025; // effective ceiling is 200 due to maxBalance only allowing 25 more units on origin. // Since depositAmt is above this ceiling, output s/b zero setQuoteParams(quoteParams{ quotePct: 25, quoteOffset: 0, minQuoteAmount: "1500", maxBalance: "1025", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(0) s.Equal(expectedAmount, quoteAmount) // Toggle insufficient gas; should be 0. s.setGasSufficiency(false) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(0) s.Equal(expectedAmount, quoteAmount) input.OriginAmountExact = new(big.Int).Add(balance, balance) // depositAmount beyond our balance. // output s/b zero even with generous limits setQuoteParams(quoteParams{ quotePct: 100, quoteOffset: 0, minQuoteAmount: "1500", maxBalance: "2000", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(0) s.Equal(expectedAmount, quoteAmount) input.OriginAmountExact = big.NewInt(0) // depositAmount set to zero. output s/b zero setQuoteParams(quoteParams{ quotePct: 100, quoteOffset: 0, minQuoteAmount: "1500", maxBalance: "2000", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(0) s.Equal(expectedAmount, quoteAmount) input.OriginAmountExact = big.NewInt(-55000000) // depositAmount set to negative. output s/b zero setQuoteParams(quoteParams{ quotePct: 100, quoteOffset: 0, minQuoteAmount: "1500", maxBalance: "2000", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(0) s.Equal(expectedAmount, quoteAmount) } func (s *QuoterSuite) TestGetOriginAmount() { origin := int(s.origin) dest := int(s.destination) address := common.HexToAddress("0x0b2c639c533813f4aa9d7837caf62653d097ff85") originAddr := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48") balance := big.NewInt(1000_000_000) // 1000 USDC type quoteParams struct { quotePct float64 quoteOffset float64 minQuoteAmount string maxBalance string maxQuoteAmount string } setQuoteParams := func(params quoteParams) { s.config.BaseChainConfig.QuotePct = ¶ms.quotePct destTokenCfg := s.config.Chains[dest].Tokens["USDC"] destTokenCfg.MinQuoteAmount = params.minQuoteAmount destTokenCfg.MaxRelayAmount = params.maxQuoteAmount originTokenCfg := s.config.Chains[origin].Tokens["USDC"] originTokenCfg.QuoteOffsetBps = params.quoteOffset originTokenCfg.MaxBalance = ¶ms.maxBalance originTokenCfg.MaxRelayAmount = params.maxQuoteAmount s.config.Chains[dest].Tokens["USDC"] = destTokenCfg s.config.Chains[origin].Tokens["USDC"] = originTokenCfg s.manager.SetConfig(s.config) } input := quoter.QuoteInput{ OriginChainID: origin, DestChainID: dest, OriginTokenAddr: originAddr, DestTokenAddr: address, OriginBalance: balance, DestBalance: balance, } // Set default quote params; should return the balance. quoteAmount, err := s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount := balance s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 50 with MinQuoteAmount of 0; should be 50% of balance. setQuoteParams(quoteParams{ quotePct: 50, quoteOffset: 0, minQuoteAmount: "0", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(500_000_000) s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 25 with MinQuoteAmount of 500; should be 50% of balance. setQuoteParams(quoteParams{ quotePct: 25, quoteOffset: 0, minQuoteAmount: "500", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(500_000_000) s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 25 with MinQuoteAmount of 500; should be 50% of balance. setQuoteParams(quoteParams{ quotePct: 25, quoteOffset: 0, minQuoteAmount: "500", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(500_000_000) s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 25 with MinQuoteAmount of 1500; should be total balance. setQuoteParams(quoteParams{ quotePct: 25, quoteOffset: 0, minQuoteAmount: "1500", maxBalance: "0", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(1000_000_000) s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 100 with MinQuoteAmount of 0 and MaxRelayAmount of 500; should be 500. setQuoteParams(quoteParams{ quotePct: 100, quoteOffset: 0, minQuoteAmount: "0", maxBalance: "0", maxQuoteAmount: "500", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(500_000_000) s.Equal(expectedAmount, quoteAmount) // Set QuotePct to 25 with MinQuoteAmount of 1500 and MaxBalance of 1200; should be 200. setQuoteParams(quoteParams{ quotePct: 25, quoteOffset: 0, minQuoteAmount: "1500", maxBalance: "1200", }) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(200_000_000) s.Equal(expectedAmount, quoteAmount) // Toggle insufficient gas; should be 0. s.setGasSufficiency(false) quoteAmount, err = s.manager.GetOriginAmount(s.GetTestContext(), input) s.NoError(err) expectedAmount = big.NewInt(0) s.Equal(expectedAmount, quoteAmount) } func (s *QuoterSuite) setGasSufficiency(sufficient bool) { clientFetcher := new(fetcherMocks.ClientFetcher) 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()) inventoryManager := new(inventoryMocks.Manager) inventoryManager.On(testsuite.GetFunctionName(inventoryManager.HasSufficientGas), mock.Anything, mock.Anything, mock.Anything).Return(sufficient, 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) } func (s *QuoterSuite) TestGetDestAmount() { balance := big.NewInt(1000_000_000) // 1000 USDC origin := int(s.origin) dest := int(s.destination) input := quoter.QuoteInput{ OriginChainID: int(s.origin), DestChainID: int(s.destination), OriginBalance: balance, DestBalance: balance, } setQuoteParams := func(originQuoteOffsetBps, destQuoteOffsetBps, quoteWidthBps float64) { tokenCfg := s.config.Chains[origin].Tokens["USDC"] tokenCfg.QuoteOffsetBps = originQuoteOffsetBps s.config.Chains[origin].Tokens["USDC"] = tokenCfg tokenCfg = s.config.Chains[dest].Tokens["USDC"] tokenCfg.QuoteOffsetBps = destQuoteOffsetBps tokenCfg.QuoteWidthBps = quoteWidthBps s.config.Chains[dest].Tokens["USDC"] = tokenCfg s.manager.SetConfig(s.config) } // Set default quote params; should return the balance. destAmount, err := s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) s.NoError(err) expectedAmount := balance s.Equal(expectedAmount, destAmount) // Set QuoteWidthBps to 100, should return 99% of balance. setQuoteParams(0, 0, 100) destAmount, err = s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) s.NoError(err) expectedAmount = big.NewInt(990_000_000) s.Equal(expectedAmount, destAmount) // Set QuoteWidthBps to 500, should return 95% of balance. setQuoteParams(0, 0, 500) destAmount, err = s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) s.NoError(err) expectedAmount = big.NewInt(950_000_000) s.Equal(expectedAmount, destAmount) // Set QuoteWidthBps to 500 and QuoteOffsetBps to 100, should return 94% of balance. setQuoteParams(0, 100, 500) destAmount, err = s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) s.NoError(err) expectedAmount = big.NewInt(940_000_000) s.Equal(expectedAmount, destAmount) // Set QuoteWidthBps to 500 and QuoteOffsetBps to -100, should return 96% of balance. setQuoteParams(0, -100, 500) destAmount, err = s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) s.NoError(err) expectedAmount = big.NewInt(960_000_000) s.Equal(expectedAmount, destAmount) // Set QuoteWidthBps to -100, should error. setQuoteParams(0, 0, -100) destAmount, err = s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) s.Error(err) s.Nil(destAmount) // Set origin offset to 100, should return 101% of balance. setQuoteParams(100, 0, 0) destAmount, err = s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) s.NoError(err) expectedAmount = big.NewInt(1_010_000_000) s.Equal(expectedAmount, destAmount) // Set origin offset to -100, should return 99% of balance. setQuoteParams(-100, 0, 0) destAmount, err = s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) s.NoError(err) expectedAmount = big.NewInt(990_000_000) s.Equal(expectedAmount, destAmount) // Set origin offset to 100, dest offset to 300, should return 98% of balance. setQuoteParams(100, 300, 0) destAmount, err = s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) s.NoError(err) expectedAmount = big.NewInt(980_000_000) s.Equal(expectedAmount, destAmount) }