Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LM-172 Load liquidity targets for plugin and rebalalgo #1133

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/services/ocr2/plugins/liquiditymanager/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (p PluginFactory) buildRebalancer() (rebalalgo.RebalancingAlgo, error) {
case models.RebalancerTypeMinLiquidity:
return rebalalgo.NewMinLiquidityRebalancer(p.lggr), nil
case models.RebalancerTypeTargetAndMin:
return rebalalgo.NewTargetMinBalancer(p.lggr), nil
return rebalalgo.NewTargetMinBalancer(p.lggr, p.config), nil
default:
return nil, fmt.Errorf("invalid rebalancer type %s", p.config.RebalancerConfig.Type)
}
Expand Down
2 changes: 2 additions & 0 deletions core/services/ocr2/plugins/liquiditymanager/graph/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type GraphWriter interface {
Add(from, to Data) error
// SetLiquidity sets the liquidity of the provided network.
SetLiquidity(n models.NetworkSelector, liquidity *big.Int) bool
// SetTargetLiquidity sets the target liquidity of the provided network.
SetTargetLiquidity(n models.NetworkSelector, liquidity *big.Int) bool
}

// NodeReader provides read access to the data saved in the graph nodes.
Expand Down
21 changes: 21 additions & 0 deletions core/services/ocr2/plugins/liquiditymanager/graph/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ func (g *liquidityGraph) SetLiquidity(n models.NetworkSelector, liquidity *big.I
return true
}

func (g *liquidityGraph) SetTargetLiquidity(n models.NetworkSelector, target *big.Int) bool {
g.lock.Lock()
defer g.lock.Unlock()

if !g.hasNetwork(n) {
return false
}

prev := g.data[n]
g.data[n] = Data{
Liquidity: prev.Liquidity,
TokenAddress: prev.TokenAddress,
LiquidityManagerAddress: prev.LiquidityManagerAddress,
ConfigDigest: prev.ConfigDigest,
NetworkSelector: prev.NetworkSelector,
MinimumLiquidity: prev.MinimumLiquidity,
TargetLiquidity: target,
}
return true
}

