Skip to content

Commit

Permalink
refactor(rfq-relayer): rebalancing with multiple tokens (#3003)
Browse files Browse the repository at this point in the history
* WIP: rebalance candidates

* WIP: impl getRebalances()

* Feat: update Rebalance() interface and execution

* Feat: add TokenName to rebalance models

* Feat: filter pending rebalances on token name

* WIP: rebalance tests

* Feat: working basic tests

* Feat: more rebalance tests

* Cleanup: logs

* Cleanup: unused ctx

* Fix: build

* Cleanup: lint

* Cleanup: add supportsRebalanceMethod() helper

* [goreleaser]

* Cleanup: lint

* Cleanup: lint

* [goreleaser]

* Feat: cleaner initial rebalance amount calculation

* Feat: add documentation for getRebalanceAmount()

* Cleanup: logs

* Cleanup: docs

* [goreleaser]

* Fix: use origin initial pct

* [goreleaser]

* Feat: more test cases

* [goreleaser]

* cleanup

* [goreleaser]

* Fix: set TokenName when storing rebalances

* [goreleaser]

* Feat: check rebalance contents in e2e test

* Feat: add tracing

* [goreleaser]

* Feat: add tracing

* [goreleaser]

* Feat: approval tracing

* [goreleaser]

* Fix: approve gateway router for scroll rebalances

* [goreleaser]

* Fix: get current block from dest when checking relay confirmations

* [goreleaser]

* Fix: add build:go directive in package.json

---------

Co-authored-by: Trajan0x <trajan0x@users.noreply.github.com>
  • Loading branch information
dwasse and trajan0x authored Sep 4, 2024
1 parent a364bbd commit dfb97a8
Show file tree
Hide file tree
Showing 14 changed files with 673 additions and 586 deletions.
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ issues:
linters:
- revive
- stylecheck
- dupl
# wrapping errors when exporting for testing is tedious
- path: export_test\.go
linters:
Expand Down
16 changes: 15 additions & 1 deletion services/rfq/e2e/rfq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,21 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() {
}
originPendingRebals, err := i.store.GetPendingRebalances(i.GetTestContext(), uint64(i.originBackend.GetChainID()))
i.NoError(err)
return len(originPendingRebals) > 0
if len(originPendingRebals) == 0 {
return false
}
expectedRebalance := reldb.Rebalance{
RebalanceID: originPendingRebals[0].RebalanceID,
OriginAmount: big.NewInt(445_000_000),
Origin: uint64(i.originBackend.GetChainID()),
Destination: uint64(i.destBackend.GetChainID()),
OriginTxHash: originPendingRebals[0].OriginTxHash,
OriginTokenAddr: originPendingRebals[0].OriginTokenAddr,
Status: reldb.RebalancePending,
TokenName: "MockMintBurnToken",
}
i.Equal(expectedRebalance, *originPendingRebals[0])
return true
})

