Skip to content

Commit

Permalink
LM-172 Load liquidity targets for plugin and rebalalgo (#1133)
Browse files Browse the repository at this point in the history
and LM-23 Create minTokenTransfer config to filter-out small rebalance
txs
  • Loading branch information
cmalec authored Jul 3, 2024
1 parent dacf09f commit 687045c
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 61 deletions.
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

0 comments on commit 687045c

Please sign in to comment.