func (g *liquidityGraph) AddNetwork(n models.NetworkSelector, data Data) bool {
g.lock.Lock()
defer g.lock.Unlock()
Expand Down
11 changes: 5 additions & 6 deletions core/services/ocr2/plugins/liquiditymanager/models/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ type PluginConfig struct {
}

type RebalancerConfig struct {
Type string `json:"type"`
}

type NetworkTarget struct {
Network NetworkSelector `json:"network,string"`
Target *big.Int `json:"target"`
Type string `json:"type"`
DefaultTarget *big.Int `json:"defaultTarget"`
// NetworkTargetOverrides is a map of NetworkSelector to big Int amounts
NetworkTargetOverrides map[NetworkSelector]*big.Int `json:"networkTargetOverrides"`
}

func ValidateRebalancerConfig(config RebalancerConfig) error {
Expand All @@ -47,6 +45,7 @@ var (
AllRebalancerTypes = []string{
RebalancerTypePingPong,
RebalancerTypeMinLiquidity,
RebalancerTypeTargetAndMin,
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ import (

// TargetMinBalancer tries to reach balance using a target and minimum liquidity that is configured on each network.
type TargetMinBalancer struct {
lggr logger.Logger
lggr logger.Logger
config models.PluginConfig
}

func NewTargetMinBalancer(lggr logger.Logger) *TargetMinBalancer {
func NewTargetMinBalancer(lggr logger.Logger, config models.PluginConfig) *TargetMinBalancer {
return &TargetMinBalancer{
lggr: lggr.With("service", "TargetMinBalancer"),
lggr: lggr.With("service", "TargetMinBalancer"),
config: config,
}
}

type determineTransfersFunc func(graphLater graph.Graph, targetNetwork models.NetworkSelector, networkFunds map[models.NetworkSelector]*Funds) ([]models.ProposedTransfer, error)
type determineTransfersFunc func(graphFuture graph.Graph, targetNetwork models.NetworkSelector, networkFunds map[models.NetworkSelector]*Funds) ([]models.ProposedTransfer, error)

func (r *TargetMinBalancer) ComputeTransfersToBalance(graphNow graph.Graph, nonExecutedTransfers []UnexecutedTransfer) ([]models.ProposedTransfer, error) {
nonExecutedTransfers = filterUnexecutedTransfers(nonExecutedTransfers)
Expand Down Expand Up @@ -59,13 +61,13 @@ func (r *TargetMinBalancer) ComputeTransfersToBalance(graphNow graph.Graph, nonE

func (r *TargetMinBalancer) rebalancingRound(graphNow graph.Graph, nonExecutedTransfers []UnexecutedTransfer, transfersFunc determineTransfersFunc) ([]models.ProposedTransfer, error) {
var err error
graphLater, err := getExpectedGraph(graphNow, nonExecutedTransfers)
graphFuture, err := r.getExpectedGraph(graphNow, nonExecutedTransfers)
if err != nil {
return nil, fmt.Errorf("get expected graph: %w", err)
}

r.lggr.Debugf("finding networks that require funding")
networksRequiringFunding, networkFunds, err := r.findNetworksRequiringFunding(graphNow, graphLater)
networksRequiringFunding, networkFunds, err := r.findNetworksRequiringFunding(graphNow, graphFuture)
if err != nil {
return nil, fmt.Errorf("find networks that require funding: %w", err)
}
Expand All @@ -75,17 +77,17 @@ func (r *TargetMinBalancer) rebalancingRound(graphNow graph.Graph, nonExecutedTr
proposedTransfers := make([]models.ProposedTransfer, 0)
for _, net := range networksRequiringFunding {
r.lggr.Debugf("finding transfers for network %v", net)
potentialTransfers, err1 := transfersFunc(graphLater, net, networkFunds)
potentialTransfers, err1 := transfersFunc(graphFuture, net, networkFunds)
if err1 != nil {
return nil, fmt.Errorf("finding transfers for network %v: %w", net, err1)
}

dataLater, err2 := graphLater.GetData(net)
dataFuture, err2 := graphFuture.GetData(net)
if err2 != nil {
return nil, fmt.Errorf("get data later of net %v: %w", net, err2)
return nil, fmt.Errorf("get future data of net %v: %w", net, err2)
}
liqDiffLater := new(big.Int).Sub(dataLater.TargetLiquidity, dataLater.Liquidity)
netProposedTransfers, err3 := r.applyProposedTransfers(graphLater, potentialTransfers, liqDiffLater)
liqDiffFuture := new(big.Int).Sub(dataFuture.TargetLiquidity, dataFuture.Liquidity)
netProposedTransfers, err3 := r.applyProposedTransfers(graphFuture, potentialTransfers, liqDiffFuture)
if err3 != nil {
return nil, fmt.Errorf("applying transfers: %w", err3)
}
Expand All @@ -95,62 +97,101 @@ func (r *TargetMinBalancer) rebalancingRound(graphNow graph.Graph, nonExecutedTr
return proposedTransfers, nil
}

func (r *TargetMinBalancer) findNetworksRequiringFunding(graphNow, graphLater graph.Graph) ([]models.NetworkSelector, map[models.NetworkSelector]*Funds, error) {
mapNetworkFunds := make(map[models.NetworkSelector]*Funds)
liqDiffsLater := make(map[models.NetworkSelector]*big.Int)
// getExpectedGraph returns a copy of the graph instance with all the non-executed transfers applied and targets set for each network.
func (r *TargetMinBalancer) getExpectedGraph(g graph.Graph, nonExecutedTransfers []UnexecutedTransfer) (graph.Graph, error) {
expG := g.Clone()

for _, tr := range nonExecutedTransfers {
liqTo, err := expG.GetLiquidity(tr.ToNetwork())
if err != nil {
return nil, err
}
expG.SetLiquidity(tr.ToNetwork(), big.NewInt(0).Add(liqTo, tr.TransferAmount()))

// we only subtract from the sender if the transfer is still in progress, otherwise the source value would have already been updated
switch tr.TransferStatus() {
case models.TransferStatusProposed, models.TransferStatusInflight:
liqFrom, err := expG.GetLiquidity(tr.FromNetwork())
if err != nil {
return nil, err
}
expG.SetLiquidity(tr.FromNetwork(), big.NewInt(0).Sub(liqFrom, tr.TransferAmount()))
}
}

//TODO: LM-23 Create minTokenTransfer config to filter-out small rebalance txs
// check that the transfer is not tiny, we should only transfer if it is significant. What is too tiny?
// we could prevent this by only making a network requiring funding if its below X% of the target
for _, selector := range expG.GetNetworks() {
target := r.config.RebalancerConfig.DefaultTarget
override, ok := r.config.RebalancerConfig.NetworkTargetOverrides[selector]
if ok {
target = override
}
expG.SetTargetLiquidity(selector, target)
}

return expG, nil
}

func (r *TargetMinBalancer) findNetworksRequiringFunding(graphNow, graphFuture graph.Graph) ([]models.NetworkSelector, map[models.NetworkSelector]*Funds, error) {
mapNetworkFunds := make(map[models.NetworkSelector]*Funds)
liqDiffsFuture := make(map[models.NetworkSelector]*big.Int)

res := make([]models.NetworkSelector, 0)
for _, net := range graphNow.GetNetworks() {
//use min here for transferable. because we don't know when the transfers will complete and want to avoid issues
transferableAmount, ataErr := availableTransferableAmount(graphNow, graphLater, net)
transferableAmount, ataErr := availableTransferableAmount(graphNow, graphFuture, net)
if ataErr != nil {
return nil, nil, fmt.Errorf("getting available transferrable amount for net %d: %v", net, ataErr)
}
dataLater, err := graphLater.GetData(net)
dataFuture, err := graphFuture.GetData(net)
if err != nil {
return nil, nil, fmt.Errorf("get data later of net %v: %w", net, err)
return nil, nil, fmt.Errorf("get future data of net %v: %w", net, err)
}
liqDiffLater := new(big.Int).Sub(dataLater.TargetLiquidity, dataLater.Liquidity)
liqDiffFuture := new(big.Int).Sub(dataFuture.TargetLiquidity, dataFuture.Liquidity)
mapNetworkFunds[net] = &Funds{
AvailableAmount: transferableAmount,
}
if liqDiffLater.Cmp(big.NewInt(0)) <= 0 {
if liqDiffFuture.Cmp(big.NewInt(0)) <= 0 {
continue
}

// only if we are below 5% else it's too little to worry about now
fivePercent := big.NewInt(5)
hundred := big.NewInt(100)
value := new(big.Int).Mul(dataFuture.TargetLiquidity, fivePercent)
value.Div(value, hundred)
if liqDiffFuture.Cmp(value) < 0 {
continue
}
liqDiffsLater[net] = liqDiffLater
liqDiffsFuture[net] = liqDiffFuture
res = append(res, net)
}

sort.Slice(res, func(i, j int) bool { return liqDiffsLater[res[i]].Cmp(liqDiffsLater[res[j]]) > 0 })
sort.Slice(res, func(i, j int) bool { return liqDiffsFuture[res[i]].Cmp(liqDiffsFuture[res[j]]) > 0 })
return res, mapNetworkFunds, nil
}

func (r *TargetMinBalancer) oneHopTransfers(graphLater graph.Graph, targetNetwork models.NetworkSelector, networkFunds map[models.NetworkSelector]*Funds) ([]models.ProposedTransfer, error) {
func (r *TargetMinBalancer) oneHopTransfers(graphFuture graph.Graph, targetNetwork models.NetworkSelector, networkFunds map[models.NetworkSelector]*Funds) ([]models.ProposedTransfer, error) {
zero := big.NewInt(0)
potentialTransfers := make([]models.ProposedTransfer, 0)

neighbors, exist := graphLater.GetNeighbors(targetNetwork, false)
neighbors, exist := graphFuture.GetNeighbors(targetNetwork, false)
if !exist {
r.lggr.Debugf("no neighbors found for %v", targetNetwork)
return nil, nil
}
targetLater, err := graphLater.GetData(targetNetwork)
targetFuture, err := graphFuture.GetData(targetNetwork)
if err != nil {
return nil, fmt.Errorf("get data later of net %v: %w", targetNetwork, err)
return nil, fmt.Errorf("get data Future of net %v: %w", targetNetwork, err)
}

for _, source := range neighbors {
transferAmount := new(big.Int).Sub(targetLater.TargetLiquidity, targetLater.Liquidity)
transferAmount := new(big.Int).Sub(targetFuture.TargetLiquidity, targetFuture.Liquidity)
r.lggr.Debugf("checking transfer from %v to %v for amount %v", source, targetNetwork, transferAmount)

//source network available transferable amount
srcData, dErr := graphLater.GetData(source)
srcData, dErr := graphFuture.GetData(source)
if dErr != nil {
return nil, fmt.Errorf("error during GetData for %v in graphLater: %v", source, dErr)
return nil, fmt.Errorf("error during GetData for %v in graphFuture: %v", source, dErr)
}
srcAvailableAmount := new(big.Int).Sub(srcData.Liquidity, srcData.MinimumLiquidity)
srcAmountToTarget := new(big.Int).Sub(srcData.Liquidity, srcData.TargetLiquidity)
Expand Down Expand Up @@ -180,40 +221,40 @@ func (r *TargetMinBalancer) oneHopTransfers(graphLater graph.Graph, targetNetwor
}

// twoHopTransfers finds networks that can increase liquidity of the target network with an intermediate network.
func (r *TargetMinBalancer) twoHopTransfers(graphLater graph.Graph, targetNetwork models.NetworkSelector, networkFunds map[models.NetworkSelector]*Funds) ([]models.ProposedTransfer, error) {
func (r *TargetMinBalancer) twoHopTransfers(graphFuture graph.Graph, targetNetwork models.NetworkSelector, networkFunds map[models.NetworkSelector]*Funds) ([]models.ProposedTransfer, error) {
zero := big.NewInt(0)
iterator := func(nodes ...graph.Data) bool { return true }
potentialTransfers := make([]models.ProposedTransfer, 0)

for _, source := range graphLater.GetNetworks() {
for _, source := range graphFuture.GetNetworks() {
if source == targetNetwork {
continue
}
path := graphLater.FindPath(source, targetNetwork, 2, iterator)
path := graphFuture.FindPath(source, targetNetwork, 2, iterator)
if len(path) != 2 {
continue
}
middle := path[0]

targetData, err := graphLater.GetData(targetNetwork)
targetData, err := graphFuture.GetData(targetNetwork)
if err != nil {
return nil, fmt.Errorf("error during GetData for %v in graphLater: %v", targetNetwork, err)
return nil, fmt.Errorf("error during GetData for %v in graphFuture: %v", targetNetwork, err)
}
transferAmount := new(big.Int).Sub(targetData.TargetLiquidity, targetData.Liquidity)
r.lggr.Debugf("checking transfer from %v -> %v -> %v for amount %v", source, middle, targetNetwork, transferAmount)

//source network available transferable amount
srcData, dErr := graphLater.GetData(source)
srcData, dErr := graphFuture.GetData(source)
if dErr != nil {
return nil, fmt.Errorf("error during GetData for %v in graphLater: %v", source, dErr)
return nil, fmt.Errorf("error during GetData for %v in graphFuture: %v", source, dErr)
}
srcAvailableAmount := new(big.Int).Sub(srcData.Liquidity, srcData.MinimumLiquidity)
srcAmountToTarget := new(big.Int).Sub(srcData.Liquidity, srcData.TargetLiquidity)

//middle network available transferable amount
middleData, dErr := graphLater.GetData(middle)
middleData, dErr := graphFuture.GetData(middle)
if dErr != nil {
return nil, fmt.Errorf("error during GetData for %v in graphLater: %v", middle, dErr)
return nil, fmt.Errorf("error during GetData for %v in graphFuture: %v", middle, dErr)
}
middleAvailableAmount := new(big.Int).Sub(middleData.Liquidity, middleData.MinimumLiquidity)
middleAmountToTarget := new(big.Int).Sub(middleData.Liquidity, middleData.TargetLiquidity)
Expand Down Expand Up @@ -251,7 +292,7 @@ func (r *TargetMinBalancer) twoHopTransfers(graphLater graph.Graph, targetNetwor
// applyProposedTransfers applies the proposed transfers to the graph.
// increments the raised funds and gives a refund to the sender if more funds have been raised than the required amount.
// It updates the liquidity of the sender and receiver networks in the graph. It stops further transfers if all funds have been raised.
func (r *TargetMinBalancer) applyProposedTransfers(graphLater graph.Graph, potentialTransfers []models.ProposedTransfer, requiredAmount *big.Int) ([]models.ProposedTransfer, error) {
func (r *TargetMinBalancer) applyProposedTransfers(graphFuture graph.Graph, potentialTransfers []models.ProposedTransfer, requiredAmount *big.Int) ([]models.ProposedTransfer, error) {
// sort by amount,sender,receiver
sort.Slice(potentialTransfers, func(i, j int) bool {
if potentialTransfers[i].Amount.Cmp(potentialTransfers[j].Amount) == 0 {
Expand All @@ -272,7 +313,7 @@ func (r *TargetMinBalancer) applyProposedTransfers(graphLater graph.Graph, poten
continue
}

senderData, err := graphLater.GetData(d.From)
senderData, err := graphFuture.GetData(d.From)
if err != nil {
return nil, fmt.Errorf("get liquidity of sender %v: %w", d.From, err)
}
Expand All @@ -298,17 +339,17 @@ func (r *TargetMinBalancer) applyProposedTransfers(graphLater graph.Graph, poten
r.lggr.Debugf("applying transfer: %v", d)
proposedTransfers = append(proposedTransfers, models.ProposedTransfer{From: d.From, To: d.To, Amount: d.Amount})

liqBefore, err := graphLater.GetLiquidity(d.To)
liqBefore, err := graphFuture.GetLiquidity(d.To)
if err != nil {
return nil, fmt.Errorf("get liquidity of transfer receiver %v: %w", d.To, err)
}
graphLater.SetLiquidity(d.To, big.NewInt(0).Add(liqBefore, d.Amount.ToInt()))
graphFuture.SetLiquidity(d.To, big.NewInt(0).Add(liqBefore, d.Amount.ToInt()))

liqBefore, err = graphLater.GetLiquidity(d.From)
liqBefore, err = graphFuture.GetLiquidity(d.From)
if err != nil {
return nil, fmt.Errorf("get liquidity of sender %v: %w", d.From, err)
}
graphLater.SetLiquidity(d.From, big.NewInt(0).Sub(liqBefore, d.Amount.ToInt()))
graphFuture.SetLiquidity(d.From, big.NewInt(0).Sub(liqBefore, d.Amount.ToInt()))

if fundsRaised.Cmp(requiredAmount) >= 0 {
r.lggr.Debugf("all funds raised skipping further transfers")
Expand Down
Loading
Loading