synapsecns/sanguine

View on GitHub
services/rfq/api/db/api_db.go

Summary

Maintainability
A
0 mins
Test Coverage
// Package db provides the database interfaces and types for the RFQ API.
package db

import (
    "context"
    "database/sql/driver"
    "fmt"
    "time"

    "github.com/shopspring/decimal"
    "github.com/synapsecns/sanguine/core/dbcommon"
    "github.com/synapsecns/sanguine/services/rfq/api/model"
)

// Quote is the database model for a quote.
type Quote struct {
    // OriginChainID is the chain which the relayer is willing to relay from
    OriginChainID uint64 `gorm:"column:origin_chain_id;index;primaryKey"`
    // OriginTokenAddr is the token address for which the relayer willing to relay from
    OriginTokenAddr string `gorm:"column:origin_token;index;primaryKey"`
    // DestChainID is the chain which the relayer is willing to relay to
    DestChainID uint64 `gorm:"column:dest_chain_id;index;primaryKey"`
    // DestToken is the token address for which the relayer willing to relay to
    DestTokenAddr string `gorm:"column:dest_token;index;primaryKey"`
    // DestAmount is the max amount of liquidity which exists for a given destination token, provided in the destination token decimals
    DestAmount decimal.Decimal `gorm:"column:dest_amount"`
    // MaxOriginAmount is the maximum amount of origin tokens bridgeable
    MaxOriginAmount decimal.Decimal `gorm:"column:max_origin_amount"`
    // FixedFee is the fixed fee for the quote, provided in the destination token terms
    FixedFee decimal.Decimal `gorm:"column:fixed_fee"`
    // Address of the relayer providing the quote
    RelayerAddr string `gorm:"column:relayer_address;primaryKey"`
    // OriginFastBridgeAddress is the address of the fast bridge contract on the origin chain
    OriginFastBridgeAddress string `gorm:"column:origin_fast_bridge_address"`
    // DestFastBridgeAddress is the address of the fast bridge contract on the destination chain
    DestFastBridgeAddress string `gorm:"column:dest_fast_bridge_address"`
    // UpdatedAt is the time that the quote was last upserted
    UpdatedAt time.Time
}

// ActiveQuoteRequestStatus is the status of a quote request in the db.
// This is the primary mechanism for moving data through the app.
//
// TODO: consider making this an interface and exporting that.
//
// EXTREMELY IMPORTANT: DO NOT ADD NEW VALUES TO THIS ENUM UNLESS THEY ARE AT THE END.
//
//go:generate go run golang.org/x/tools/cmd/stringer -type=ActiveQuoteRequestStatus
type ActiveQuoteRequestStatus uint8

const (
    // Received means the quote request has been received by the server.
    Received ActiveQuoteRequestStatus = iota + 1
    // Pending means the quote request is pending awaiting relayer responses.
    Pending
    // Expired means the quote request has expired without any valid responses.
    Expired
    // Closed means the quote request has been fulfilled.
    Closed
)

// Int returns the int value of the quote request status.
func (q ActiveQuoteRequestStatus) Int() uint8 {
    return uint8(q)
}

// GormDataType implements the gorm common interface for enums.
func (q ActiveQuoteRequestStatus) GormDataType() string {
    return dbcommon.EnumDataType
}

// Scan implements the gorm common interface for enums.
func (q *ActiveQuoteRequestStatus) Scan(src any) error {
    res, err := dbcommon.EnumScan(src)
    if err != nil {
        return fmt.Errorf("could not scan %w", err)
    }
    newStatus := ActiveQuoteRequestStatus(res)
    *q = newStatus
    return nil
}

// Value implements the gorm common interface for enums.
func (q ActiveQuoteRequestStatus) Value() (driver.Value, error) {
    // nolint: wrapcheck
    return dbcommon.EnumValue(q)
}

var _ dbcommon.Enum = (*ActiveQuoteRequestStatus)(nil)

// ActiveQuoteResponseStatus is the status of a quote request in the db.
// This is the primary mechanism for moving data through the app.
//
// TODO: consider making this an interface and exporting that.
//
// EXTREMELY IMPORTANT: DO NOT ADD NEW VALUES TO THIS ENUM UNLESS THEY ARE AT THE END.
//
//go:generate go run golang.org/x/tools/cmd/stringer -type=ActiveQuoteResponseStatus
type ActiveQuoteResponseStatus uint8

const (
    // Considered means the quote request was considered by the relayer, but was not ultimately the fulfilling response.
    Considered ActiveQuoteResponseStatus = iota + 1
    // Returned means the quote request was returned by the relayer to the user.
    Returned
    // PastExpiration means the quote request was received, but past the expiration window.
    PastExpiration
    // Malformed means that the quote request was malformed.
    Malformed
    // Duplicate means that the quote request was a duplicate.
    Duplicate
)

// Int returns the int value of the quote request status.
func (q ActiveQuoteResponseStatus) Int() uint8 {
    return uint8(q)
}

// GormDataType implements the gorm common interface for enums.
func (q ActiveQuoteResponseStatus) GormDataType() string {
    return dbcommon.EnumDataType
}

// Scan implements the gorm common interface for enums.
func (q *ActiveQuoteResponseStatus) Scan(src any) error {
    res, err := dbcommon.EnumScan(src)
    if err != nil {
        return fmt.Errorf("could not scan %w", err)
    }
    newStatus := ActiveQuoteResponseStatus(res)
    *q = newStatus
    return nil
}

// Value implements the gorm common interface for enums.
func (q ActiveQuoteResponseStatus) Value() (driver.Value, error) {
    // nolint: wrapcheck
    return dbcommon.EnumValue(q)
}

var _ dbcommon.Enum = (*ActiveQuoteResponseStatus)(nil)

