mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-24 07:34:31 +00:00
932 lines
40 KiB
Go
932 lines
40 KiB
Go
package XDCxlending
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/XinFinOrg/XDPoSChain/XDCx"
|
|
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
|
"github.com/XinFinOrg/XDPoSChain/XDCxDAO"
|
|
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
|
|
"github.com/XinFinOrg/XDPoSChain/common"
|
|
"github.com/XinFinOrg/XDPoSChain/common/lru"
|
|
"github.com/XinFinOrg/XDPoSChain/common/prque"
|
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
|
"github.com/XinFinOrg/XDPoSChain/core/state"
|
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
|
"github.com/XinFinOrg/XDPoSChain/log"
|
|
"github.com/XinFinOrg/XDPoSChain/p2p"
|
|
"github.com/XinFinOrg/XDPoSChain/rpc"
|
|
)
|
|
|
|
const (
|
|
ProtocolName = "XDCxlending"
|
|
ProtocolVersion = uint64(1)
|
|
ProtocolVersionStr = "1.0"
|
|
defaultCacheLimit = 1024
|
|
)
|
|
|
|
var (
|
|
ErrNonceTooHigh = errors.New("nonce too high")
|
|
ErrNonceTooLow = errors.New("nonce too low")
|
|
)
|
|
|
|
type Lending struct {
|
|
Triegc *prque.Prque[int64, common.Hash] // Priority queue mapping block numbers to tries to gc
|
|
StateCache lendingstate.Database // State database to reuse between imports (contains state cache) *lendingstate.TradingStateDB
|
|
|
|
orderNonce map[common.Address]*big.Int
|
|
|
|
XDCx *XDCx.XDCX
|
|
lendingItemHistory *lru.Cache[common.Hash, map[common.Hash]lendingstate.LendingItemHistoryItem]
|
|
lendingTradeHistory *lru.Cache[common.Hash, map[common.Hash]lendingstate.LendingTradeHistoryItem]
|
|
}
|
|
|
|
func (l *Lending) Protocols() []p2p.Protocol {
|
|
return []p2p.Protocol{}
|
|
}
|
|
|
|
func (l *Lending) Start(server *p2p.Server) error {
|
|
return nil
|
|
}
|
|
|
|
func (l *Lending) Stop() error {
|
|
return nil
|
|
}
|
|
|
|
func New(XDCx *XDCx.XDCX) *Lending {
|
|
lending := &Lending{
|
|
orderNonce: make(map[common.Address]*big.Int),
|
|
Triegc: prque.New[int64, common.Hash](nil),
|
|
lendingItemHistory: lru.NewCache[common.Hash, map[common.Hash]lendingstate.LendingItemHistoryItem](defaultCacheLimit),
|
|
lendingTradeHistory: lru.NewCache[common.Hash, map[common.Hash]lendingstate.LendingTradeHistoryItem](defaultCacheLimit),
|
|
}
|
|
lending.StateCache = lendingstate.NewDatabase(XDCx.GetLevelDB())
|
|
lending.XDCx = XDCx
|
|
return lending
|
|
}
|
|
|
|
func (l *Lending) GetLevelDB() XDCxDAO.XDCXDAO {
|
|
return l.XDCx.GetLevelDB()
|
|
}
|
|
|
|
func (l *Lending) GetMongoDB() XDCxDAO.XDCXDAO {
|
|
return l.XDCx.GetMongoDB()
|
|
}
|
|
|
|
// APIs returns the RPC descriptors the Lending implementation offers
|
|
func (l *Lending) APIs() []rpc.API {
|
|
return []rpc.API{
|
|
{
|
|
Namespace: ProtocolName,
|
|
Version: ProtocolVersionStr,
|
|
Service: NewPublicXDCXLendingAPI(l),
|
|
Public: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Version returns the Lending sub-protocols version number.
|
|
func (l *Lending) Version() uint64 {
|
|
return ProtocolVersion
|
|
}
|
|
|
|
func (l *Lending) ProcessOrderPending(header *types.Header, coinbase common.Address, chain consensus.ChainContext, pending map[common.Address]types.LendingTransactions, statedb *state.StateDB, lendingStatedb *lendingstate.LendingStateDB, tradingStateDb *tradingstate.TradingStateDB) ([]*lendingstate.LendingItem, map[common.Hash]lendingstate.MatchingResult) {
|
|
lendingItems := []*lendingstate.LendingItem{}
|
|
matchingResults := map[common.Hash]lendingstate.MatchingResult{}
|
|
|
|
txs := types.NewLendingTransactionByNonce(types.LendingTxSigner{}, pending)
|
|
for {
|
|
tx := txs.Peek()
|
|
if tx == nil {
|
|
break
|
|
}
|
|
log.Debug("ProcessOrderPending start", "len", len(pending))
|
|
log.Debug("Get pending orders to process", "address", tx.UserAddress(), "nonce", tx.Nonce())
|
|
V, R, S := tx.Signature()
|
|
|
|
bigstr := V.String()
|
|
n, e := strconv.ParseInt(bigstr, 10, 8)
|
|
if e != nil {
|
|
continue
|
|
}
|
|
|
|
order := &lendingstate.LendingItem{
|
|
Nonce: big.NewInt(int64(tx.Nonce())),
|
|
Quantity: tx.Quantity(),
|
|
Interest: new(big.Int).SetUint64(tx.Interest()),
|
|
Relayer: tx.RelayerAddress(),
|
|
Term: tx.Term(),
|
|
UserAddress: tx.UserAddress(),
|
|
LendingToken: tx.LendingToken(),
|
|
CollateralToken: tx.CollateralToken(),
|
|
AutoTopUp: tx.AutoTopUp(),
|
|
Status: tx.Status(),
|
|
Side: tx.Side(),
|
|
Type: tx.Type(),
|
|
Hash: tx.LendingHash(),
|
|
LendingId: tx.LendingId(),
|
|
LendingTradeId: tx.LendingTradeId(),
|
|
ExtraData: tx.ExtraData(),
|
|
Signature: &lendingstate.Signature{
|
|
V: byte(n),
|
|
R: common.BigToHash(R),
|
|
S: common.BigToHash(S),
|
|
},
|
|
}
|
|
|
|
log.Info("Process order pending", "orderPending", order, "LendingToken", order.LendingToken.Hex(), "CollateralToken", order.CollateralToken)
|
|
originalOrder := &lendingstate.LendingItem{}
|
|
*originalOrder = *order
|
|
originalOrder.Quantity = lendingstate.CloneBigInt(order.Quantity)
|
|
|
|
newTrades, newRejectedOrders, err := l.CommitOrder(header, coinbase, chain, statedb, lendingStatedb, tradingStateDb, lendingstate.GetLendingOrderBookHash(order.LendingToken, order.Term), order)
|
|
for _, reject := range newRejectedOrders {
|
|
log.Debug("Reject order", "reject", *reject)
|
|
}
|
|
|
|
switch err {
|
|
case ErrNonceTooLow:
|
|
// New head notification data race between the transaction pool and miner, shift
|
|
log.Debug("Skipping order with low nonce", "sender", tx.UserAddress(), "nonce", tx.Nonce())
|
|
txs.Shift()
|
|
continue
|
|
|
|
case ErrNonceTooHigh:
|
|
// Reorg notification data race between the transaction pool and miner, skip account =
|
|
log.Debug("Skipping order account with high nonce", "sender", tx.UserAddress(), "nonce", tx.Nonce())
|
|
txs.Pop()
|
|
continue
|
|
|
|
case nil:
|
|
// everything ok
|
|
txs.Shift()
|
|
|
|
default:
|
|
// Strange error, discard the transaction and get the next in line (note, the
|
|
// nonce-too-high clause will prevent us from executing in vain).
|
|
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
|
txs.Shift()
|
|
continue
|
|
}
|
|
|
|
// orderID has been updated
|
|
originalOrder.LendingId = order.LendingId
|
|
originalOrder.ExtraData = order.ExtraData
|
|
lendingItems = append(lendingItems, originalOrder)
|
|
matchingResults[lendingstate.GetLendingCacheKey(order)] = lendingstate.MatchingResult{
|
|
Trades: newTrades,
|
|
Rejects: newRejectedOrders,
|
|
}
|
|
}
|
|
return lendingItems, matchingResults
|
|
}
|
|
|
|
// there are 3 tasks need to complete (for SDK nodes) after matching
|
|
// 1. Put takerLendingItem to database
|
|
// 2.a Update status, filledAmount of makerLendingItem
|
|
// 2.b. Put lendingTrade to database
|
|
// 3. Update status of rejected items
|
|
func (l *Lending) SyncDataToSDKNode(chain consensus.ChainContext, statedb *state.StateDB, block *types.Block, takerLendingItem *lendingstate.LendingItem, txHash common.Hash, txMatchTime time.Time, trades []*lendingstate.LendingTrade, rejectedItems []*lendingstate.LendingItem, dirtyOrderCount *uint64) error {
|
|
var (
|
|
// originTakerLendingItem: item getting from database
|
|
originTakerLendingItem, updatedTakerLendingItem *lendingstate.LendingItem
|
|
makerDirtyHashes []string
|
|
makerDirtyFilledAmount map[string]*big.Int
|
|
err error
|
|
)
|
|
db := l.GetMongoDB()
|
|
db.InitLendingBulk()
|
|
if takerLendingItem.Status == lendingstate.LendingStatusCancelled && len(rejectedItems) > 0 {
|
|
// cancel order is rejected -> nothing change
|
|
log.Debug("Cancel order is rejected", "order", lendingstate.ToJSON(takerLendingItem))
|
|
return nil
|
|
}
|
|
// 1. put processed takerLendingItem to database
|
|
lastState := lendingstate.LendingItemHistoryItem{}
|
|
// Typically, takerItem has never existed in database
|
|
// except cancel case: in this case, item existed in database with status = OPEN, then use send another lendingItem to cancel it
|
|
val, err := db.GetObject(takerLendingItem.Hash, &lendingstate.LendingItem{Type: takerLendingItem.Type})
|
|
if err == nil && val != nil {
|
|
originTakerLendingItem = val.(*lendingstate.LendingItem)
|
|
lastState = lendingstate.LendingItemHistoryItem{
|
|
TxHash: originTakerLendingItem.TxHash,
|
|
FilledAmount: lendingstate.CloneBigInt(originTakerLendingItem.FilledAmount),
|
|
Status: originTakerLendingItem.Status,
|
|
UpdatedAt: originTakerLendingItem.UpdatedAt,
|
|
}
|
|
}
|
|
if originTakerLendingItem != nil {
|
|
updatedTakerLendingItem = originTakerLendingItem
|
|
} else {
|
|
updatedTakerLendingItem = takerLendingItem
|
|
updatedTakerLendingItem.FilledAmount = new(big.Int)
|
|
}
|
|
|
|
if takerLendingItem.Status == lendingstate.LendingStatusNew {
|
|
updatedTakerLendingItem.Status = lendingstate.LendingStatusOpen
|
|
} else if takerLendingItem.Status == lendingstate.LendingStatusCancelled {
|
|
updatedTakerLendingItem.Status = lendingstate.LendingStatusCancelled
|
|
updatedTakerLendingItem.ExtraData = takerLendingItem.ExtraData
|
|
}
|
|
updatedTakerLendingItem.TxHash = txHash
|
|
if updatedTakerLendingItem.CreatedAt.IsZero() {
|
|
updatedTakerLendingItem.CreatedAt = txMatchTime
|
|
}
|
|
if txMatchTime.Before(updatedTakerLendingItem.UpdatedAt) || (txMatchTime.Equal(updatedTakerLendingItem.UpdatedAt) && *dirtyOrderCount == 0) {
|
|
log.Debug("Ignore old lendingItem/lendingTrades taker", "txHash", txHash.Hex(), "txTime", txMatchTime.UnixNano(), "updatedAt", updatedTakerLendingItem.UpdatedAt.UnixNano())
|
|
return nil
|
|
}
|
|
*dirtyOrderCount++
|
|
|
|
l.UpdateLendingItemCache(updatedTakerLendingItem.LendingToken, updatedTakerLendingItem.CollateralToken, updatedTakerLendingItem.Hash, txHash, lastState)
|
|
updatedTakerLendingItem.UpdatedAt = txMatchTime
|
|
|
|
// 2. put trades to database and update status
|
|
log.Debug("Got lendingTrades", "number", len(trades), "txhash", txHash.Hex())
|
|
makerDirtyFilledAmount = make(map[string]*big.Int)
|
|
|
|
tradeList := map[common.Hash]*lendingstate.LendingTrade{}
|
|
for _, tradeRecord := range trades {
|
|
// 2.a. put to trades
|
|
if tradeRecord == nil {
|
|
continue
|
|
}
|
|
if updatedTakerLendingItem.Type == lendingstate.Repay || updatedTakerLendingItem.Type == lendingstate.TopUp || updatedTakerLendingItem.Type == lendingstate.Recall {
|
|
// repay, topup: assign hash = trade.hash
|
|
updatedTakerLendingItem.Hash = tradeRecord.Hash
|
|
updatedTakerLendingItem.CollateralToken = tradeRecord.CollateralToken
|
|
updatedTakerLendingItem.FilledAmount = updatedTakerLendingItem.Quantity
|
|
updatedTakerLendingItem.Interest = new(big.Int).SetUint64(tradeRecord.Interest)
|
|
switch updatedTakerLendingItem.Type {
|
|
case lendingstate.TopUp:
|
|
updatedTakerLendingItem.Status = lendingstate.TopUp
|
|
extraData, _ := json.Marshal(struct {
|
|
Price *big.Int
|
|
}{
|
|
Price: new(big.Int).Div(new(big.Int).Mul(tradeRecord.LiquidationPrice, tradeRecord.DepositRate), tradeRecord.LiquidationRate),
|
|
})
|
|
updatedTakerLendingItem.ExtraData = string(extraData)
|
|
// manual topUp item
|
|
updatedTakerLendingItem.AutoTopUp = false
|
|
case lendingstate.Repay:
|
|
updatedTakerLendingItem.Status = lendingstate.Repay
|
|
paymentBalance := lendingstate.CalculateTotalRepayValue(block.Time().Uint64(), tradeRecord.LiquidationTime, tradeRecord.Term, tradeRecord.Interest, tradeRecord.Amount)
|
|
updatedTakerLendingItem.Quantity = paymentBalance
|
|
updatedTakerLendingItem.FilledAmount = paymentBalance
|
|
// manual repay item
|
|
updatedTakerLendingItem.AutoTopUp = false
|
|
case lendingstate.Recall:
|
|
updatedTakerLendingItem.Status = lendingstate.Recall
|
|
// manual recall item
|
|
updatedTakerLendingItem.AutoTopUp = false
|
|
}
|
|
|
|
log.Debug("UpdateLendingTrade:", "type", updatedTakerLendingItem.Type, "hash", tradeRecord.Hash.Hex(), "status", tradeRecord.Status, "tradeId", tradeRecord.TradeId)
|
|
tradeList[tradeRecord.Hash] = tradeRecord
|
|
continue
|
|
|
|
}
|
|
if tradeRecord.CreatedAt.IsZero() {
|
|
tradeRecord.CreatedAt = txMatchTime
|
|
}
|
|
tradeRecord.UpdatedAt = txMatchTime
|
|
tradeRecord.TxHash = txHash
|
|
tradeRecord.Hash = tradeRecord.ComputeHash()
|
|
tradeList[tradeRecord.Hash] = tradeRecord
|
|
|
|
// 2.b. update status and filledAmount
|
|
filledAmount := new(big.Int)
|
|
if tradeRecord.Amount != nil {
|
|
filledAmount = lendingstate.CloneBigInt(tradeRecord.Amount)
|
|
}
|
|
// maker dirty order
|
|
makerFilledAmount := big.NewInt(0)
|
|
var makerOrderHash common.Hash
|
|
if updatedTakerLendingItem.Side == lendingstate.Borrowing {
|
|
makerOrderHash = tradeRecord.InvestingOrderHash
|
|
} else {
|
|
makerOrderHash = tradeRecord.BorrowingOrderHash
|
|
}
|
|
if amount, ok := makerDirtyFilledAmount[makerOrderHash.Hex()]; ok {
|
|
makerFilledAmount = lendingstate.CloneBigInt(amount)
|
|
}
|
|
makerFilledAmount = new(big.Int).Add(makerFilledAmount, filledAmount)
|
|
makerDirtyFilledAmount[makerOrderHash.Hex()] = makerFilledAmount
|
|
makerDirtyHashes = append(makerDirtyHashes, makerOrderHash.Hex())
|
|
|
|
if updatedTakerLendingItem.Type == lendingstate.Limit || updatedTakerLendingItem.Type == lendingstate.Market {
|
|
//updatedTakerOrder = l.updateMatchedOrder(updatedTakerOrder, filledAmount, txMatchTime, txHash)
|
|
// update filledAmount, status of takerOrder
|
|
updatedTakerLendingItem.FilledAmount = new(big.Int).Add(updatedTakerLendingItem.FilledAmount, filledAmount)
|
|
if updatedTakerLendingItem.FilledAmount.Cmp(updatedTakerLendingItem.Quantity) < 0 && updatedTakerLendingItem.Type == lendingstate.Limit {
|
|
updatedTakerLendingItem.Status = lendingstate.LendingStatusPartialFilled
|
|
} else {
|
|
updatedTakerLendingItem.Status = lendingstate.LendingStatusFilled
|
|
}
|
|
}
|
|
}
|
|
if err := l.UpdateLendingTrade(tradeList, txHash, txMatchTime); err != nil {
|
|
return err
|
|
}
|
|
|
|
// for Market orders
|
|
// filledAmount > 0 : FILLED
|
|
// otherwise: REJECTED
|
|
if updatedTakerLendingItem.Type == lendingstate.Market {
|
|
if updatedTakerLendingItem.FilledAmount.Sign() > 0 {
|
|
updatedTakerLendingItem.Status = lendingstate.LendingStatusFilled
|
|
} else {
|
|
updatedTakerLendingItem.Status = lendingstate.LendingStatusReject
|
|
}
|
|
}
|
|
|
|
log.Debug("PutObject processed takerLendingItem",
|
|
"term", updatedTakerLendingItem.Term, "userAddr", updatedTakerLendingItem.UserAddress.Hex(), "side", updatedTakerLendingItem.Side,
|
|
"Interest", updatedTakerLendingItem.Interest, "quantity", updatedTakerLendingItem.Quantity, "filledAmount", updatedTakerLendingItem.FilledAmount, "status", updatedTakerLendingItem.Status,
|
|
"hash", updatedTakerLendingItem.Hash.Hex(), "txHash", updatedTakerLendingItem.TxHash.Hex())
|
|
|
|
if !(updatedTakerLendingItem.Type == lendingstate.Repay || updatedTakerLendingItem.Type == lendingstate.TopUp || updatedTakerLendingItem.Type == lendingstate.Recall) || updatedTakerLendingItem.Status != lendingstate.LendingStatusOpen {
|
|
if err := db.PutObject(updatedTakerLendingItem.Hash, updatedTakerLendingItem); err != nil {
|
|
return fmt.Errorf("SDKNode: failed to put processed takerOrder. Hash: %s Error: %s", updatedTakerLendingItem.Hash.Hex(), err.Error())
|
|
}
|
|
}
|
|
|
|
items := db.GetListItemByHashes(makerDirtyHashes, &lendingstate.LendingItem{})
|
|
if items != nil {
|
|
makerItems := items.([]*lendingstate.LendingItem)
|
|
log.Debug("Maker dirty lendingItem", "len", len(makerItems), "txhash", txHash.Hex())
|
|
for _, m := range makerItems {
|
|
if txMatchTime.Before(m.UpdatedAt) {
|
|
log.Debug("Ignore old lendingItem/lendingTrades maker", "txHash", txHash.Hex(), "txTime", txMatchTime.UnixNano(), "updatedAt", m.UpdatedAt.UnixNano())
|
|
continue
|
|
}
|
|
lastState = lendingstate.LendingItemHistoryItem{
|
|
TxHash: m.TxHash,
|
|
FilledAmount: lendingstate.CloneBigInt(m.FilledAmount),
|
|
Status: m.Status,
|
|
UpdatedAt: m.UpdatedAt,
|
|
}
|
|
l.UpdateLendingItemCache(m.LendingToken, m.CollateralToken, m.Hash, txHash, lastState)
|
|
m.TxHash = txHash
|
|
m.UpdatedAt = txMatchTime
|
|
m.FilledAmount = new(big.Int).Add(m.FilledAmount, makerDirtyFilledAmount[m.Hash.Hex()])
|
|
if m.FilledAmount.Cmp(m.Quantity) < 0 {
|
|
m.Status = lendingstate.LendingStatusPartialFilled
|
|
} else {
|
|
m.Status = lendingstate.LendingStatusFilled
|
|
}
|
|
log.Debug("PutObject processed makerLendingItem",
|
|
"term", m.Term, "userAddr", m.UserAddress.Hex(), "side", m.Side,
|
|
"Interest", m.Interest, "quantity", m.Quantity, "filledAmount", m.FilledAmount, "status", m.Status,
|
|
"hash", m.Hash.Hex(), "txHash", m.TxHash.Hex())
|
|
if err := db.PutObject(m.Hash, m); err != nil {
|
|
return fmt.Errorf("SDKNode: failed to put processed makerOrder. Hash: %s Error: %s", m.Hash.Hex(), err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. put rejected orders to leveldb and update status REJECTED
|
|
log.Debug("Got rejected lendingItems", "number", len(rejectedItems), "rejectedLendingItems", rejectedItems)
|
|
|
|
if len(rejectedItems) > 0 {
|
|
var rejectedHashes []string
|
|
// updateRejectedOrders
|
|
for _, r := range rejectedItems {
|
|
rejectedHashes = append(rejectedHashes, r.Hash.Hex())
|
|
if updatedTakerLendingItem.Hash == r.Hash && !txMatchTime.Before(r.UpdatedAt) {
|
|
// cache r history for handling reorg
|
|
historyRecord := lendingstate.LendingItemHistoryItem{
|
|
TxHash: updatedTakerLendingItem.TxHash,
|
|
FilledAmount: lendingstate.CloneBigInt(updatedTakerLendingItem.FilledAmount),
|
|
Status: updatedTakerLendingItem.Status,
|
|
UpdatedAt: updatedTakerLendingItem.UpdatedAt,
|
|
}
|
|
l.UpdateLendingItemCache(updatedTakerLendingItem.LendingToken, updatedTakerLendingItem.CollateralToken, updatedTakerLendingItem.Hash, txHash, historyRecord)
|
|
// if whole order is rejected, status = REJECTED
|
|
// otherwise, status = FILLED
|
|
if updatedTakerLendingItem.FilledAmount.Sign() > 0 {
|
|
updatedTakerLendingItem.Status = lendingstate.LendingStatusFilled
|
|
} else {
|
|
updatedTakerLendingItem.Status = lendingstate.LendingStatusReject
|
|
}
|
|
updatedTakerLendingItem.TxHash = txHash
|
|
updatedTakerLendingItem.UpdatedAt = txMatchTime
|
|
if err := db.PutObject(updatedTakerLendingItem.Hash, updatedTakerLendingItem); err != nil {
|
|
return fmt.Errorf("SDKNode: failed to reject takerOrder. Hash: %s Error: %s", updatedTakerLendingItem.Hash.Hex(), err.Error())
|
|
}
|
|
}
|
|
}
|
|
items := db.GetListItemByHashes(rejectedHashes, &lendingstate.LendingItem{})
|
|
if items != nil {
|
|
dirtyRejectedItems := items.([]*lendingstate.LendingItem)
|
|
for _, r := range dirtyRejectedItems {
|
|
if txMatchTime.Before(r.UpdatedAt) {
|
|
log.Debug("Ignore old orders/trades reject", "txHash", txHash.Hex(), "txTime", txMatchTime.UnixNano(), "updatedAt", updatedTakerLendingItem.UpdatedAt.UnixNano())
|
|
continue
|
|
}
|
|
// cache lendingItem for handling reorg
|
|
historyRecord := lendingstate.LendingItemHistoryItem{
|
|
TxHash: r.TxHash,
|
|
FilledAmount: lendingstate.CloneBigInt(r.FilledAmount),
|
|
Status: r.Status,
|
|
UpdatedAt: r.UpdatedAt,
|
|
}
|
|
l.UpdateLendingItemCache(r.LendingToken, r.CollateralToken, r.Hash, txHash, historyRecord)
|
|
dirtyFilledAmount, ok := makerDirtyFilledAmount[r.Hash.Hex()]
|
|
if ok && dirtyFilledAmount != nil {
|
|
r.FilledAmount = new(big.Int).Add(r.FilledAmount, dirtyFilledAmount)
|
|
}
|
|
// if whole order is rejected, status = REJECTED
|
|
// otherwise, status = FILLED
|
|
if r.FilledAmount.Sign() > 0 {
|
|
r.Status = lendingstate.LendingStatusFilled
|
|
} else {
|
|
r.Status = lendingstate.LendingStatusReject
|
|
}
|
|
r.TxHash = txHash
|
|
r.UpdatedAt = txMatchTime
|
|
if err = db.PutObject(r.Hash, r); err != nil {
|
|
return fmt.Errorf("SDKNode: failed to update rejectedOder to sdkNode %s", err.Error())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := db.CommitLendingBulk(); err != nil {
|
|
return fmt.Errorf("SDKNode fail to commit bulk update lendingItem/lendingTrades at txhash %s . Error: %s", txHash.Hex(), err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Lending) UpdateLiquidatedTrade(blockTime uint64, result lendingstate.FinalizedResult, trades map[common.Hash]*lendingstate.LendingTrade) error {
|
|
db := l.GetMongoDB()
|
|
db.InitLendingBulk()
|
|
|
|
txhash := result.TxHash
|
|
txTime := time.Unix(int64(blockTime), 0).UTC()
|
|
if err := l.UpdateLendingTrade(trades, txhash, txTime); err != nil {
|
|
return err
|
|
}
|
|
|
|
// adding auto repay transaction
|
|
if len(result.AutoRepay) > 0 {
|
|
for _, hash := range result.AutoRepay {
|
|
trade := trades[hash]
|
|
if trade == nil {
|
|
continue
|
|
}
|
|
paymentBalance := lendingstate.CalculateTotalRepayValue(blockTime, trade.LiquidationTime, trade.Term, trade.Interest, trade.Amount)
|
|
repayItem := &lendingstate.LendingItem{
|
|
Quantity: paymentBalance,
|
|
Interest: big.NewInt(int64(trade.Interest)),
|
|
Side: "",
|
|
Type: lendingstate.Repay,
|
|
LendingToken: trade.LendingToken,
|
|
CollateralToken: trade.CollateralToken,
|
|
FilledAmount: paymentBalance,
|
|
Status: lendingstate.Repay,
|
|
Relayer: trade.BorrowingRelayer,
|
|
Term: trade.Term,
|
|
UserAddress: trade.Borrower,
|
|
Signature: nil,
|
|
Hash: trade.Hash,
|
|
TxHash: txhash,
|
|
Nonce: nil,
|
|
CreatedAt: txTime,
|
|
UpdatedAt: txTime,
|
|
LendingId: 0,
|
|
LendingTradeId: trade.TradeId,
|
|
AutoTopUp: true, // auto repay
|
|
ExtraData: "",
|
|
}
|
|
if err := db.PutObject(repayItem.Hash, repayItem); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// adding auto topup transaction
|
|
if len(result.AutoTopUp) > 0 {
|
|
oldTradeHashes := []string{}
|
|
for _, hash := range result.AutoTopUp {
|
|
oldTradeHashes = append(oldTradeHashes, hash.Hex())
|
|
}
|
|
items := db.GetListItemByHashes(oldTradeHashes, &lendingstate.LendingTrade{})
|
|
if items != nil && len(items.([]*lendingstate.LendingTrade)) > 0 {
|
|
for _, oldTrade := range items.([]*lendingstate.LendingTrade) {
|
|
newTrade := trades[oldTrade.Hash]
|
|
topUpAmount := new(big.Int).Sub(newTrade.CollateralLockedAmount, oldTrade.CollateralLockedAmount)
|
|
extraData, _ := json.Marshal(struct {
|
|
Price *big.Int
|
|
}{
|
|
Price: new(big.Int).Div(new(big.Int).Mul(newTrade.LiquidationPrice, common.BaseTopUp), common.RateTopUp),
|
|
})
|
|
topUpItem := &lendingstate.LendingItem{
|
|
Quantity: topUpAmount,
|
|
Interest: big.NewInt(int64(oldTrade.Interest)),
|
|
Side: "",
|
|
Type: lendingstate.TopUp,
|
|
LendingToken: oldTrade.LendingToken,
|
|
CollateralToken: oldTrade.CollateralToken,
|
|
FilledAmount: topUpAmount,
|
|
Status: lendingstate.TopUp,
|
|
AutoTopUp: true, // auto topup
|
|
Relayer: oldTrade.BorrowingRelayer,
|
|
Term: oldTrade.Term,
|
|
UserAddress: oldTrade.Borrower,
|
|
Signature: nil,
|
|
Hash: oldTrade.Hash,
|
|
TxHash: txhash,
|
|
Nonce: nil,
|
|
CreatedAt: txTime,
|
|
UpdatedAt: txTime,
|
|
LendingId: 0,
|
|
LendingTradeId: oldTrade.TradeId,
|
|
ExtraData: string(extraData),
|
|
}
|
|
if err := db.PutObject(topUpItem.Hash, topUpItem); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// adding auto recall transaction
|
|
if len(result.AutoRecall) > 0 {
|
|
oldTradeHashes := []string{}
|
|
for _, hash := range result.AutoRecall {
|
|
oldTradeHashes = append(oldTradeHashes, hash.Hex())
|
|
}
|
|
items := db.GetListItemByHashes(oldTradeHashes, &lendingstate.LendingTrade{})
|
|
if items != nil && len(items.([]*lendingstate.LendingTrade)) > 0 {
|
|
for _, oldTrade := range items.([]*lendingstate.LendingTrade) {
|
|
newTrade := trades[oldTrade.Hash]
|
|
recallAmount := new(big.Int).Sub(oldTrade.CollateralLockedAmount, newTrade.CollateralLockedAmount)
|
|
extraData, _ := json.Marshal(struct {
|
|
Price *big.Int
|
|
}{
|
|
Price: new(big.Int).Div(new(big.Int).Mul(newTrade.LiquidationPrice, oldTrade.DepositRate), oldTrade.LiquidationRate),
|
|
})
|
|
topUpItem := &lendingstate.LendingItem{
|
|
Quantity: recallAmount,
|
|
Interest: big.NewInt(int64(oldTrade.Interest)),
|
|
Side: "",
|
|
Type: lendingstate.Recall,
|
|
LendingToken: oldTrade.LendingToken,
|
|
CollateralToken: oldTrade.CollateralToken,
|
|
FilledAmount: recallAmount,
|
|
Status: lendingstate.Recall,
|
|
AutoTopUp: true, // auto recall
|
|
Relayer: oldTrade.BorrowingRelayer,
|
|
Term: oldTrade.Term,
|
|
UserAddress: oldTrade.Borrower,
|
|
Signature: nil,
|
|
Hash: oldTrade.Hash,
|
|
TxHash: txhash,
|
|
Nonce: nil,
|
|
CreatedAt: txTime,
|
|
UpdatedAt: txTime,
|
|
LendingId: 0,
|
|
LendingTradeId: oldTrade.TradeId,
|
|
ExtraData: string(extraData),
|
|
}
|
|
if err := db.PutObject(topUpItem.Hash, topUpItem); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := db.CommitLendingBulk(); err != nil {
|
|
return fmt.Errorf("failed to updateLendingTrade . Err: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *Lending) UpdateLendingTrade(trades map[common.Hash]*lendingstate.LendingTrade, txhash common.Hash, txTime time.Time) error {
|
|
db := l.GetMongoDB()
|
|
hashQuery := []string{}
|
|
if len(trades) == 0 {
|
|
return nil
|
|
}
|
|
for _, trade := range trades {
|
|
hashQuery = append(hashQuery, trade.Hash.Hex())
|
|
}
|
|
items := db.GetListItemByHashes(hashQuery, &lendingstate.LendingTrade{})
|
|
if items != nil && len(items.([]*lendingstate.LendingTrade)) > 0 {
|
|
for _, trade := range items.([]*lendingstate.LendingTrade) {
|
|
history := lendingstate.LendingTradeHistoryItem{
|
|
TxHash: trade.TxHash,
|
|
CollateralLockedAmount: trade.CollateralLockedAmount,
|
|
LiquidationPrice: trade.LiquidationPrice,
|
|
Status: trade.Status,
|
|
UpdatedAt: trade.UpdatedAt,
|
|
}
|
|
l.UpdateLendingTradeCache(trade.Hash, txhash, history)
|
|
trade.TxHash = txhash
|
|
trade.UpdatedAt = txTime
|
|
|
|
newTrade := trades[trade.Hash]
|
|
trade.CollateralLockedAmount = newTrade.CollateralLockedAmount
|
|
trade.Status = newTrade.Status
|
|
trade.LiquidationPrice = newTrade.LiquidationPrice
|
|
trade.ExtraData = newTrade.ExtraData
|
|
|
|
if err := db.PutObject(trade.Hash, trade); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
log.Debug("UpdateLendingTrade successfully", "txhash", txhash, "hash", hashQuery)
|
|
} else {
|
|
// not update, just upsert
|
|
for _, trade := range trades {
|
|
if err := db.PutObject(trade.Hash, trade); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Lending) GetLendingState(block *types.Block, author common.Address) (*lendingstate.LendingStateDB, error) {
|
|
root, err := l.GetLendingStateRoot(block, author)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if l.StateCache == nil {
|
|
return nil, errors.New("not initialized XDCx")
|
|
}
|
|
state, err := lendingstate.New(root, l.StateCache)
|
|
if err != nil {
|
|
log.Info("Not found lending state when GetLendingState", "block", block.Number(), "lendingRoot", root.Hex())
|
|
}
|
|
return state, err
|
|
}
|
|
|
|
func (l *Lending) GetStateCache() lendingstate.Database {
|
|
return l.StateCache
|
|
}
|
|
|
|
func (l *Lending) HasLendingState(block *types.Block, author common.Address) bool {
|
|
root, err := l.GetLendingStateRoot(block, author)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
_, err = l.StateCache.OpenTrie(root)
|
|
return err == nil
|
|
}
|
|
|
|
func (l *Lending) GetTriegc() *prque.Prque[int64, common.Hash] {
|
|
return l.Triegc
|
|
}
|
|
|
|
func (l *Lending) GetLendingStateRoot(block *types.Block, author common.Address) (common.Hash, error) {
|
|
for _, tx := range block.Transactions() {
|
|
to := tx.To()
|
|
if to != nil && *to == common.TradingStateAddrBinary && *tx.From() == author {
|
|
data := tx.Data()
|
|
if len(data) >= 64 {
|
|
return common.BytesToHash(data[32:]), nil
|
|
}
|
|
}
|
|
}
|
|
return lendingstate.EmptyRoot, nil
|
|
}
|
|
|
|
func (l *Lending) UpdateLendingItemCache(LendingToken, CollateralToken common.Address, hash common.Hash, txhash common.Hash, lastState lendingstate.LendingItemHistoryItem) {
|
|
lendingCacheAtTxHash, ok := l.lendingItemHistory.Get(txhash)
|
|
if !ok || lendingCacheAtTxHash == nil {
|
|
lendingCacheAtTxHash = make(map[common.Hash]lendingstate.LendingItemHistoryItem)
|
|
}
|
|
orderKey := lendingstate.GetLendingItemHistoryKey(LendingToken, CollateralToken, hash)
|
|
_, ok = lendingCacheAtTxHash[orderKey]
|
|
if !ok {
|
|
lendingCacheAtTxHash[orderKey] = lastState
|
|
}
|
|
l.lendingItemHistory.Add(txhash, lendingCacheAtTxHash)
|
|
}
|
|
|
|
func (l *Lending) UpdateLendingTradeCache(hash common.Hash, txhash common.Hash, lastState lendingstate.LendingTradeHistoryItem) {
|
|
var lendingCacheAtTxHash map[common.Hash]lendingstate.LendingTradeHistoryItem
|
|
lendingCacheAtTxHash, ok := l.lendingTradeHistory.Get(txhash)
|
|
if !ok || lendingCacheAtTxHash == nil {
|
|
lendingCacheAtTxHash = make(map[common.Hash]lendingstate.LendingTradeHistoryItem)
|
|
}
|
|
_, ok = lendingCacheAtTxHash[hash]
|
|
if !ok {
|
|
lendingCacheAtTxHash[hash] = lastState
|
|
}
|
|
l.lendingTradeHistory.Add(txhash, lendingCacheAtTxHash)
|
|
}
|
|
|
|
func (l *Lending) RollbackLendingData(txhash common.Hash) error {
|
|
db := l.GetMongoDB()
|
|
db.InitLendingBulk()
|
|
|
|
// rollback lendingItem
|
|
items := db.GetListItemByTxHash(txhash, &lendingstate.LendingItem{})
|
|
if items != nil {
|
|
for _, item := range items.([]*lendingstate.LendingItem) {
|
|
cacheAtTxHash, ok := l.lendingItemHistory.Get(txhash)
|
|
log.Debug("XDCxlending reorg: rollback lendingItem", "txhash", txhash.Hex(), "item", lendingstate.ToJSON(item), "lendingItemHistory", cacheAtTxHash)
|
|
if !ok || cacheAtTxHash == nil {
|
|
log.Debug("XDCxlending reorg: remove item due to no lendingItemHistory", "item", lendingstate.ToJSON(item))
|
|
if err := db.DeleteObject(item.Hash, &lendingstate.LendingItem{}); err != nil {
|
|
return fmt.Errorf("failed to remove reorg LendingItem. Err: %v . Item: %s", err.Error(), lendingstate.ToJSON(item))
|
|
}
|
|
continue
|
|
}
|
|
lendingItemHistory := cacheAtTxHash[lendingstate.GetLendingItemHistoryKey(item.LendingToken, item.CollateralToken, item.Hash)]
|
|
if (lendingItemHistory == lendingstate.LendingItemHistoryItem{}) {
|
|
log.Debug("XDCxlending reorg: remove item due to empty lendingItemHistory", "item", lendingstate.ToJSON(item))
|
|
if err := db.DeleteObject(item.Hash, &lendingstate.LendingItem{}); err != nil {
|
|
return fmt.Errorf("failed to remove reorg LendingItem. Err: %v . Item: %s", err.Error(), lendingstate.ToJSON(item))
|
|
}
|
|
continue
|
|
}
|
|
item.TxHash = lendingItemHistory.TxHash
|
|
item.Status = lendingItemHistory.Status
|
|
item.FilledAmount = lendingstate.CloneBigInt(lendingItemHistory.FilledAmount)
|
|
item.UpdatedAt = lendingItemHistory.UpdatedAt
|
|
log.Debug("XDCxlending reorg: update item to the last lendingItemHistory", "item", lendingstate.ToJSON(item), "lendingItemHistory", lendingItemHistory)
|
|
if err := db.PutObject(item.Hash, item); err != nil {
|
|
return fmt.Errorf("failed to update reorg LendingItem. Err: %v . Item: %s", err.Error(), lendingstate.ToJSON(item))
|
|
}
|
|
}
|
|
}
|
|
|
|
// rollback lendingTrade
|
|
items = db.GetListItemByTxHash(txhash, &lendingstate.LendingTrade{})
|
|
if items != nil {
|
|
for _, trade := range items.([]*lendingstate.LendingTrade) {
|
|
cacheAtTxHash, ok := l.lendingTradeHistory.Get(txhash)
|
|
log.Debug("XDCxlending reorg: rollback LendingTrade", "txhash", txhash.Hex(), "trade", lendingstate.ToJSON(trade), "LendingTradeHistory", cacheAtTxHash)
|
|
if !ok || cacheAtTxHash == nil {
|
|
log.Debug("XDCxlending reorg: remove trade due to no LendingTradeHistory", "trade", lendingstate.ToJSON(trade))
|
|
if err := db.DeleteObject(trade.Hash, &lendingstate.LendingTrade{}); err != nil {
|
|
return fmt.Errorf("failed to remove reorg LendingTrade. Err: %v . Trade: %s", err.Error(), lendingstate.ToJSON(trade))
|
|
}
|
|
continue
|
|
}
|
|
lendingTradeHistoryItem := cacheAtTxHash[trade.Hash]
|
|
if (lendingTradeHistoryItem == lendingstate.LendingTradeHistoryItem{}) {
|
|
log.Debug("XDCxlending reorg: remove trade due to empty LendingTradeHistory", "trade", lendingstate.ToJSON(trade))
|
|
if err := db.DeleteObject(trade.Hash, &lendingstate.LendingTrade{}); err != nil {
|
|
return fmt.Errorf("failed to remove reorg LendingTrade. Err: %v . Trade: %s", err.Error(), lendingstate.ToJSON(trade))
|
|
}
|
|
continue
|
|
}
|
|
trade.TxHash = lendingTradeHistoryItem.TxHash
|
|
trade.Status = lendingTradeHistoryItem.Status
|
|
trade.CollateralLockedAmount = lendingstate.CloneBigInt(lendingTradeHistoryItem.CollateralLockedAmount)
|
|
trade.LiquidationPrice = lendingstate.CloneBigInt(lendingTradeHistoryItem.LiquidationPrice)
|
|
trade.UpdatedAt = lendingTradeHistoryItem.UpdatedAt
|
|
log.Debug("XDCxlending reorg: update trade to the last lendingTradeHistoryItem", "trade", lendingstate.ToJSON(trade), "lendingTradeHistoryItem", lendingTradeHistoryItem)
|
|
if err := db.PutObject(trade.Hash, trade); err != nil {
|
|
return fmt.Errorf("failed to update reorg LendingTrade. Err: %v . Trade: %s", err.Error(), lendingstate.ToJSON(trade))
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove repay/topup/recall history
|
|
db.DeleteItemByTxHash(txhash, &lendingstate.LendingItem{Type: lendingstate.Repay})
|
|
db.DeleteItemByTxHash(txhash, &lendingstate.LendingItem{Type: lendingstate.TopUp})
|
|
db.DeleteItemByTxHash(txhash, &lendingstate.LendingItem{Type: lendingstate.Recall})
|
|
|
|
if err := db.CommitLendingBulk(); err != nil {
|
|
return fmt.Errorf("failed to RollbackLendingData. %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Lending) ProcessLiquidationData(header *types.Header, chain consensus.ChainContext, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, lendingState *lendingstate.LendingStateDB) (updatedTrades map[common.Hash]*lendingstate.LendingTrade, liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades []*lendingstate.LendingTrade, err error) {
|
|
time := header.Time
|
|
updatedTrades = map[common.Hash]*lendingstate.LendingTrade{} // sum of liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades
|
|
liquidatedTrades = []*lendingstate.LendingTrade{}
|
|
autoRepayTrades = []*lendingstate.LendingTrade{}
|
|
autoTopUpTrades = []*lendingstate.LendingTrade{}
|
|
autoRecallTrades = []*lendingstate.LendingTrade{}
|
|
|
|
allPairs, err := lendingstate.GetAllLendingPairs(statedb)
|
|
if err != nil {
|
|
log.Debug("Not found all trading pairs", "error", err)
|
|
return updatedTrades, liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades, nil
|
|
}
|
|
allLendingBooks, err := lendingstate.GetAllLendingBooks(statedb)
|
|
if err != nil {
|
|
log.Debug("Not found all lending books", "error", err)
|
|
return updatedTrades, liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades, nil
|
|
}
|
|
|
|
// liquidate trades by time
|
|
for lendingBook := range allLendingBooks {
|
|
lowestTime, tradingIds := lendingState.GetLowestLiquidationTime(lendingBook, time)
|
|
log.Debug("ProcessLiquidationData time", "tradeIds", len(tradingIds))
|
|
for lowestTime.Sign() > 0 && lowestTime.Cmp(time) < 0 {
|
|
for _, tradingId := range tradingIds {
|
|
log.Debug("ProcessRepay", "lowestTime", lowestTime, "time", time, "lendingBook", lendingBook.Hex(), "tradingId", tradingId.Hex())
|
|
trade, err := l.ProcessRepayLendingTrade(header, chain, lendingState, statedb, tradingState, lendingBook, tradingId.Big().Uint64())
|
|
if err != nil {
|
|
log.Error("Fail when process payment ", "time", time, "lendingBook", lendingBook.Hex(), "tradingId", tradingId, "error", err)
|
|
return updatedTrades, liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades, err
|
|
}
|
|
if trade != nil && trade.Hash != (common.Hash{}) {
|
|
updatedTrades[trade.Hash] = trade
|
|
if trade.Status == lendingstate.TradeStatusLiquidated {
|
|
liquidatedTrades = append(liquidatedTrades, trade)
|
|
} else if trade.Status == lendingstate.TradeStatusClosed {
|
|
autoRepayTrades = append(autoRepayTrades, trade)
|
|
}
|
|
}
|
|
}
|
|
lowestTime, tradingIds = lendingState.GetLowestLiquidationTime(lendingBook, time)
|
|
}
|
|
}
|
|
|
|
for _, lendingPair := range allPairs {
|
|
orderbook := tradingstate.GetTradingOrderBookHash(lendingPair.CollateralToken, lendingPair.LendingToken)
|
|
_, collateralPrice, err := l.GetCollateralPrices(header, chain, statedb, tradingState, lendingPair.CollateralToken, lendingPair.LendingToken)
|
|
if err != nil || collateralPrice == nil || collateralPrice.Sign() == 0 {
|
|
log.Error("Fail when get price collateral/lending ", "CollateralToken", lendingPair.CollateralToken.Hex(), "LendingToken", lendingPair.LendingToken.Hex(), "error", err)
|
|
// ignore this pair, do not throw error
|
|
continue
|
|
}
|
|
// liquidate trades
|
|
highestLiquidatePrice, liquidationData := tradingState.GetHighestLiquidationPriceData(orderbook, collateralPrice)
|
|
for highestLiquidatePrice.Sign() > 0 && collateralPrice.Cmp(highestLiquidatePrice) < 0 {
|
|
for lendingBook, tradingIds := range liquidationData {
|
|
for _, tradingIdHash := range tradingIds {
|
|
trade := lendingState.GetLendingTrade(lendingBook, tradingIdHash)
|
|
if trade.AutoTopUp {
|
|
if newTrade, err := l.AutoTopUp(statedb, tradingState, lendingState, lendingBook, tradingIdHash, collateralPrice); err == nil {
|
|
// if this action complete successfully, do not liquidate this trade in this epoch
|
|
log.Debug("AutoTopUp", "borrower", trade.Borrower.Hex(), "collateral", newTrade.CollateralToken.Hex(), "tradingIdHash", tradingIdHash.Hex(), "newLockedAmount", newTrade.CollateralLockedAmount)
|
|
autoTopUpTrades = append(autoTopUpTrades, newTrade)
|
|
updatedTrades[newTrade.Hash] = newTrade
|
|
continue
|
|
}
|
|
}
|
|
log.Debug("LiquidationTrade", "highestLiquidatePrice", highestLiquidatePrice, "lendingBook", lendingBook.Hex(), "tradingIdHash", tradingIdHash.Hex())
|
|
newTrade, err := l.LiquidationTrade(lendingState, statedb, tradingState, lendingBook, tradingIdHash.Big().Uint64())
|
|
if err != nil {
|
|
log.Error("Fail when remove liquidation newTrade", "time", time, "lendingBook", lendingBook.Hex(), "tradingIdHash", tradingIdHash.Hex(), "error", err)
|
|
return updatedTrades, liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades, err
|
|
}
|
|
if newTrade != nil && newTrade.Hash != (common.Hash{}) {
|
|
newTrade.Status = lendingstate.TradeStatusLiquidated
|
|
liquidationData := lendingstate.LiquidationData{
|
|
RecallAmount: common.Big0,
|
|
LiquidationAmount: newTrade.CollateralLockedAmount,
|
|
CollateralPrice: collateralPrice,
|
|
Reason: lendingstate.LiquidatedByPrice,
|
|
}
|
|
extraData, _ := json.Marshal(liquidationData)
|
|
newTrade.ExtraData = string(extraData)
|
|
liquidatedTrades = append(liquidatedTrades, newTrade)
|
|
updatedTrades[newTrade.Hash] = newTrade
|
|
}
|
|
}
|
|
}
|
|
highestLiquidatePrice, liquidationData = tradingState.GetHighestLiquidationPriceData(orderbook, collateralPrice)
|
|
}
|
|
// recall trades
|
|
depositRate, liquidationRate, recallRate := lendingstate.GetCollateralDetail(statedb, lendingPair.CollateralToken)
|
|
recalLiquidatePrice := new(big.Int).Mul(collateralPrice, common.BaseRecall)
|
|
recalLiquidatePrice = new(big.Int).Div(recalLiquidatePrice, recallRate)
|
|
newLiquidatePrice := new(big.Int).Mul(collateralPrice, liquidationRate)
|
|
newLiquidatePrice = new(big.Int).Div(newLiquidatePrice, depositRate)
|
|
allLowertLiquidationData := tradingState.GetAllLowerLiquidationPriceData(orderbook, recalLiquidatePrice)
|
|
log.Debug("ProcessLiquidationData", "orderbook", orderbook.Hex(), "collateralPrice", collateralPrice, "recallRate", recallRate, "recalLiquidatePrice", recalLiquidatePrice, "newLiquidatePrice", newLiquidatePrice, "allLowertLiquidationData", len(allLowertLiquidationData))
|
|
for price, liquidationData := range allLowertLiquidationData {
|
|
if price.Sign() > 0 && recalLiquidatePrice.Cmp(price) > 0 {
|
|
for lendingBook, tradingIds := range liquidationData {
|
|
for _, tradingIdHash := range tradingIds {
|
|
log.Debug("Process Recall", "price", price, "lendingBook", lendingBook, "tradingIdHash", tradingIdHash.Hex())
|
|
trade := lendingState.GetLendingTrade(lendingBook, tradingIdHash)
|
|
log.Debug("TestRecall", "borrower", trade.Borrower.Hex(), "lendingToken", trade.LendingToken.Hex(), "collateral", trade.CollateralToken.Hex(), "price", price, "tradingIdHash", tradingIdHash.Hex())
|
|
if trade.AutoTopUp {
|
|
_, newTrade, err := l.ProcessRecallLendingTrade(lendingState, statedb, tradingState, lendingBook, tradingIdHash, newLiquidatePrice)
|
|
if err != nil {
|
|
log.Error("ProcessRecallLendingTrade", "lendingBook", lendingBook.Hex(), "tradingIdHash", tradingIdHash.Hex(), "newLiquidatePrice", newLiquidatePrice, "err", err)
|
|
return updatedTrades, liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades, err
|
|
}
|
|
// if this action complete successfully, do not liquidate this trade in this epoch
|
|
log.Debug("AutoRecall", "borrower", trade.Borrower.Hex(), "collateral", newTrade.CollateralToken.Hex(), "lendingBook", lendingBook.Hex(), "tradingIdHash", tradingIdHash.Hex(), "newLockedAmount", newTrade.CollateralLockedAmount)
|
|
autoRecallTrades = append(autoRecallTrades, newTrade)
|
|
updatedTrades[newTrade.Hash] = newTrade
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Debug("ProcessLiquidationData", "updatedTrades", len(updatedTrades), "liquidated", len(liquidatedTrades), "autoRepay", len(autoRepayTrades), "autoTopUp", len(autoTopUpTrades), "autoRecall", len(autoRecallTrades))
|
|
return updatedTrades, liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades, nil
|
|
}
|