synapsecns/sanguine

View on GitHub
services/rfq/relayer/reldb/base/model.go

Summary

Maintainability
A
35 mins
Test Coverage
package base

import (
    "database/sql"
    "errors"
    "fmt"
    "math"
    "math/big"
    "time"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/shopspring/decimal"
    "github.com/synapsecns/sanguine/core/dbcommon"
    "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
    "github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
)

func init() {
    namer := dbcommon.NewNamer(GetAllModels())
    statusFieldName = namer.GetConsistentName("Status")
    transactionIDFieldName = namer.GetConsistentName("TransactionID")
    originTxHashFieldName = namer.GetConsistentName("OriginTxHash")
    destTxHashFieldName = namer.GetConsistentName("DestTxHash")
    rebalanceIDFieldName = namer.GetConsistentName("RebalanceID")
    relayNonceFieldName = namer.GetConsistentName("RelayNonce")
}

var (
    // statusFieldName is the status field name.
    statusFieldName string
    // transactionIDFieldName is the transactions id field name.
    transactionIDFieldName string
    // originTxHashFieldName is the origin tx hash field name.
    originTxHashFieldName string
    // destTxHashFieldName is the dest tx hash field name.
    destTxHashFieldName string
    // rebalanceIDFieldName is the rebalances id field name.
    rebalanceIDFieldName string
    // relayNonceFieldName is the relay nonce field name.
    relayNonceFieldName string
)

// RequestForQuote is the primary event model.
type RequestForQuote struct {
    // CreatedAt is the creation time
    CreatedAt time.Time
    // UpdatedAt is the update time
    UpdatedAt time.Time
    // TransactionID is the transaction id of the event
    TransactionID string `gorm:"column:transaction_id;primaryKey"`
    // OriginChainID is the origin chain for the transactions
    OriginChainID uint32
    // DestChainID is the destination chain for the tx
    DestChainID uint32
    // OriginSender is the original sender
    OriginSender string
    // DestRecipient is the recipient of the destination tx
    DestRecipient string
    // OriginToken is the origin token address
    OriginToken string
    // OriginAmountOriginal is the origin amount used for preicison
    OriginAmountOriginal string
    // OriginTokenDecimals is the origin token decimals
    OriginTokenDecimals uint8
    // DestToken is the destination token address
    DestToken string
    // DestTokenDecimals is the destination token decimal count
    DestTokenDecimals uint8
    // OriginAmount is the origin amount stored for sorting.
    // This is not the source of truth, but is approximate
    OriginAmount decimal.Decimal `gorm:"index"`
    // OriginTxHash is the origin tx hash
    OriginTxHash sql.NullString
    // DestAmountOriginal is the original amount used for precision
    DestAmountOriginal string
    // DestAmountOriginal is the original destination amount
    DestAmount decimal.Decimal `gorm:"index"`
    // DestTxHash is the destination tx hash
    DestTxHash sql.NullString
    // Deadline is the deadline for the relay
    Deadline time.Time `gorm:"index"`
    // OriginNonce is the nonce on the origin chain in the app.
    // this is not effected by the message.sender nonce.
    OriginNonce int `gorm:"index"`
    // Status is the current status of the event
    Status reldb.QuoteRequestStatus `gorm:"column:status;index:idx_rfq_status_name"`
    // BlockNumber is the block number of the event
    BlockNumber uint64
    // RawRequest is the raw request, hex encoded.
    RawRequest string
    // SendChainGas is true if the chain should send gas
    SendChainGas bool
    // RelayNonce is the nonce for the relay transaction.
    RelayNonce uint64
}

// Rebalance is the event model for a rebalance action.
type Rebalance struct {
    CreatedAt       time.Time
    UpdatedAt       time.Time
    RebalanceID     sql.NullString
    Origin          uint64
    Destination     uint64
    OriginAmount    string
    OriginTokenAddr sql.NullString
    Status          reldb.RebalanceStatus
    OriginTxHash    sql.NullString
    DestTxHash      sql.NullString
    TokenName       string
}

// FromQuoteRequest converts a quote request to an object that can be stored in the db.
// TODO: add validation for deadline > uint64
// TODO: roundtripper test.
func FromQuoteRequest(request reldb.QuoteRequest) RequestForQuote {
    return RequestForQuote{
        TransactionID:        hexutil.Encode(request.TransactionID[:]),
        OriginChainID:        request.Transaction.OriginChainId,
        DestChainID:          request.Transaction.DestChainId,
        OriginSender:         request.Transaction.OriginSender.String(),
        DestRecipient:        request.Transaction.DestRecipient.String(),
        OriginToken:          request.Transaction.OriginToken.String(),
        OriginTokenDecimals:  request.OriginTokenDecimals,
        OriginTxHash:         stringToNullString(request.OriginTxHash.String()),
        RawRequest:           hexutil.Encode(request.RawRequest),
        SendChainGas:         request.Transaction.SendChainGas,
        DestTokenDecimals:    request.DestTokenDecimals,
        DestToken:            request.Transaction.DestToken.String(),
        DestTxHash:           stringToNullString(request.DestTxHash.String()),
        OriginAmountOriginal: request.Transaction.OriginAmount.String(),
        OriginAmount:         decimal.NewFromBigInt(request.Transaction.OriginAmount, int32(request.OriginTokenDecimals)),
        DestAmountOriginal:   request.Transaction.DestAmount.String(),
        DestAmount:           decimal.NewFromBigInt(request.Transaction.DestAmount, int32(request.DestTokenDecimals)),
        Deadline:             time.Unix(int64(request.Transaction.Deadline.Uint64()), 0),
        OriginNonce:          int(request.Transaction.Nonce.Uint64()),
        Status:               request.Status,
        BlockNumber:          request.BlockNumber,
        RelayNonce:           request.RelayNonce,
    }
}