// ActiveQuoteRequest is the database model for an active quote request.
type ActiveQuoteRequest struct {
    RequestID         string                   `gorm:"column:request_id;primaryKey"`
    IntegratorID      string                   `gorm:"column:integrator_id"`
    UserAddress       string                   `gorm:"column:user_address"`
    OriginChainID     uint64                   `gorm:"column:origin_chain_id"`
    OriginTokenAddr   string                   `gorm:"column:origin_token"`
    DestChainID       uint64                   `gorm:"column:dest_chain_id"`
    DestTokenAddr     string                   `gorm:"column:dest_token"`
    OriginAmountExact decimal.Decimal          `gorm:"column:origin_amount_exact"`
    ExpirationWindow  time.Duration            `gorm:"column:expiration_window"`
    CreatedAt         time.Time                `gorm:"column:created_at"`
    Status            ActiveQuoteRequestStatus `gorm:"column:status"`
    ClosedAt          *time.Time               `gorm:"column:closed_at"`
    ClosedQuoteID     *string                  `gorm:"column:closed_quote_id"`
}

// FromUserRequest converts a model.PutRFQRequest to an ActiveQuoteRequest.
func FromUserRequest(req *model.PutRFQRequest, requestID string) (*ActiveQuoteRequest, error) {
    originAmountExact, err := decimal.NewFromString(req.Data.OriginAmountExact)
    if err != nil {
        return nil, fmt.Errorf("invalid origin amount: %w", err)
    }
    return &ActiveQuoteRequest{
        RequestID:         requestID,
        IntegratorID:      req.IntegratorID,
        UserAddress:       req.UserAddress,
        OriginChainID:     uint64(req.Data.OriginChainID), //nolint:gosec
        OriginTokenAddr:   req.Data.OriginTokenAddr,
        DestChainID:       uint64(req.Data.DestChainID), //nolint:gosec
        DestTokenAddr:     req.Data.DestTokenAddr,
        OriginAmountExact: originAmountExact,
        ExpirationWindow:  time.Duration(req.Data.ExpirationWindow),
        CreatedAt:         time.Now(),
        Status:            Received,
    }, nil
}

// ActiveQuoteResponse is the database model for an active quote response.
type ActiveQuoteResponse struct {
    RequestID   string                    `gorm:"column:request_id"`
    QuoteID     string                    `gorm:"column:quote_id;primaryKey"`
    DestAmount  decimal.Decimal           `gorm:"column:dest_amount"`
    RelayerAddr string                    `gorm:"column:relayer_address"`
    UpdatedAt   time.Time                 `gorm:"column:updated_at"`
    Status      ActiveQuoteResponseStatus `gorm:"column:status"`
}

// FromRelayerResponse converts a model.WsRFQResponse to an ActiveQuoteResponse.
func FromRelayerResponse(resp *model.WsRFQResponse, relayerAddr string, status ActiveQuoteResponseStatus) (*ActiveQuoteResponse, error) {
    destAmount, err := decimal.NewFromString(resp.DestAmount)
    if err != nil {
        return nil, fmt.Errorf("invalid dest amount: %w", err)
    }
    return &ActiveQuoteResponse{
        RequestID:   resp.RequestID,
        QuoteID:     resp.QuoteID,
        DestAmount:  destAmount,
        RelayerAddr: relayerAddr,
        UpdatedAt:   resp.UpdatedAt,
        Status:      status,
    }, nil
}

// APIDBReader is the interface for reading from the database.
type APIDBReader interface {
    // GetQuotesByDestChainAndToken gets quotes from the database by destination chain and token.
    GetQuotesByDestChainAndToken(ctx context.Context, destChainID uint64, destTokenAddr string) ([]*Quote, error)
    // GetQuotesByOriginAndDestination gets quotes from the database by origin and destination.
    GetQuotesByOriginAndDestination(ctx context.Context, originChainID uint64, originTokenAddr string, destChainID uint64, destTokenAddr string) ([]*Quote, error)
    // GetQuotesByRelayerAddress gets quotes from the database by relayer address.
    GetQuotesByRelayerAddress(ctx context.Context, relayerAddress string) ([]*Quote, error)
    // GetActiveQuoteRequests gets active quote requests from the database.
    GetActiveQuoteRequests(ctx context.Context, matchStatuses ...ActiveQuoteRequestStatus) ([]*ActiveQuoteRequest, error)
    // GetAllQuotes retrieves all quotes from the database.
    GetAllQuotes(ctx context.Context) ([]*Quote, error)
}

// APIDBWriter is the interface for writing to the database.
type APIDBWriter interface {
    // UpsertQuote upserts a quote in the database.
    UpsertQuote(ctx context.Context, quote *Quote) error
    // UpsertQuotes upserts multiple quotes in the database.
    UpsertQuotes(ctx context.Context, quotes []*Quote) error
    // InsertActiveQuoteRequest inserts an active quote request into the database.
    InsertActiveQuoteRequest(ctx context.Context, req *model.PutRFQRequest, requestID string) error
    // UpdateActiveQuoteRequestStatus updates the status of an active quote request in the database.
    UpdateActiveQuoteRequestStatus(ctx context.Context, requestID string, quoteID *string, status ActiveQuoteRequestStatus) error
    // InsertActiveQuoteResponse inserts an active quote response into the database.
    InsertActiveQuoteResponse(ctx context.Context, resp *model.WsRFQResponse, relayerAddr string, status ActiveQuoteResponseStatus) error
    // UpdateActiveQuoteResponseStatus updates the status of an active quote response in the database.
    UpdateActiveQuoteResponseStatus(ctx context.Context, quoteID string, status ActiveQuoteResponseStatus) error
}

// APIDB is the interface for the database service.
type APIDB interface {
    APIDBReader
    APIDBWriter
}