services/rfq/relayer/reldb/base/model.go
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
}