i.Eventually(func() bool {
Expand Down
10 changes: 6 additions & 4 deletions services/rfq/relayer/inventory/circle.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,12 @@ func (c *rebalanceManagerCircleCCTP) Execute(parentCtx context.Context, rebalanc

// store the rebalance in the db
rebalanceModel := reldb.Rebalance{
Origin: uint64(rebalance.OriginMetadata.ChainID),
Destination: uint64(rebalance.DestMetadata.ChainID),
OriginAmount: rebalance.Amount,
Status: reldb.RebalanceInitiated,
Origin: uint64(rebalance.OriginMetadata.ChainID),
Destination: uint64(rebalance.DestMetadata.ChainID),
OriginAmount: rebalance.Amount,
Status: reldb.RebalanceInitiated,
OriginTokenAddr: rebalance.OriginMetadata.Addr,
TokenName: rebalance.OriginMetadata.Name,

Check warning on line 250 in services/rfq/relayer/inventory/circle.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/circle.go#L245-L250

Added lines #L245 - L250 were not covered by tests
}
err = c.db.StoreRebalance(ctx, rebalanceModel)
if err != nil {
Expand Down
11 changes: 4 additions & 7 deletions services/rfq/relayer/inventory/export_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package inventory

import (
"context"

"github.com/ethereum/go-ethereum/common"
"github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"
)

// GetRebalance is a wrapper around the internal getRebalance function.
func GetRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (*RebalanceData, error) {
return getRebalance(nil, cfg, tokens, chainID, token)
}

// GetRebalanceMetadatas is a wrapper around the internal getRebalanceMetadatas function.
func GetRebalanceMetadatas(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, tokenName string, methods []relconfig.RebalanceMethod) (originTokenData, destTokenData *TokenMetadata, method relconfig.RebalanceMethod) {
return getRebalanceMetadatas(cfg, tokens, tokenName, methods)
func GetRebalances(ctx context.Context, cfg relconfig.Config, inv map[int]map[common.Address]*TokenMetadata) (rebalances map[string]*RebalanceData, err error) {
return getRebalances(ctx, cfg, inv)
}
105 changes: 63 additions & 42 deletions services/rfq/relayer/inventory/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"math/big"
"strconv"
"sync"
"time"

Expand Down Expand Up @@ -49,9 +48,8 @@ type Manager interface {
ApproveAllTokens(ctx context.Context) error
// HasSufficientGas checks if there is sufficient gas for a given route.
HasSufficientGas(ctx context.Context, chainID int, gasValue *big.Int) (bool, error)
// Rebalance checks whether a given token should be rebalanced, and
// executes the rebalance if necessary.
Rebalance(ctx context.Context, chainID int, token common.Address) error
// Rebalance attempts any rebalances that could be executed across all supported tokens and chains.
Rebalance(ctx context.Context) error
// GetTokenMetadata gets the metadata for a token.
GetTokenMetadata(chainID int, token common.Address) (*TokenMetadata, error)
}
Expand Down Expand Up @@ -273,13 +271,10 @@ func (i *inventoryManagerImpl) Start(ctx context.Context) error {
metrics.EndSpanWithErr(span, err)
return fmt.Errorf("could not refresh balances: %w", err)
}
for chainID, chainConfig := range i.cfg.Chains {
for tokenName, tokenConfig := range chainConfig.Tokens {
err = i.Rebalance(rebalanceCtx, chainID, common.HexToAddress(tokenConfig.Address))
if err != nil {
logger.Errorf("could not rebalance %s on chain %d: %v", tokenName, chainID, err)
}
}

err = i.Rebalance(rebalanceCtx)
if err != nil {
logger.Errorf("could not rebalance: %v", err)

Check warning on line 277 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L275-L277

Added lines #L275 - L277 were not covered by tests
}

metrics.EndSpanWithErr(span, err)
Expand All @@ -299,7 +294,12 @@ const maxBatchSize = 10

// ApproveAllTokens approves all checks if allowance is set and if not approves.
// nolint:gocognit,nestif,cyclop
func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error {
func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) (err error) {
ctx, span := i.handler.Tracer().Start(ctx, "approveAllTokens")
defer func() {
metrics.EndSpanWithErr(span, err)
}()

Check warning on line 301 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L297-L301

Added lines #L297 - L301 were not covered by tests

i.mux.RLock()
defer i.mux.RUnlock()

Expand Down Expand Up @@ -338,6 +338,11 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error {

parentAddr, addrErr := i.cfg.GetL1GatewayAddress(chainID)
if addrErr == nil {
span.AddEvent(fmt.Sprintf("got l1 gateway address: %s", parentAddr.Hex()))
err = i.approve(ctx, tokenAddr, parentAddr, backendClient)
if err != nil {
return fmt.Errorf("could not approve L1GatewayRouter contract: %w", err)
}

Check warning on line 345 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L341-L345

Added lines #L341 - L345 were not covered by tests
contract, err := l1gateway.NewL1GatewayRouter(parentAddr, backendClient)
if err != nil {
return fmt.Errorf("could not get L1Gateway contract: %w", err)
Expand All @@ -346,6 +351,7 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error {
if err != nil {
return fmt.Errorf("could not get L1ERC20Gateway address: %w", err)
}
span.AddEvent(fmt.Sprintf("got l1 erc20 gateway address: %s", contractAddr.Hex()))

Check warning on line 354 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L354

Added line #L354 was not covered by tests
err = i.approve(ctx, tokenAddr, contractAddr, backendClient)
if err != nil {
return fmt.Errorf("could not approve L1ERC20Gateway contract: %w", err)
Expand All @@ -354,6 +360,11 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error {

parentAddr, addrErr = i.cfg.GetL2GatewayAddress(chainID)
if addrErr == nil {
span.AddEvent(fmt.Sprintf("got l2 gateway address: %s", parentAddr.Hex()))
err = i.approve(ctx, tokenAddr, parentAddr, backendClient)
if err != nil {
return fmt.Errorf("could not approve L2GatewayRouter contract: %w", err)
}

Check warning on line 367 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L363-L367

Added lines #L363 - L367 were not covered by tests
contract, err := l2gateway.NewL2GatewayRouter(parentAddr, backendClient)
if err != nil {
return fmt.Errorf("could not get L2Gateway contract: %w", err)
Expand All @@ -362,6 +373,7 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error {
if err != nil {
return fmt.Errorf("could not get L2ERC20Gateway address: %w", err)
}
span.AddEvent(fmt.Sprintf("got l2 erc20 gateway address: %s", contractAddr.Hex()))

Check warning on line 376 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L376

Added line #L376 was not covered by tests
err = i.approve(ctx, tokenAddr, contractAddr, backendClient)
if err != nil {
return fmt.Errorf("could not approve L2ERC20Gateway contract: %w", err)
Expand Down Expand Up @@ -458,50 +470,58 @@ func (i *inventoryManagerImpl) HasSufficientGas(parentCtx context.Context, chain
return sufficient, nil
}

// Rebalance checks whether a given token should be rebalanced, and executes the rebalance if necessary.
// Note that if there are multiple tokens whose balance is below the maintenance balance, only the lowest balance
// will be rebalanced.
//
//nolint:cyclop
func (i *inventoryManagerImpl) Rebalance(parentCtx context.Context, chainID int, token common.Address) (err error) {
// short circuit if origin does not specify a rebalance method
methodsOrigin, err := i.cfg.GetRebalanceMethods(chainID, token.Hex())
if err != nil {
return fmt.Errorf("could not get origin rebalance method: %w", err)
}
if len(methodsOrigin) == 0 {
return nil
}

ctx, span := i.handler.Tracer().Start(parentCtx, "Rebalance", trace.WithAttributes(
attribute.Int(metrics.ChainID, chainID),
attribute.String("token", token.Hex()),
))
func (i *inventoryManagerImpl) Rebalance(ctx context.Context) (err error) {
ctx, span := i.handler.Tracer().Start(ctx, "Rebalance")

Check warning on line 474 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L473-L474

Added lines #L473 - L474 were not covered by tests
defer func(err error) {
metrics.EndSpanWithErr(span, err)
}(err)

// build the rebalance action
rebalance, err := getRebalance(span, i.cfg, i.tokens, chainID, token)
rebalances, err := getRebalances(ctx, i.cfg, i.tokens)

Check warning on line 479 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L479

Added line #L479 was not covered by tests
if err != nil {
return fmt.Errorf("could not get rebalance: %w", err)
return fmt.Errorf("could not get rebalances: %w", err)

Check warning on line 481 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L481

Added line #L481 was not covered by tests
}
if rebalance == nil || rebalance.Amount.Cmp(big.NewInt(0)) <= 0 {
return nil

for tokenName, rebalance := range rebalances {
if rebalance == nil || rebalance.Amount == nil {
continue

Check warning on line 486 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L484-L486

Added lines #L484 - L486 were not covered by tests
}

err = i.tryExecuteRebalance(ctx, rebalance)
if err != nil {
return fmt.Errorf("could not execute rebalance for token %s: %w", tokenName, err)
}

Check warning on line 492 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L489-L492

Added lines #L489 - L492 were not covered by tests
}
span.SetAttributes(
attribute.String("rebalance_origin", strconv.Itoa(rebalance.OriginMetadata.ChainID)),
attribute.String("rebalance_dest", strconv.Itoa(rebalance.DestMetadata.ChainID)),

return nil

Check warning on line 495 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L495

Added line #L495 was not covered by tests
}

func (i *inventoryManagerImpl) tryExecuteRebalance(ctx context.Context, rebalance *RebalanceData) (err error) {
ctx, span := i.handler.Tracer().Start(ctx, "tryExecuteRebalance", trace.WithAttributes(
attribute.Int("origin", rebalance.OriginMetadata.ChainID),
attribute.Int("dest", rebalance.DestMetadata.ChainID),
attribute.String("origin_token", rebalance.OriginMetadata.Addr.Hex()),
attribute.String("dest_token", rebalance.DestMetadata.Addr.Hex()),
attribute.String("origin_balance", rebalance.OriginMetadata.Balance.String()),
attribute.String("dest_balance", rebalance.DestMetadata.Balance.String()),

Check warning on line 505 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L498-L505

Added lines #L498 - L505 were not covered by tests
attribute.String("rebalance_amount", rebalance.Amount.String()),
attribute.String("rebalance_method", rebalance.Method.String()),
)
attribute.String("token_name", rebalance.OriginMetadata.Name),
))
defer func(err error) {
metrics.EndSpanWithErr(span, err)
}(err)

Check warning on line 511 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L507-L511

Added lines #L507 - L511 were not covered by tests

// make sure there are no pending rebalances that touch the given path
pendingRebalances, err := i.db.GetPendingRebalances(ctx, uint64(rebalance.OriginMetadata.ChainID), uint64(rebalance.DestMetadata.ChainID))
if err != nil {
return fmt.Errorf("could not check pending rebalance: %w", err)
}
pending := len(pendingRebalances) > 0
var pending bool
for _, pendingRebalance := range pendingRebalances {
if pendingRebalance.TokenName == rebalance.OriginMetadata.Name {
pending = true
break

Check warning on line 522 in services/rfq/relayer/inventory/manager.go

View check run for this annotation

Codecov / codecov/patch

services/rfq/relayer/inventory/manager.go#L518-L522

Added lines #L518 - L522 were not covered by tests
}
}
span.SetAttributes(attribute.Bool("rebalance_pending", pending))
if pending {
return nil
Expand All @@ -517,6 +537,7 @@ func (i *inventoryManagerImpl) Rebalance(parentCtx context.Context, chainID int,
if err != nil {
return fmt.Errorf("could not execute rebalance: %w", err)
}

return nil
}

Expand Down
Loading

0 comments on commit dfb97a8

Please sign in to comment.