feat: allow to lock amount in router

This commit is contained in:
Anthony Laibe 2022-11-23 18:49:23 +01:00 committed by Anthony Laibe
parent b4bdfd3df6
commit 6abbe98cd2
2 changed files with 166 additions and 38 deletions

View File

@ -347,9 +347,20 @@ func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64,
return api.s.feesManager.transactionEstimatedTime(ctx, chainID, maxFeePerGas), nil return api.s.feesManager.transactionEstimatedTime(ctx, chainID, maxFeePerGas), nil
} }
func (api *API) GetSuggestedRoutes(ctx context.Context, sendType SendType, account common.Address, amountIn *hexutil.Big, tokenSymbol string, disabledFromChainIDs, disabledToChaindIDs, preferedChainIDs []uint64, gasFeeMode GasFeeMode) (*SuggestedRoutes, error) { func (api *API) GetSuggestedRoutes(
ctx context.Context,
sendType SendType,
account common.Address,
amountIn *hexutil.Big,
tokenSymbol string,
disabledFromChainIDs,
disabledToChaindIDs,
preferedChainIDs []uint64,
gasFeeMode GasFeeMode,
fromLockedAmount map[uint64]*hexutil.Big,
) (*SuggestedRoutes, error) {
log.Debug("call to GetSuggestedRoutes") log.Debug("call to GetSuggestedRoutes")
return api.router.suggestedRoutes(ctx, sendType, account, amountIn.ToInt(), tokenSymbol, disabledFromChainIDs, disabledToChaindIDs, preferedChainIDs, gasFeeMode) return api.router.suggestedRoutes(ctx, sendType, account, amountIn.ToInt(), tokenSymbol, disabledFromChainIDs, disabledToChaindIDs, preferedChainIDs, gasFeeMode, fromLockedAmount)
} }
func (api *API) GetDerivedAddressesForPath(ctx context.Context, password string, derivedFrom string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) { func (api *API) GetDerivedAddressesForPath(ctx context.Context, password string, derivedFrom string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) {

View File

@ -2,9 +2,11 @@ package wallet
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"sort"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -106,6 +108,7 @@ type Path struct {
To *params.Network To *params.Network
MaxAmountIn *hexutil.Big MaxAmountIn *hexutil.Big
AmountIn *hexutil.Big AmountIn *hexutil.Big
AmountInLocked bool
AmountOut *hexutil.Big AmountOut *hexutil.Big
GasAmount uint64 GasAmount uint64
GasFees *SuggestedFees GasFees *SuggestedFees
@ -115,6 +118,10 @@ type Path struct {
EstimatedTime TransactionEstimation EstimatedTime TransactionEstimation
} }
func (p *Path) Equal(o *Path) bool {
return p.From.ChainID == o.From.ChainID && p.To.ChainID == o.To.ChainID
}
type Graph = []*Node type Graph = []*Node
type Node struct { type Node struct {
@ -143,7 +150,7 @@ func buildGraph(AmountIn *big.Int, routes []*Path, level int, sourceChainIDs []u
newRoutes := make([]*Path, 0) newRoutes := make([]*Path, 0)
for _, r := range routes { for _, r := range routes {
if r.From.ChainID == route.From.ChainID && r.To.ChainID == route.To.ChainID { if route.Equal(r) {
continue continue
} }
newRoutes = append(newRoutes, r) newRoutes = append(newRoutes, r)
@ -167,30 +174,112 @@ func buildGraph(AmountIn *big.Int, routes []*Path, level int, sourceChainIDs []u
return graph return graph
} }
func (n Node) findBest(level int) ([]*Path, *big.Float) { func (n Node) buildAllRoutes() [][]*Path {
if len(n.Children) == 0 { res := make([][]*Path, 0)
if n.Path == nil {
return []*Path{}, big.NewFloat(0)
}
return []*Path{n.Path}, n.Path.Cost
}
var best []*Path if len(n.Children) == 0 && n.Path != nil {
bestTotalCost := big.NewFloat(math.Inf(1)) res = append(res, []*Path{n.Path})
}
for _, node := range n.Children { for _, node := range n.Children {
routes, totalCost := node.findBest(level + 1) for _, route := range node.buildAllRoutes() {
if totalCost.Cmp(bestTotalCost) < 0 { extendedRoute := route
best = routes if n.Path != nil {
bestTotalCost = totalCost extendedRoute = append([]*Path{n.Path}, route...)
}
res = append(res, extendedRoute)
} }
} }
if n.Path == nil { return res
return best, bestTotalCost }
func filterRoutes(routes [][]*Path, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) [][]*Path {
if len(fromLockedAmount) == 0 {
return routes
} }
return append([]*Path{n.Path}, best...), new(big.Float).Add(bestTotalCost, n.Path.Cost) filteredRoutesLevel1 := make([][]*Path, 0)
for _, route := range routes {
routeOk := true
fromIncluded := make(map[uint64]bool)
fromExcluded := make(map[uint64]bool)
for chainID, amount := range fromLockedAmount {
if amount.ToInt().Cmp(zero) == 0 {
fromExcluded[chainID] = false
} else {
fromIncluded[chainID] = false
}
}
for _, path := range route {
if _, ok := fromExcluded[path.From.ChainID]; ok {
routeOk = false
break
}
if _, ok := fromIncluded[path.From.ChainID]; ok {
fromIncluded[path.From.ChainID] = true
}
}
for _, value := range fromIncluded {
if !value {
routeOk = false
break
}
}
if routeOk {
filteredRoutesLevel1 = append(filteredRoutesLevel1, route)
}
}
filteredRoutesLevel2 := make([][]*Path, 0)
for _, route := range filteredRoutesLevel1 {
routeOk := true
for _, path := range route {
if amount, ok := fromLockedAmount[path.From.ChainID]; ok {
requiredAmountIn := new(big.Int).Sub(amountIn, amount.ToInt())
restAmountIn := big.NewInt(0)
for _, otherPath := range route {
if path.Equal(otherPath) {
continue
}
restAmountIn = new(big.Int).Add(otherPath.MaxAmountIn.ToInt(), restAmountIn)
}
if restAmountIn.Cmp(requiredAmountIn) >= 0 {
path.AmountIn = amount
path.AmountInLocked = true
} else {
routeOk = false
break
}
}
}
if routeOk {
filteredRoutesLevel2 = append(filteredRoutesLevel2, route)
}
}
return filteredRoutesLevel2
}
func findBest(routes [][]*Path) []*Path {
var best []*Path
bestCost := big.NewFloat(math.Inf(1))
for _, route := range routes {
currentCost := big.NewFloat(0)
for _, path := range route {
currentCost = new(big.Float).Add(currentCost, path.Cost)
}
if currentCost.Cmp(bestCost) == -1 {
best = route
bestCost = currentCost
}
}
return best
} }
type SuggestedRoutes struct { type SuggestedRoutes struct {
@ -200,7 +289,11 @@ type SuggestedRoutes struct {
NativeChainTokenPrice float64 NativeChainTokenPrice float64
} }
func newSuggestedRoutes(amountIn *big.Int, candidates []*Path) *SuggestedRoutes { func newSuggestedRoutes(
amountIn *big.Int,
candidates []*Path,
fromLockedAmount map[uint64]*hexutil.Big,
) *SuggestedRoutes {
if len(candidates) == 0 { if len(candidates) == 0 {
return &SuggestedRoutes{ return &SuggestedRoutes{
Candidates: candidates, Candidates: candidates,
@ -212,14 +305,19 @@ func newSuggestedRoutes(amountIn *big.Int, candidates []*Path) *SuggestedRoutes
Path: nil, Path: nil,
Children: buildGraph(amountIn, candidates, 0, []uint64{}), Children: buildGraph(amountIn, candidates, 0, []uint64{}),
} }
best, _ := node.findBest(0) routes := node.buildAllRoutes()
routes = filterRoutes(routes, amountIn, fromLockedAmount)
best := findBest(routes)
if len(best) > 0 { if len(best) > 0 {
sort.Slice(best, func(i, j int) bool {
return best[i].AmountInLocked
})
rest := new(big.Int).Set(amountIn) rest := new(big.Int).Set(amountIn)
for _, path := range best { for _, path := range best {
diff := new(big.Int).Sub(rest, path.MaxAmountIn.ToInt()) diff := new(big.Int).Sub(rest, path.MaxAmountIn.ToInt())
if diff.Cmp(zero) >= 0 { if diff.Cmp(zero) >= 0 {
path.AmountIn = path.MaxAmountIn path.AmountIn = (*hexutil.Big)(path.MaxAmountIn.ToInt())
} else { } else {
path.AmountIn = (*hexutil.Big)(new(big.Int).Set(rest)) path.AmountIn = (*hexutil.Big)(new(big.Int).Set(rest))
} }
@ -279,7 +377,18 @@ func (r *Router) estimateTimes(ctx context.Context, network *params.Network, gas
return r.s.feesManager.transactionEstimatedTime(ctx, network.ChainID, gasFees.MaxFeePerGasHigh) return r.s.feesManager.transactionEstimatedTime(ctx, network.ChainID, gasFees.MaxFeePerGasHigh)
} }
func (r *Router) suggestedRoutes(ctx context.Context, sendType SendType, account common.Address, amountIn *big.Int, tokenSymbol string, disabledFromChainIDs, disabledToChaindIDs, preferedChainIDs []uint64, gasFeeMode GasFeeMode) (*SuggestedRoutes, error) { func (r *Router) suggestedRoutes(
ctx context.Context,
sendType SendType,
account common.Address,
amountIn *big.Int,
tokenSymbol string,
disabledFromChainIDs,
disabledToChaindIDs,
preferedChainIDs []uint64,
gasFeeMode GasFeeMode,
fromLockedAmount map[uint64]*hexutil.Big,
) (*SuggestedRoutes, error) {
areTestNetworksEnabled, err := r.s.accountsDB.GetTestNetworksEnabled() areTestNetworksEnabled, err := r.s.accountsDB.GetTestNetworksEnabled()
if err != nil { if err != nil {
return nil, err return nil, err
@ -335,6 +444,14 @@ func (r *Router) suggestedRoutes(ctx context.Context, sendType SendType, account
return err return err
} }
maxAmountIn := (*hexutil.Big)(balance)
if amount, ok := fromLockedAmount[network.ChainID]; ok {
if amount.ToInt().Cmp(balance) == 1 {
return errors.New("locked amount cannot be bigger than balance")
}
maxAmountIn = amount
}
nativeBalance, err := r.getBalance(ctx, network, nativeToken, account) nativeBalance, err := r.getBalance(ctx, network, nativeToken, account)
if err != nil { if err != nil {
return err return err
@ -365,7 +482,7 @@ func (r *Router) suggestedRoutes(ctx context.Context, sendType SendType, account
continue continue
} }
can, err := bridge.Can(network, dest, token, balance) can, err := bridge.Can(network, dest, token, maxAmountIn.ToInt())
if err != nil || !can { if err != nil || !can {
continue continue
} }
@ -409,7 +526,7 @@ func (r *Router) suggestedRoutes(ctx context.Context, sendType SendType, account
BridgeName: bridge.Name(), BridgeName: bridge.Name(),
From: network, From: network,
To: dest, To: dest,
MaxAmountIn: (*hexutil.Big)(balance), MaxAmountIn: maxAmountIn,
AmountIn: (*hexutil.Big)(zero), AmountIn: (*hexutil.Big)(zero),
AmountOut: (*hexutil.Big)(zero), AmountOut: (*hexutil.Big)(zero),
GasAmount: gasLimit, GasAmount: gasLimit,
@ -428,7 +545,7 @@ func (r *Router) suggestedRoutes(ctx context.Context, sendType SendType, account
group.Wait() group.Wait()
suggestedRoutes := newSuggestedRoutes(amountIn, candidates) suggestedRoutes := newSuggestedRoutes(amountIn, candidates, fromLockedAmount)
suggestedRoutes.TokenPrice = prices[tokenSymbol] suggestedRoutes.TokenPrice = prices[tokenSymbol]
suggestedRoutes.NativeChainTokenPrice = prices["ETH"] suggestedRoutes.NativeChainTokenPrice = prices["ETH"]
for _, path := range suggestedRoutes.Best { for _, path := range suggestedRoutes.Best {