mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
405 lines
14 KiB
Go
405 lines
14 KiB
Go
package tradingstate
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/XinFinOrg/XDPoSChain/common"
|
|
"github.com/XinFinOrg/XDPoSChain/core/state"
|
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
|
"github.com/XinFinOrg/XDPoSChain/log"
|
|
"github.com/globalsign/mgo/bson"
|
|
)
|
|
|
|
const (
|
|
OrderStatusNew = "NEW"
|
|
OrderStatusOpen = "OPEN"
|
|
OrderStatusPartialFilled = "PARTIAL_FILLED"
|
|
OrderStatusFilled = "FILLED"
|
|
OrderStatusCancelled = "CANCELLED"
|
|
OrderStatusRejected = "REJECTED"
|
|
)
|
|
|
|
// OrderItem : info that will be store in database
|
|
type OrderItem struct {
|
|
Quantity *big.Int `json:"quantity,omitempty"`
|
|
Price *big.Int `json:"price,omitempty"`
|
|
ExchangeAddress common.Address `json:"exchangeAddress,omitempty"`
|
|
UserAddress common.Address `json:"userAddress,omitempty"`
|
|
BaseToken common.Address `json:"baseToken,omitempty"`
|
|
QuoteToken common.Address `json:"quoteToken,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
Side string `json:"side,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
Hash common.Hash `json:"hash,omitempty"`
|
|
TxHash common.Hash `json:"txHash,omitempty"`
|
|
Signature *Signature `json:"signature,omitempty"`
|
|
FilledAmount *big.Int `json:"filledAmount,omitempty"`
|
|
Nonce *big.Int `json:"nonce,omitempty"`
|
|
CreatedAt time.Time `json:"createdAt,omitempty"`
|
|
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
|
OrderID uint64 `json:"orderID,omitempty"`
|
|
ExtraData string `json:"extraData,omitempty"`
|
|
}
|
|
|
|
// Signature struct
|
|
type Signature struct {
|
|
V byte
|
|
R common.Hash
|
|
S common.Hash
|
|
}
|
|
|
|
type SignatureRecord struct {
|
|
V byte `json:"V" bson:"V"`
|
|
R string `json:"R" bson:"R"`
|
|
S string `json:"S" bson:"S"`
|
|
}
|
|
|
|
type OrderItemBSON struct {
|
|
Quantity string `json:"quantity,omitempty" bson:"quantity"`
|
|
Price string `json:"price,omitempty" bson:"price"`
|
|
ExchangeAddress string `json:"exchangeAddress,omitempty" bson:"exchangeAddress"`
|
|
UserAddress string `json:"userAddress,omitempty" bson:"userAddress"`
|
|
BaseToken string `json:"baseToken,omitempty" bson:"baseToken"`
|
|
QuoteToken string `json:"quoteToken,omitempty" bson:"quoteToken"`
|
|
Status string `json:"status,omitempty" bson:"status"`
|
|
Side string `json:"side,omitempty" bson:"side"`
|
|
Type string `json:"type,omitempty" bson:"type"`
|
|
Hash string `json:"hash,omitempty" bson:"hash"`
|
|
TxHash string `json:"txHash,omitempty" bson:"txHash"`
|
|
Signature *SignatureRecord `json:"signature,omitempty" bson:"signature"`
|
|
FilledAmount string `json:"filledAmount,omitempty" bson:"filledAmount"`
|
|
Nonce string `json:"nonce,omitempty" bson:"nonce"`
|
|
CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt,omitempty" bson:"updatedAt"`
|
|
OrderID string `json:"orderID,omitempty" bson:"orderID"`
|
|
ExtraData string `json:"extraData,omitempty" bson:"extraData"`
|
|
}
|
|
|
|
func (o *OrderItem) GetBSON() (interface{}, error) {
|
|
or := OrderItemBSON{
|
|
ExchangeAddress: o.ExchangeAddress.Hex(),
|
|
UserAddress: o.UserAddress.Hex(),
|
|
BaseToken: o.BaseToken.Hex(),
|
|
QuoteToken: o.QuoteToken.Hex(),
|
|
Status: o.Status,
|
|
Side: o.Side,
|
|
Type: o.Type,
|
|
Hash: o.Hash.Hex(),
|
|
TxHash: o.TxHash.Hex(),
|
|
Quantity: o.Quantity.String(),
|
|
Price: o.Price.String(),
|
|
Nonce: o.Nonce.String(),
|
|
CreatedAt: o.CreatedAt,
|
|
UpdatedAt: o.UpdatedAt,
|
|
OrderID: strconv.FormatUint(o.OrderID, 10),
|
|
ExtraData: o.ExtraData,
|
|
}
|
|
|
|
if o.FilledAmount != nil {
|
|
or.FilledAmount = o.FilledAmount.String()
|
|
}
|
|
|
|
if o.Signature != nil {
|
|
or.Signature = &SignatureRecord{
|
|
V: o.Signature.V,
|
|
R: o.Signature.R.Hex(),
|
|
S: o.Signature.S.Hex(),
|
|
}
|
|
}
|
|
|
|
return or, nil
|
|
}
|
|
|
|
func (o *OrderItem) SetBSON(raw bson.Raw) error {
|
|
decoded := new(struct {
|
|
ID bson.ObjectId `json:"id,omitempty" bson:"_id"`
|
|
ExchangeAddress string `json:"exchangeAddress" bson:"exchangeAddress"`
|
|
UserAddress string `json:"userAddress" bson:"userAddress"`
|
|
BaseToken string `json:"baseToken" bson:"baseToken"`
|
|
QuoteToken string `json:"quoteToken" bson:"quoteToken"`
|
|
Status string `json:"status" bson:"status"`
|
|
Side string `json:"side" bson:"side"`
|
|
Type string `json:"type" bson:"type"`
|
|
Hash string `json:"hash" bson:"hash"`
|
|
TxHash string `json:"txHash,omitempty" bson:"txHash"`
|
|
Price string `json:"price" bson:"price"`
|
|
Quantity string `json:"quantity" bson:"quantity"`
|
|
FilledAmount string `json:"filledAmount" bson:"filledAmount"`
|
|
Nonce string `json:"nonce" bson:"nonce"`
|
|
MakeFee string `json:"makeFee" bson:"makeFee"`
|
|
TakeFee string `json:"takeFee" bson:"takeFee"`
|
|
Signature *SignatureRecord `json:"signature" bson:"signature"`
|
|
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
|
|
OrderID string `json:"orderID" bson:"orderID"`
|
|
ExtraData string `json:"extraData,omitempty" bson:"extraData"`
|
|
})
|
|
|
|
err := raw.Unmarshal(decoded)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
o.ExchangeAddress = common.HexToAddress(decoded.ExchangeAddress)
|
|
o.UserAddress = common.HexToAddress(decoded.UserAddress)
|
|
o.BaseToken = common.HexToAddress(decoded.BaseToken)
|
|
o.QuoteToken = common.HexToAddress(decoded.QuoteToken)
|
|
o.FilledAmount = ToBigInt(decoded.FilledAmount)
|
|
o.Nonce = ToBigInt(decoded.Nonce)
|
|
o.Status = decoded.Status
|
|
o.Side = decoded.Side
|
|
o.Type = decoded.Type
|
|
o.Hash = common.HexToHash(decoded.Hash)
|
|
o.TxHash = common.HexToHash(decoded.TxHash)
|
|
|
|
if decoded.Quantity != "" {
|
|
o.Quantity = ToBigInt(decoded.Quantity)
|
|
}
|
|
|
|
if decoded.FilledAmount != "" {
|
|
o.FilledAmount = ToBigInt(decoded.FilledAmount)
|
|
}
|
|
|
|
if decoded.Price != "" {
|
|
o.Price = ToBigInt(decoded.Price)
|
|
}
|
|
|
|
if decoded.Signature != nil {
|
|
o.Signature = &Signature{
|
|
V: decoded.Signature.V,
|
|
R: common.HexToHash(decoded.Signature.R),
|
|
S: common.HexToHash(decoded.Signature.S),
|
|
}
|
|
}
|
|
|
|
o.CreatedAt = decoded.CreatedAt
|
|
o.UpdatedAt = decoded.UpdatedAt
|
|
orderID, err := strconv.ParseInt(decoded.OrderID, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.OrderID = uint64(orderID)
|
|
o.ExtraData = decoded.ExtraData
|
|
return nil
|
|
}
|
|
|
|
// VerifyOrder verify orderItem
|
|
func (o *OrderItem) VerifyOrder(state *state.StateDB) error {
|
|
if err := o.VerifyBasicOrderInfo(); err != nil {
|
|
return err
|
|
}
|
|
if err := o.verifyRelayer(state); err != nil {
|
|
return err
|
|
}
|
|
if o.Status == OrderNew {
|
|
if err := VerifyPair(state, o.ExchangeAddress, o.BaseToken, o.QuoteToken); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// VerifyBasicOrderInfo verify basic info
|
|
func (o *OrderItem) VerifyBasicOrderInfo() error {
|
|
if o.Status == OrderNew {
|
|
if o.Type == Limit {
|
|
if err := o.verifyPrice(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := o.verifyQuantity(); err != nil {
|
|
return err
|
|
}
|
|
if err := o.verifyOrderSide(); err != nil {
|
|
return err
|
|
}
|
|
if err := o.verifyOrderType(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := o.verifyStatus(); err != nil {
|
|
return err
|
|
}
|
|
if err := o.verifySignature(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verify whether the exchange applies to become relayer
|
|
func (o *OrderItem) verifyRelayer(state *state.StateDB) error {
|
|
if !IsValidRelayer(state, o.ExchangeAddress) {
|
|
return ErrInvalidRelayer
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verify signatures
|
|
func (o *OrderItem) verifySignature() error {
|
|
bigstr := o.Nonce.String()
|
|
n, err := strconv.ParseInt(bigstr, 10, 64)
|
|
if err != nil {
|
|
return ErrInvalidSignature
|
|
}
|
|
V := big.NewInt(int64(o.Signature.V))
|
|
R := o.Signature.R.Big()
|
|
S := o.Signature.S.Big()
|
|
|
|
tx := types.NewOrderTransaction(uint64(n), o.Quantity, o.Price, o.ExchangeAddress, o.UserAddress,
|
|
o.BaseToken, o.QuoteToken, o.Status, o.Side, o.Type, o.Hash, o.OrderID)
|
|
tx.ImportSignature(V, R, S)
|
|
from, _ := types.OrderSender(types.OrderTxSigner{}, tx)
|
|
if from != tx.UserAddress() {
|
|
return ErrInvalidSignature
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verify order type
|
|
func (o *OrderItem) verifyOrderType() error {
|
|
if _, ok := MatchingOrderType[o.Type]; !ok {
|
|
log.Debug("Invalid order type", "type", o.Type)
|
|
return ErrInvalidOrderType
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verify order side
|
|
func (o *OrderItem) verifyOrderSide() error {
|
|
if o.Side != Bid && o.Side != Ask {
|
|
log.Debug("Invalid orderSide", "side", o.Side)
|
|
return ErrInvalidOrderSide
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verifyPrice make sure price is a positive number
|
|
func (o *OrderItem) verifyPrice() error {
|
|
if o.Price == nil || o.Price.Sign() <= 0 {
|
|
log.Debug("Invalid price", "price", o.Price.String())
|
|
return ErrInvalidPrice
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verifyQuantity make sure quantity is a positive number
|
|
func (o *OrderItem) verifyQuantity() error {
|
|
if o.Quantity == nil || o.Quantity.Sign() <= 0 {
|
|
log.Debug("Invalid quantity", "quantity", o.Quantity.String())
|
|
return ErrInvalidQuantity
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verifyStatus make sure status is NEW OR CANCELLED
|
|
func (o *OrderItem) verifyStatus() error {
|
|
if o.Status != Cancel && o.Status != OrderNew {
|
|
log.Debug("Invalid status", "status", o.Status)
|
|
return ErrInvalidStatus
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func IsValidRelayer(statedb *state.StateDB, address common.Address) bool {
|
|
slot := RelayerMappingSlot["RELAYER_LIST"]
|
|
locRelayerState := GetLocMappingAtKey(address.Hash(), slot)
|
|
|
|
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locRelayerState, RelayerStructMappingSlot["_deposit"])
|
|
locHashDeposit := common.BigToHash(locBigDeposit)
|
|
balance := statedb.GetState(common.RelayerRegistrationSMC, locHashDeposit).Big()
|
|
if balance.Cmp(new(big.Int).Mul(common.BasePrice, common.RelayerLockedFund)) <= 0 {
|
|
log.Debug("Relayer is not in relayer list", "relayer", address, "balance", balance)
|
|
return false
|
|
}
|
|
if IsResignedRelayer(address, statedb) {
|
|
log.Debug("Relayer has resigned", "relayer", address)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func VerifyPair(statedb *state.StateDB, exchangeAddress, baseToken, quoteToken common.Address) error {
|
|
baseTokenLength := GetBaseTokenLength(exchangeAddress, statedb)
|
|
quoteTokenLength := GetQuoteTokenLength(exchangeAddress, statedb)
|
|
if baseTokenLength != quoteTokenLength {
|
|
return fmt.Errorf("invalid length of baseTokenList: %d . QuoteTokenList: %d", baseTokenLength, quoteTokenLength)
|
|
}
|
|
var baseIndexes []uint64
|
|
for i := uint64(0); i < baseTokenLength; i++ {
|
|
if baseToken == GetBaseTokenAtIndex(exchangeAddress, statedb, i) {
|
|
baseIndexes = append(baseIndexes, i)
|
|
}
|
|
}
|
|
if len(baseIndexes) == 0 {
|
|
return fmt.Errorf("basetoken not found in relayer registration. BaseToken: %s. Exchange: %s", baseToken.Hex(), exchangeAddress.Hex())
|
|
}
|
|
for _, index := range baseIndexes {
|
|
if quoteToken == GetQuoteTokenAtIndex(exchangeAddress, statedb, index) {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("invalid exchange pair. Base: %s. Quote: %s. Exchange: %s", baseToken.Hex(), quoteToken.Hex(), exchangeAddress.Hex())
|
|
}
|
|
|
|
func VerifyBalance(statedb *state.StateDB, XDCxStateDb *TradingStateDB, order *types.OrderTransaction, baseDecimal, quoteDecimal *big.Int) error {
|
|
var quotePrice *big.Int
|
|
if order.QuoteToken() != common.XDCNativeAddressBinary {
|
|
quotePrice = XDCxStateDb.GetLastPrice(GetTradingOrderBookHash(order.QuoteToken(), common.XDCNativeAddressBinary))
|
|
log.Debug("TryGet quotePrice QuoteToken/XDC", "quotePrice", quotePrice)
|
|
if quotePrice == nil || quotePrice.Sign() == 0 {
|
|
inversePrice := XDCxStateDb.GetLastPrice(GetTradingOrderBookHash(common.XDCNativeAddressBinary, order.QuoteToken()))
|
|
log.Debug("TryGet inversePrice XDC/QuoteToken", "inversePrice", inversePrice)
|
|
if inversePrice != nil && inversePrice.Sign() > 0 {
|
|
quotePrice = new(big.Int).Mul(common.BasePrice, quoteDecimal)
|
|
quotePrice = new(big.Int).Div(quotePrice, inversePrice)
|
|
log.Debug("TryGet quotePrice after get inversePrice XDC/QuoteToken", "quotePrice", quotePrice, "quoteTokenDecimal", quoteDecimal)
|
|
}
|
|
}
|
|
} else {
|
|
quotePrice = common.BasePrice
|
|
}
|
|
feeRate := GetExRelayerFee(order.ExchangeAddress(), statedb)
|
|
balanceResult, err := GetSettleBalance(quotePrice, order.Side(), feeRate, order.BaseToken(), order.QuoteToken(), order.Price(), feeRate, baseDecimal, quoteDecimal, order.Quantity())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expectedBalance := balanceResult.Taker.OutTotal
|
|
actualBalance := GetTokenBalance(order.UserAddress(), balanceResult.Taker.OutToken, statedb)
|
|
if actualBalance.Cmp(expectedBalance) < 0 {
|
|
return fmt.Errorf("token: %s . ExpectedBalance: %s . ActualBalance: %s", balanceResult.Taker.OutToken.Hex(), expectedBalance, actualBalance)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MarshalSignature marshals the signature struct to []byte
|
|
func (s *Signature) MarshalSignature() ([]byte, error) {
|
|
sigBytes1 := s.R.Bytes()
|
|
sigBytes2 := s.S.Bytes()
|
|
sigBytes3 := s.V - 27
|
|
|
|
sigBytes := append([]byte{}, sigBytes1...)
|
|
sigBytes = append(sigBytes, sigBytes2...)
|
|
sigBytes = append(sigBytes, sigBytes3)
|
|
|
|
return sigBytes, nil
|
|
}
|
|
|
|
// Verify returns the address that corresponds to the given signature and signed message
|
|
func (s *Signature) Verify(hash common.Hash) (common.Address, error) {
|
|
hashBytes := hash.Bytes()
|
|
sigBytes, err := s.MarshalSignature()
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
|
|
pubKey, err := crypto.SigToPub(hashBytes, sigBytes)
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
address := crypto.PubkeyToAddress(*pubKey)
|
|
return address, nil
|
|
}
|