// FromRebalance converts a rebalance to a db object.
func FromRebalance(rebalance reldb.Rebalance) Rebalance {
    var id sql.NullString
    if rebalance.RebalanceID == nil {
        id = sql.NullString{Valid: false}
    } else {
        id = sql.NullString{String: *rebalance.RebalanceID, Valid: true}
    }
    return Rebalance{
        RebalanceID:     id,
        Origin:          rebalance.Origin,
        Destination:     rebalance.Destination,
        OriginAmount:    rebalance.OriginAmount.String(),
        OriginTokenAddr: stringToNullString(rebalance.OriginTokenAddr.String()),
        Status:          rebalance.Status,
        OriginTxHash:    hashToNullString(rebalance.OriginTxHash),
        DestTxHash:      hashToNullString(rebalance.DestTxHash),
        TokenName:       rebalance.TokenName,
    }
}

var emptyHash = common.HexToHash("").Hex()

func hashToNullString(h common.Hash) sql.NullString {
    if h.Hex() == emptyHash {
        return sql.NullString{Valid: false}
    }
    return sql.NullString{
        String: h.Hex(),
        Valid:  true,
    }
}

func stringToNullString(s string) sql.NullString {
    if s == "" {
        return sql.NullString{Valid: false}
    }
    return sql.NullString{
        String: s,
        Valid:  true,
    }
}

// ToQuoteRequest converts a db object to a quote request.
func (r RequestForQuote) ToQuoteRequest() (*reldb.QuoteRequest, error) {
    txID, err := hexutil.Decode(r.TransactionID)
    if err != nil {
        return nil, fmt.Errorf("could not get transaction id: %w", err)
    }

    req, err := hexutil.Decode(r.RawRequest)
    if err != nil {
        return nil, fmt.Errorf("could not get request: %w", err)
    }

    transactionID, err := sliceToArray(txID)
    if err != nil {
        return nil, fmt.Errorf("could not convert transaction id: %w", err)
    }

    return &reldb.QuoteRequest{
        OriginTokenDecimals: r.OriginTokenDecimals,
        DestTokenDecimals:   r.DestTokenDecimals,
        TransactionID:       transactionID,
        RawRequest:          req,
        Sender:              common.HexToAddress(r.OriginSender),
        BlockNumber:         r.BlockNumber,
        Transaction: fastbridge.IFastBridgeBridgeTransaction{
            OriginChainId: r.OriginChainID,
            DestChainId:   r.DestChainID,
            OriginSender:  common.HexToAddress(r.OriginSender),
            DestRecipient: common.HexToAddress(r.DestRecipient),
            OriginToken:   common.HexToAddress(r.OriginToken),
            SendChainGas:  r.SendChainGas,
            DestToken:     common.HexToAddress(r.DestToken),
            OriginAmount:  new(big.Int).Div(r.OriginAmount.BigInt(), big.NewInt(int64(math.Pow10(int(r.OriginTokenDecimals))))),
            // OriginAmount: new(big.Int).Div(r.OriginAmount.BigInt(), big.NewInt(int64(r.OriginTokenDecimals))),
            DestAmount: new(big.Int).Div(r.DestAmount.BigInt(), big.NewInt(int64(math.Pow10(int(r.DestTokenDecimals))))),
            Deadline:   big.NewInt(r.Deadline.Unix()),
            Nonce:      big.NewInt(int64(r.OriginNonce)),
        },
        Status:       r.Status,
        OriginTxHash: common.HexToHash(r.OriginTxHash.String),
        DestTxHash:   common.HexToHash(r.DestTxHash.String),
        RelayNonce:   r.RelayNonce,
    }, nil
}

// ToRebalance converts a db object to a rebalance.
func (r Rebalance) ToRebalance() (*reldb.Rebalance, error) {
    originAmount, ok := new(big.Int).SetString(r.OriginAmount, 10)
    if !ok {
        return nil, errors.New("could not convert origin amount")
    }
    return &reldb.Rebalance{
        RebalanceID:     &r.RebalanceID.String,
        Origin:          r.Origin,
        Destination:     r.Destination,
        OriginAmount:    originAmount,
        OriginTokenAddr: common.HexToAddress(r.OriginTokenAddr.String),
        Status:          r.Status,
        OriginTxHash:    common.HexToHash(r.OriginTxHash.String),
        DestTxHash:      common.HexToHash(r.DestTxHash.String),
        TokenName:       r.TokenName,
    }, nil
}

func sliceToArray(slice []byte) ([32]byte, error) {
    var arr [32]byte
    if len(slice) != 32 {
        return arr, errors.New("slice is not 32 bytes long")
    }
    copy(arr[:], slice)
    return arr, nil
}