feat_: the router - add candidates, as potential paths, by taking the max amount on enabled chains

This commit is contained in:
Sale Djenic 2024-07-04 12:48:14 +02:00 committed by saledjenic
parent 4b19845592
commit 378e5741b9
4 changed files with 1026 additions and 589 deletions

View File

@ -27,4 +27,5 @@ var (
ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-019"), Details: "disabled chain found among locked networks"} ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-019"), Details: "disabled chain found among locked networks"}
ErrENSSetPubKeyInvalidUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WR-020"), Details: "a valid username, ending in '.eth', is required for ENSSetPubKey"} ErrENSSetPubKeyInvalidUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WR-020"), Details: "a valid username, ending in '.eth', is required for ENSSetPubKey"}
ErrLockedAmountExcludesAllSupported = &errors.ErrorResponse{Code: errors.ErrorCode("WR-021"), Details: "all supported chains are excluded, routing impossible"} ErrLockedAmountExcludesAllSupported = &errors.ErrorResponse{Code: errors.ErrorCode("WR-021"), Details: "all supported chains are excluded, routing impossible"}
ErrTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-022"), Details: "token not found"}
) )

View File

@ -414,8 +414,8 @@ func (r *Router) requireApproval(ctx context.Context, sendType SendType, approva
return true, params.AmountIn, estimate, l1Fee, nil return true, params.AmountIn, estimate, l1Fee, nil
} }
func (r *Router) getBalance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) { func (r *Router) getBalance(ctx context.Context, chainID uint64, token *token.Token, account common.Address) (*big.Int, error) {
client, err := r.rpcClient.EthClient(network.ChainID) client, err := r.rpcClient.EthClient(chainID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -521,7 +521,7 @@ func (r *Router) SuggestedRoutes(
return err return err
} }
} else if sendType != ERC721Transfer { } else if sendType != ERC721Transfer {
balance, err = r.getBalance(ctx, network, token, addrFrom) balance, err = r.getBalance(ctx, network.ChainID, token, addrFrom)
if err != nil { if err != nil {
return err return err
} }
@ -535,7 +535,7 @@ func (r *Router) SuggestedRoutes(
maxAmountIn = amount maxAmountIn = amount
} }
nativeBalance, err := r.getBalance(ctx, network, nativeToken, addrFrom) nativeBalance, err := r.getBalance(ctx, network.ChainID, nativeToken, addrFrom)
if err != nil { if err != nil {
return err return err
} }

View File

@ -79,15 +79,13 @@ type routerTestParams struct {
approvalL1Fee uint64 approvalL1Fee uint64
} }
func makeTestBalanceKey(chainID uint64, symbol string) string { type amountOption struct {
return fmt.Sprintf("%d-%s", chainID, symbol) amount *big.Int
locked bool
} }
func (rt routerTestParams) getTestBalance(chainID uint64, symbol string) *big.Int { func makeBalanceKey(chainID uint64, symbol string) string {
if val, ok := rt.balanceMap[makeTestBalanceKey(chainID, symbol)]; ok { return fmt.Sprintf("%d-%s", chainID, symbol)
return val
}
return big.NewInt(0)
} }
type PathV2 struct { type PathV2 struct {
@ -487,40 +485,246 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
return nil, errors.CreateErrorResponseFromError(err) return nil, errors.CreateErrorResponseFromError(err)
} }
candidates, err := r.resolveCandidates(ctx, input) selectedFromChains, selectedTohains, err := r.getSelectedChains(input)
if err != nil { if err != nil {
return nil, errors.CreateErrorResponseFromError(err) return nil, errors.CreateErrorResponseFromError(err)
} }
return r.resolveRoutes(ctx, input, candidates) balanceMap, err := r.getBalanceMapForTokenOnChains(ctx, input, selectedFromChains)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
candidates, err := r.resolveCandidates(ctx, input, selectedFromChains, selectedTohains, balanceMap)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
return r.resolveRoutes(ctx, input, candidates, balanceMap)
} }
func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams) (candidates []*PathV2, err error) { // getBalanceMapForTokenOnChains returns the balance map for passed address, where the key is in format "chainID-tokenSymbol" and
var ( // value is the balance of the token. Native token (EHT) is always added to the balance map.
testsMode = input.testsMode && input.testParams != nil func (r *Router) getBalanceMapForTokenOnChains(ctx context.Context, input *RouteInputParams, selectedFromChains []*params.Network) (balanceMap map[string]*big.Int, err error) {
networks []*params.Network if input.testsMode {
) return input.testParams.balanceMap, nil
networks, err = r.rpcClient.NetworkManager.Get(false)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
} }
var ( balanceMap = make(map[string]*big.Int)
group = async.NewAtomicGroup(ctx)
mu sync.Mutex
)
for networkIdx := range networks { for _, chain := range selectedFromChains {
network := networks[networkIdx] token := input.SendType.FindToken(r.tokenManager, r.collectiblesService, input.AddrFrom, chain, input.TokenID)
if token == nil {
continue
}
// add token balance for the chain
tokenBalance := big.NewInt(1)
if input.SendType == ERC1155Transfer {
tokenBalance, err = r.getERC1155Balance(ctx, chain, token, input.AddrFrom)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
} else if input.SendType != ERC721Transfer {
tokenBalance, err = r.getBalance(ctx, chain.ChainID, token, input.AddrFrom)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
}
balanceMap[makeBalanceKey(chain.ChainID, token.Symbol)] = tokenBalance
// add native token balance for the chain
nativeToken := r.tokenManager.FindToken(chain, chain.NativeCurrencySymbol)
if nativeToken == nil {
return nil, ErrNativeTokenNotFound
}
nativeBalance, err := r.getBalance(ctx, chain.ChainID, nativeToken, input.AddrFrom)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
balanceMap[makeBalanceKey(chain.ChainID, nativeToken.Symbol)] = nativeBalance
}
return
}
func (r *Router) getSelectedUnlockedChains(input *RouteInputParams, processingChain *params.Network, selectedFromChains []*params.Network) []*params.Network {
selectedButNotLockedChains := []*params.Network{processingChain} // always add the processing chain at the beginning
for _, net := range selectedFromChains {
if net.ChainID == processingChain.ChainID {
continue
}
if _, ok := input.FromLockedAmount[net.ChainID]; !ok {
selectedButNotLockedChains = append(selectedButNotLockedChains, net)
}
}
return selectedButNotLockedChains
}
func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input *RouteInputParams, amountToSplit *big.Int, processingChain *params.Network,
selectedFromChains []*params.Network, balanceMap map[string]*big.Int) map[uint64][]amountOption {
selectedButNotLockedChains := r.getSelectedUnlockedChains(input, processingChain, selectedFromChains)
crossChainAmountOptions := make(map[uint64][]amountOption)
for _, chain := range selectedButNotLockedChains {
var (
ok bool
tokenBalance *big.Int
)
if tokenBalance, ok = balanceMap[makeBalanceKey(chain.ChainID, input.TokenID)]; !ok {
continue
}
if tokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
if tokenBalance.Cmp(amountToSplit) <= 0 {
crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{
amount: tokenBalance,
locked: false,
})
amountToSplit = new(big.Int).Sub(amountToSplit, tokenBalance)
} else if amountToSplit.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{
amount: amountToSplit,
locked: false,
})
// break since amountToSplit is fully addressed and the rest is 0
break
}
}
}
return crossChainAmountOptions
}
func (r *Router) getCrossChainsOptionsForSendingAmount(input *RouteInputParams, selectedFromChains []*params.Network,
balanceMap map[string]*big.Int) map[uint64][]amountOption {
// All we do in this block we're free to do, because of the validateInputData function which checks if the locked amount
// was properly set and if there is something unexpected it will return an error and we will not reach this point
finalCrossChainAmountOptions := make(map[uint64][]amountOption) // represents all possible amounts that can be sent from the "from" chain
for _, selectedFromChain := range selectedFromChains {
amountLocked := false
amountToSend := input.AmountIn.ToInt()
lockedAmount, fromChainLocked := input.FromLockedAmount[selectedFromChain.ChainID]
if fromChainLocked {
amountToSend = lockedAmount.ToInt()
amountLocked = true
} else if len(input.FromLockedAmount) > 0 {
for chainID, lockedAmount := range input.FromLockedAmount {
if chainID == selectedFromChain.ChainID {
continue
}
amountToSend = new(big.Int).Sub(amountToSend, lockedAmount.ToInt())
}
}
if amountToSend.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
// add full amount always, cause we want to check for balance errors at the end of the routing algorithm
// TODO: once we introduce bettwer error handling and start checking for the balance at the beginning of the routing algorithm
// we can remove this line and optimize the routing algorithm more
finalCrossChainAmountOptions[selectedFromChain.ChainID] = append(finalCrossChainAmountOptions[selectedFromChain.ChainID], amountOption{
amount: amountToSend,
locked: amountLocked,
})
if amountLocked {
continue
}
// If the amount that need to be send is bigger than the balance on the chain, then we want to check options if that
// amount can be splitted and sent across multiple chains.
if input.SendType == Transfer && len(selectedFromChains) > 1 {
// All we do in this block we're free to do, because of the validateInputData function which checks if the locked amount
// was properly set and if there is something unexpected it will return an error and we will not reach this point
amountToSplitAccrossChains := new(big.Int).Set(amountToSend)
crossChainAmountOptions := r.getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input, amountToSend, selectedFromChain, selectedFromChains, balanceMap)
// sum up all the allocated amounts accorss all chains
allocatedAmount := big.NewInt(0)
for _, amountOptions := range crossChainAmountOptions {
for _, amountOption := range amountOptions {
allocatedAmount = new(big.Int).Add(allocatedAmount, amountOption.amount)
}
}
// if the allocated amount is the same as the amount that need to be sent, then we can add the options to the finalCrossChainAmountOptions
if allocatedAmount.Cmp(amountToSplitAccrossChains) == 0 {
for cID, amountOptions := range crossChainAmountOptions {
finalCrossChainAmountOptions[cID] = append(finalCrossChainAmountOptions[cID], amountOptions...)
}
}
}
}
}
return finalCrossChainAmountOptions
}
func (r *Router) findOptionsForSendingAmount(input *RouteInputParams, selectedFromChains []*params.Network,
balanceMap map[string]*big.Int) (map[uint64][]amountOption, error) {
crossChainAmountOptions := r.getCrossChainsOptionsForSendingAmount(input, selectedFromChains, balanceMap)
// filter out duplicates values for the same chain
for chainID, amountOptions := range crossChainAmountOptions {
uniqueAmountOptions := make(map[string]amountOption)
for _, amountOption := range amountOptions {
uniqueAmountOptions[amountOption.amount.String()] = amountOption
}
crossChainAmountOptions[chainID] = make([]amountOption, 0)
for _, amountOption := range uniqueAmountOptions {
crossChainAmountOptions[chainID] = append(crossChainAmountOptions[chainID], amountOption)
}
}
return crossChainAmountOptions, nil
}
func (r *Router) getSelectedChains(input *RouteInputParams) (selectedFromChains []*params.Network, selectedTohains []*params.Network, err error) {
var networks []*params.Network
networks, err = r.rpcClient.NetworkManager.Get(false)
if err != nil {
return nil, nil, errors.CreateErrorResponseFromError(err)
}
for _, network := range networks {
if network.IsTest != input.testnetMode { if network.IsTest != input.testnetMode {
continue continue
} }
if containsNetworkChainID(network.ChainID, input.DisabledFromChainIDs) { if !containsNetworkChainID(network.ChainID, input.DisabledFromChainIDs) {
continue selectedFromChains = append(selectedFromChains, network)
} }
if !containsNetworkChainID(network.ChainID, input.DisabledToChainIDs) {
selectedTohains = append(selectedTohains, network)
}
}
return selectedFromChains, selectedTohains, nil
}
func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams, selectedFromChains []*params.Network,
selectedTohains []*params.Network, balanceMap map[string]*big.Int) (candidates []*PathV2, err error) {
var (
testsMode = input.testsMode && input.testParams != nil
group = async.NewAtomicGroup(ctx)
mu sync.Mutex
)
crossChainAmountOptions, err := r.findOptionsForSendingAmount(input, selectedFromChains, balanceMap)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
for networkIdx := range selectedFromChains {
network := selectedFromChains[networkIdx]
if !input.SendType.isAvailableFor(network) { if !input.SendType.isAvailableFor(network) {
continue continue
} }
@ -543,20 +747,6 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
toToken = input.SendType.FindToken(r.tokenManager, r.collectiblesService, common.Address{}, network, input.ToTokenID) toToken = input.SendType.FindToken(r.tokenManager, r.collectiblesService, common.Address{}, network, input.ToTokenID)
} }
amountLocked := false
amountToSend := input.AmountIn.ToInt()
if lockedAmount, ok := input.FromLockedAmount[network.ChainID]; ok {
amountToSend = lockedAmount.ToInt()
amountLocked = true
} else if len(input.FromLockedAmount) > 0 {
for chainID, lockedAmount := range input.FromLockedAmount {
if chainID == network.ChainID {
continue
}
amountToSend = new(big.Int).Sub(amountToSend, lockedAmount.ToInt())
}
}
var fees *SuggestedFees var fees *SuggestedFees
if testsMode { if testsMode {
fees = input.testParams.suggestedFees fees = input.testParams.suggestedFees
@ -568,144 +758,139 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
} }
group.Add(func(c context.Context) error { group.Add(func(c context.Context) error {
for _, pProcessor := range r.pathProcessors { for _, amountOption := range crossChainAmountOptions[network.ChainID] {
// With the condition below we're eliminating `Swap` as potential path that can participate in calculating the best route for _, pProcessor := range r.pathProcessors {
// once we decide to inlcude `Swap` in the calculation we need to update `canUseProcessor` function. // With the condition below we're eliminating `Swap` as potential path that can participate in calculating the best route
// This also applies to including another (Celer) bridge in the calculation. // once we decide to inlcude `Swap` in the calculation we need to update `canUseProcessor` function.
// TODO: // This also applies to including another (Celer) bridge in the calculation.
// this algorithm, includeing finding the best route, has to be updated to include more bridges and one (for now) or more swap options // TODO:
// it means that candidates should not be treated linearly, but improve the logic to have multiple routes with different processors of the same type. // this algorithm, includeing finding the best route, has to be updated to include more bridges and one (for now) or more swap options
// Example: // it means that candidates should not be treated linearly, but improve the logic to have multiple routes with different processors of the same type.
// Routes for sending SNT from Ethereum to Optimism can be: // Example:
// 1. Swap SNT(mainnet) to ETH(mainnet); then bridge via Hop ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination // Routes for sending SNT from Ethereum to Optimism can be:
// 2. Swap SNT(mainnet) to ETH(mainnet); then bridge via Celer ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination // 1. Swap SNT(mainnet) to ETH(mainnet); then bridge via Hop ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination
// 3. Swap SNT(mainnet) to USDC(mainnet); then bridge via Hop USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination // 2. Swap SNT(mainnet) to ETH(mainnet); then bridge via Celer ETH(mainnet) to ETH(opt); then Swap ETH(opt) to SNT(opt); then send SNT (opt) to the destination
// 4. Swap SNT(mainnet) to USDC(mainnet); then bridge via Celer USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination // 3. Swap SNT(mainnet) to USDC(mainnet); then bridge via Hop USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination
// 5. ... // 4. Swap SNT(mainnet) to USDC(mainnet); then bridge via Celer USDC(mainnet) to USDC(opt); then Swap USDC(opt) to SNT(opt); then send SNT (opt) to the destination
// 6. ... // 5. ...
// // 6. ...
// With the current routing algorithm atm we're not able to generate all possible routes. //
if !input.SendType.canUseProcessor(pProcessor) { // With the current routing algorithm atm we're not able to generate all possible routes.
continue if !input.SendType.canUseProcessor(pProcessor) {
}
for _, dest := range networks {
if dest.IsTest != input.testnetMode {
continue continue
} }
if !input.SendType.isAvailableFor(network) { for _, dest := range selectedTohains {
continue
if !input.SendType.isAvailableFor(network) {
continue
}
if !input.SendType.isAvailableBetween(network, dest) {
continue
}
processorInputParams := pathprocessor.ProcessorInputParams{
FromChain: network,
ToChain: dest,
FromToken: token,
ToToken: toToken,
ToAddr: input.AddrTo,
FromAddr: input.AddrFrom,
AmountIn: amountOption.amount,
AmountOut: input.AmountOut.ToInt(),
Username: input.Username,
PublicKey: input.PublicKey,
PackID: input.PackID.ToInt(),
}
if input.testsMode {
processorInputParams.TestsMode = input.testsMode
processorInputParams.TestEstimationMap = input.testParams.estimationMap
processorInputParams.TestBonderFeeMap = input.testParams.bonderFeeMap
processorInputParams.TestApprovalGasEstimation = input.testParams.approvalGasEstimation
processorInputParams.TestApprovalL1Fee = input.testParams.approvalL1Fee
}
can, err := pProcessor.AvailableFor(processorInputParams)
if err != nil || !can {
continue
}
bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams)
if err != nil {
continue
}
gasLimit, err := pProcessor.EstimateGas(processorInputParams)
if err != nil {
continue
}
approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams)
if err != nil {
continue
}
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, input.SendType, &approvalContractAddress, processorInputParams)
if err != nil {
continue
}
// TODO: keep l1 fees at 0 until we have the correct algorithm, as we do base fee x 2 that should cover the l1 fees
var l1FeeWei uint64 = 0
// if input.SendType.needL1Fee() {
// txInputData, err := pProcessor.PackTxInputData(processorInputParams)
// if err != nil {
// continue
// }
// l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
// }
amountOut, err := pProcessor.CalculateAmountOut(processorInputParams)
if err != nil {
continue
}
maxFeesPerGas := fees.feeFor(input.GasFeeMode)
estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas)
if approvalRequired && estimatedTime < MoreThanFiveMinutes {
estimatedTime += 1
}
mu.Lock()
candidates = append(candidates, &PathV2{
ProcessorName: pProcessor.Name(),
FromChain: network,
ToChain: dest,
FromToken: token,
ToToken: toToken,
AmountIn: (*hexutil.Big)(amountOption.amount),
AmountInLocked: amountOption.locked,
AmountOut: (*hexutil.Big)(amountOut),
SuggestedLevelsForMaxFeesPerGas: fees.MaxFeesLevels,
TxBaseFee: (*hexutil.Big)(fees.BaseFee),
TxPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
TxGasAmount: gasLimit,
TxBonderFees: (*hexutil.Big)(bonderFees),
TxTokenFees: (*hexutil.Big)(tokenFees),
TxL1Fee: (*hexutil.Big)(big.NewInt(int64(l1FeeWei))),
ApprovalRequired: approvalRequired,
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
ApprovalContractAddress: &approvalContractAddress,
ApprovalBaseFee: (*hexutil.Big)(fees.BaseFee),
ApprovalPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
ApprovalGasAmount: approvalGasLimit,
ApprovalL1Fee: (*hexutil.Big)(big.NewInt(int64(l1ApprovalFee))),
EstimatedTime: estimatedTime,
})
mu.Unlock()
} }
if !input.SendType.isAvailableBetween(network, dest) {
continue
}
if containsNetworkChainID(dest.ChainID, input.DisabledToChainIDs) {
continue
}
processorInputParams := pathprocessor.ProcessorInputParams{
FromChain: network,
ToChain: dest,
FromToken: token,
ToToken: toToken,
ToAddr: input.AddrTo,
FromAddr: input.AddrFrom,
AmountIn: amountToSend,
AmountOut: input.AmountOut.ToInt(),
Username: input.Username,
PublicKey: input.PublicKey,
PackID: input.PackID.ToInt(),
}
if input.testsMode {
processorInputParams.TestsMode = input.testsMode
processorInputParams.TestEstimationMap = input.testParams.estimationMap
processorInputParams.TestBonderFeeMap = input.testParams.bonderFeeMap
processorInputParams.TestApprovalGasEstimation = input.testParams.approvalGasEstimation
processorInputParams.TestApprovalL1Fee = input.testParams.approvalL1Fee
}
can, err := pProcessor.AvailableFor(processorInputParams)
if err != nil || !can {
continue
}
bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams)
if err != nil {
continue
}
gasLimit, err := pProcessor.EstimateGas(processorInputParams)
if err != nil {
continue
}
approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams)
if err != nil {
continue
}
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, input.SendType, &approvalContractAddress, processorInputParams)
if err != nil {
continue
}
// TODO: keep l1 fees at 0 until we have the correct algorithm, as we do base fee x 2 that should cover the l1 fees
var l1FeeWei uint64 = 0
// if input.SendType.needL1Fee() {
// txInputData, err := pProcessor.PackTxInputData(processorInputParams)
// if err != nil {
// continue
// }
// l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
// }
amountOut, err := pProcessor.CalculateAmountOut(processorInputParams)
if err != nil {
continue
}
maxFeesPerGas := fees.feeFor(input.GasFeeMode)
estimatedTime := r.feesManager.TransactionEstimatedTime(ctx, network.ChainID, maxFeesPerGas)
if approvalRequired && estimatedTime < MoreThanFiveMinutes {
estimatedTime += 1
}
mu.Lock()
candidates = append(candidates, &PathV2{
ProcessorName: pProcessor.Name(),
FromChain: network,
ToChain: dest,
FromToken: token,
ToToken: toToken,
AmountIn: (*hexutil.Big)(amountToSend),
AmountInLocked: amountLocked,
AmountOut: (*hexutil.Big)(amountOut),
SuggestedLevelsForMaxFeesPerGas: fees.MaxFeesLevels,
TxBaseFee: (*hexutil.Big)(fees.BaseFee),
TxPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
TxGasAmount: gasLimit,
TxBonderFees: (*hexutil.Big)(bonderFees),
TxTokenFees: (*hexutil.Big)(tokenFees),
TxL1Fee: (*hexutil.Big)(big.NewInt(int64(l1FeeWei))),
ApprovalRequired: approvalRequired,
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
ApprovalContractAddress: &approvalContractAddress,
ApprovalBaseFee: (*hexutil.Big)(fees.BaseFee),
ApprovalPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
ApprovalGasAmount: approvalGasLimit,
ApprovalL1Fee: (*hexutil.Big)(big.NewInt(int64(l1ApprovalFee))),
EstimatedTime: estimatedTime,
})
mu.Unlock()
} }
} }
return nil return nil
@ -716,49 +901,25 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
return candidates, nil return candidates, nil
} }
func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*PathV2, input *RouteInputParams) (err error) { func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*PathV2, input *RouteInputParams, balanceMap map[string]*big.Int) (err error) {
// check the best route for the required balances // check the best route for the required balances
for _, path := range bestRoute { for _, path := range bestRoute {
if path.requiredTokenBalance != nil && path.requiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 { if path.requiredTokenBalance != nil && path.requiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
tokenBalance := big.NewInt(1) if tokenBalance, ok := balanceMap[makeBalanceKey(path.FromChain.ChainID, path.FromToken.Symbol)]; ok {
if input.testsMode { if tokenBalance.Cmp(path.requiredTokenBalance) == -1 {
tokenBalance = input.testParams.getTestBalance(path.FromChain.ChainID, path.FromToken.Symbol) return ErrNotEnoughTokenBalance
} else {
if input.SendType == ERC1155Transfer {
tokenBalance, err = r.getERC1155Balance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
if err != nil {
return errors.CreateErrorResponseFromError(err)
}
} else if input.SendType != ERC721Transfer {
tokenBalance, err = r.getBalance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
if err != nil {
return errors.CreateErrorResponseFromError(err)
}
} }
} } else {
return ErrTokenNotFound
if tokenBalance.Cmp(path.requiredTokenBalance) == -1 {
return ErrNotEnoughTokenBalance
} }
} }
var nativeBalance *big.Int if nativeBalance, ok := balanceMap[makeBalanceKey(path.FromChain.ChainID, pathprocessor.EthSymbol)]; ok {
if input.testsMode { if nativeBalance.Cmp(path.requiredNativeBalance) == -1 {
nativeBalance = input.testParams.getTestBalance(path.FromChain.ChainID, pathprocessor.EthSymbol) return ErrNotEnoughNativeBalance
}
} else { } else {
nativeToken := r.tokenManager.FindToken(path.FromChain, path.FromChain.NativeCurrencySymbol) return ErrNativeTokenNotFound
if nativeToken == nil {
return ErrNativeTokenNotFound
}
nativeBalance, err = r.getBalance(ctx, path.FromChain, nativeToken, input.AddrFrom)
if err != nil {
return errors.CreateErrorResponseFromError(err)
}
}
if nativeBalance.Cmp(path.requiredNativeBalance) == -1 {
return ErrNotEnoughNativeBalance
} }
} }
@ -792,7 +953,7 @@ func removeBestRouteFromAllRouters(allRoutes [][]*PathV2, best []*PathV2) [][]*P
return nil return nil
} }
func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, candidates []*PathV2) (suggestedRoutes *SuggestedRoutesV2, err error) { func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, candidates []*PathV2, balanceMap map[string]*big.Int) (suggestedRoutes *SuggestedRoutesV2, err error) {
var prices map[string]float64 var prices map[string]float64
if input.testsMode { if input.testsMode {
prices = input.testParams.tokenPrices prices = input.testParams.tokenPrices
@ -812,7 +973,7 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
for len(allRoutes) > 0 { for len(allRoutes) > 0 {
best := findBestV2(allRoutes, tokenPrice, nativeChainTokenPrice) best := findBestV2(allRoutes, tokenPrice, nativeChainTokenPrice)
err := r.checkBalancesForTheBestRoute(ctx, best, input) err := r.checkBalancesForTheBestRoute(ctx, best, input, balanceMap)
if err != nil { if err != nil {
// If it's about transfer or bridge and there is more routes, but on the best (cheapest) one there is not enugh balance // If it's about transfer or bridge and there is more routes, but on the best (cheapest) one there is not enugh balance
// we shold check other routes even though there are not the cheapest ones // we shold check other routes even though there are not the cheapest ones

File diff suppressed because it is too large Load Diff