mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
1273 lines
71 KiB
Go
1273 lines
71 KiB
Go
package XDCxlending
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
|
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
|
|
"github.com/XinFinOrg/XDPoSChain/common"
|
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
|
"github.com/XinFinOrg/XDPoSChain/core/state"
|
|
"github.com/XinFinOrg/XDPoSChain/core/tracing"
|
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
|
"github.com/XinFinOrg/XDPoSChain/log"
|
|
)
|
|
|
|
func (l *Lending) CommitOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, lendingStateDB *lendingstate.LendingStateDB, tradingStateDb *tradingstate.TradingStateDB, lendingOrderBook common.Hash, order *lendingstate.LendingItem) ([]*lendingstate.LendingTrade, []*lendingstate.LendingItem, error) {
|
|
lendingSnap := lendingStateDB.Snapshot()
|
|
tradingSnap := tradingStateDb.Snapshot()
|
|
dbSnap := statedb.Snapshot()
|
|
trades, rejects, err := l.ApplyOrder(header, coinbase, chain, statedb, lendingStateDB, tradingStateDb, lendingOrderBook, order)
|
|
if err != nil {
|
|
lendingStateDB.RevertToSnapshot(lendingSnap)
|
|
tradingStateDb.RevertToSnapshot(tradingSnap)
|
|
statedb.RevertToSnapshot(dbSnap)
|
|
return nil, nil, err
|
|
}
|
|
return trades, rejects, err
|
|
}
|
|
|
|
func (l *Lending) ApplyOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, lendingStateDB *lendingstate.LendingStateDB, tradingStateDb *tradingstate.TradingStateDB, lendingOrderBook common.Hash, order *lendingstate.LendingItem) ([]*lendingstate.LendingTrade, []*lendingstate.LendingItem, error) {
|
|
var (
|
|
rejects []*lendingstate.LendingItem
|
|
trades []*lendingstate.LendingTrade
|
|
err error
|
|
)
|
|
nonce := lendingStateDB.GetNonce(order.UserAddress.Hash())
|
|
log.Debug("ApplyOrder", "addr", order.UserAddress, "statenonce", nonce, "ordernonce", order.Nonce)
|
|
if big.NewInt(int64(nonce)).Cmp(order.Nonce) == -1 {
|
|
return nil, nil, ErrNonceTooHigh
|
|
} else if big.NewInt(int64(nonce)).Cmp(order.Nonce) == 1 {
|
|
return nil, nil, ErrNonceTooLow
|
|
}
|
|
|
|
log.Debug("Exchange add user nonce:", "address", order.UserAddress, "status", order.Status, "nonce", nonce+1)
|
|
lendingStateDB.SetNonce(order.UserAddress.Hash(), nonce+1)
|
|
|
|
lendingSnap := lendingStateDB.Snapshot()
|
|
tradingSnap := tradingStateDb.Snapshot()
|
|
dbSnap := statedb.Snapshot()
|
|
// revert if process order fail
|
|
defer func() {
|
|
if err != nil {
|
|
lendingStateDB.RevertToSnapshot(lendingSnap)
|
|
tradingStateDb.RevertToSnapshot(tradingSnap)
|
|
statedb.RevertToSnapshot(dbSnap)
|
|
}
|
|
}()
|
|
|
|
if err := order.VerifyLendingItem(statedb); err != nil {
|
|
log.Debug("invalid lending order", "order", lendingstate.ToJSON(order), "err", err)
|
|
rejects = append(rejects, order)
|
|
return trades, rejects, nil
|
|
}
|
|
|
|
switch order.Type {
|
|
case lendingstate.TopUp:
|
|
reject, newLendingTrade, err := l.ProcessTopUp(lendingStateDB, statedb, tradingStateDb, order)
|
|
if err != nil || reject {
|
|
rejects = append(rejects, order)
|
|
}
|
|
trades = append(trades, newLendingTrade)
|
|
return trades, rejects, nil
|
|
case lendingstate.Repay:
|
|
lendingTrade, err := l.ProcessRepay(header, chain, lendingStateDB, statedb, tradingStateDb, lendingOrderBook, order)
|
|
if err != nil {
|
|
log.Debug("Can not process payment", "err", err)
|
|
rejects = append(rejects, order)
|
|
}
|
|
trades = append(trades, lendingTrade)
|
|
return trades, rejects, nil
|
|
default:
|
|
}
|
|
|
|
if order.Status == lendingstate.LendingStatusCancelled {
|
|
err, reject := l.ProcessCancelOrder(header, lendingStateDB, statedb, tradingStateDb, chain, coinbase, lendingOrderBook, order)
|
|
if err != nil || reject {
|
|
rejects = append(rejects, order)
|
|
}
|
|
return trades, rejects, nil
|
|
}
|
|
|
|
if order.Type != lendingstate.Market {
|
|
if order.Interest.Sign() == 0 || common.BigToHash(order.Interest).Big().Cmp(order.Interest) != 0 {
|
|
log.Debug("Reject order Interest invalid", "Interest", order.Interest)
|
|
rejects = append(rejects, order)
|
|
return trades, rejects, nil
|
|
}
|
|
}
|
|
if order.Quantity.Sign() == 0 || common.BigToHash(order.Quantity).Big().Cmp(order.Quantity) != 0 {
|
|
log.Debug("Reject order quantity invalid", "quantity", order.Quantity)
|
|
rejects = append(rejects, order)
|
|
return trades, rejects, nil
|
|
}
|
|
orderType := order.Type
|
|
// if we do not use auto-increment orderid, we must set Interest slot to avoid conflict
|
|
if orderType == lendingstate.Market {
|
|
log.Debug("Process maket order", "side", order.Side, "quantity", order.Quantity, "Interest", order.Interest)
|
|
trades, rejects, err = l.processMarketOrder(header, coinbase, chain, statedb, lendingStateDB, tradingStateDb, lendingOrderBook, order)
|
|
if err != nil {
|
|
trades = []*lendingstate.LendingTrade{}
|
|
rejects = append(rejects, order)
|
|
}
|
|
} else {
|
|
log.Debug("Process limit order", "side", order.Side, "quantity", order.Quantity, "Interest", order.Interest)
|
|
trades, rejects, err = l.processLimitOrder(header, coinbase, chain, statedb, lendingStateDB, tradingStateDb, lendingOrderBook, order)
|
|
if err != nil {
|
|
trades = []*lendingstate.LendingTrade{}
|
|
rejects = append(rejects, order)
|
|
}
|
|
}
|
|
return trades, rejects, nil
|
|
}
|
|
|
|
// processMarketOrder : process the market order
|
|
func (l *Lending) processMarketOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, lendingStateDB *lendingstate.LendingStateDB, tradingStateDb *tradingstate.TradingStateDB, lendingOrderBook common.Hash, order *lendingstate.LendingItem) ([]*lendingstate.LendingTrade, []*lendingstate.LendingItem, error) {
|
|
var (
|
|
trades []*lendingstate.LendingTrade
|
|
newTrades []*lendingstate.LendingTrade
|
|
rejects []*lendingstate.LendingItem
|
|
newRejects []*lendingstate.LendingItem
|
|
err error
|
|
)
|
|
quantityToTrade := order.Quantity
|
|
side := order.Side
|
|
// speedup the comparison, do not assign because it is pointer
|
|
zero := lendingstate.Zero
|
|
if side == lendingstate.Borrowing {
|
|
bestInterest, volume := lendingStateDB.GetBestInvestingRate(lendingOrderBook)
|
|
log.Debug("processMarketOrder ", "side", side, "bestInterest", bestInterest, "quantityToTrade", quantityToTrade, "volume", volume)
|
|
for quantityToTrade.Cmp(zero) > 0 && bestInterest.Cmp(zero) > 0 {
|
|
quantityToTrade, newTrades, newRejects, err = l.processOrderList(header, coinbase, chain, statedb, lendingStateDB, tradingStateDb, lendingstate.Investing, lendingOrderBook, bestInterest, quantityToTrade, order)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
trades = append(trades, newTrades...)
|
|
rejects = append(rejects, newRejects...)
|
|
bestInterest, volume = lendingStateDB.GetBestInvestingRate(lendingOrderBook)
|
|
log.Debug("processMarketOrder ", "side", side, "bestInterest", bestInterest, "quantityToTrade", quantityToTrade, "volume", volume)
|
|
}
|
|
} else {
|
|
bestInterest, volume := lendingStateDB.GetBestBorrowRate(lendingOrderBook)
|
|
log.Debug("processMarketOrder ", "side", side, "bestInterest", bestInterest, "quantityToTrade", quantityToTrade, "volume", volume)
|
|
for quantityToTrade.Cmp(zero) > 0 && bestInterest.Cmp(zero) > 0 {
|
|
quantityToTrade, newTrades, newRejects, err = l.processOrderList(header, coinbase, chain, statedb, lendingStateDB, tradingStateDb, lendingstate.Borrowing, lendingOrderBook, bestInterest, quantityToTrade, order)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
trades = append(trades, newTrades...)
|
|
rejects = append(rejects, newRejects...)
|
|
bestInterest, volume = lendingStateDB.GetBestBorrowRate(lendingOrderBook)
|
|
log.Debug("processMarketOrder ", "side", side, "bestInterest", bestInterest, "quantityToTrade", quantityToTrade, "volume", volume)
|
|
}
|
|
}
|
|
return trades, rejects, nil
|
|
}
|
|
|
|
// processLimitOrder : process the limit order, can change the quote
|
|
// If not care for performance, we should make a copy of quote to prevent further reference problem
|
|
func (l *Lending) processLimitOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, lendingStateDB *lendingstate.LendingStateDB, tradingStateDb *tradingstate.TradingStateDB, lendingOrderBook common.Hash, order *lendingstate.LendingItem) ([]*lendingstate.LendingTrade, []*lendingstate.LendingItem, error) {
|
|
var (
|
|
trades []*lendingstate.LendingTrade
|
|
newTrades []*lendingstate.LendingTrade
|
|
rejects []*lendingstate.LendingItem
|
|
newRejects []*lendingstate.LendingItem
|
|
err error
|
|
)
|
|
quantityToTrade := order.Quantity
|
|
side := order.Side
|
|
Interest := order.Interest
|
|
|
|
// speedup the comparison, do not assign because it is pointer
|
|
zero := lendingstate.Zero
|
|
if side == lendingstate.Borrowing {
|
|
minInterest, volume := lendingStateDB.GetBestInvestingRate(lendingOrderBook)
|
|
log.Debug("processLimitOrder ", "side", side, "minInterest", minInterest, "orderInterest", Interest, "volume", volume)
|
|
for quantityToTrade.Cmp(zero) > 0 && Interest.Cmp(minInterest) >= 0 && minInterest.Cmp(zero) > 0 {
|
|
log.Debug("Min Interest in Investing tree", "Interest", minInterest.String())
|
|
quantityToTrade, newTrades, newRejects, err = l.processOrderList(header, coinbase, chain, statedb, lendingStateDB, tradingStateDb, lendingstate.Investing, lendingOrderBook, minInterest, quantityToTrade, order)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
trades = append(trades, newTrades...)
|
|
rejects = append(rejects, newRejects...)
|
|
log.Debug("New trade found", "newTrades", newTrades, "quantityToTrade", quantityToTrade)
|
|
minInterest, volume = lendingStateDB.GetBestInvestingRate(lendingOrderBook)
|
|
log.Debug("processLimitOrder ", "side", side, "minInterest", minInterest, "orderInterest", Interest, "volume", volume)
|
|
}
|
|
} else {
|
|
maxInterest, volume := lendingStateDB.GetBestBorrowRate(lendingOrderBook)
|
|
log.Debug("processLimitOrder ", "side", side, "maxInterest", maxInterest, "orderInterest", Interest, "volume", volume)
|
|
for quantityToTrade.Cmp(zero) > 0 && Interest.Cmp(maxInterest) <= 0 && maxInterest.Cmp(zero) > 0 {
|
|
log.Debug("Max Interest in Borrowing tree", "Interest", maxInterest.String())
|
|
quantityToTrade, newTrades, newRejects, err = l.processOrderList(header, coinbase, chain, statedb, lendingStateDB, tradingStateDb, lendingstate.Borrowing, lendingOrderBook, maxInterest, quantityToTrade, order)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
trades = append(trades, newTrades...)
|
|
rejects = append(rejects, newRejects...)
|
|
log.Debug("New trade found", "newTrades", newTrades, "quantityToTrade", quantityToTrade)
|
|
maxInterest, volume = lendingStateDB.GetBestBorrowRate(lendingOrderBook)
|
|
log.Debug("processLimitOrder ", "side", side, "maxInterest", maxInterest, "orderInterest", Interest, "volume", volume)
|
|
}
|
|
}
|
|
if quantityToTrade.Cmp(zero) > 0 {
|
|
oldOrderId := lendingStateDB.GetNonce(lendingOrderBook)
|
|
order.LendingId = oldOrderId + 1
|
|
order.Quantity = quantityToTrade
|
|
lendingStateDB.SetNonce(lendingOrderBook, oldOrderId+1)
|
|
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.LendingId))
|
|
lendingStateDB.InsertLendingItem(lendingOrderBook, orderIdHash, *order)
|
|
log.Debug("After matching, order (unmatched part) is now added to tree", "side", order.Side, "order", order)
|
|
investingRate, investingVolume := lendingStateDB.GetBestInvestingRate(lendingOrderBook)
|
|
borrowingRate, borrowingVolume := lendingStateDB.GetBestBorrowRate(lendingOrderBook)
|
|
log.Debug("After matching", "side", order.Side, "LendingId", order.LendingId, "investingRate", investingRate, "investingVolume", investingVolume, "borrowingRate", borrowingRate, "borrowingVolume", borrowingVolume)
|
|
}
|
|
return trades, rejects, nil
|
|
}
|
|
|
|
// processOrderList : process the order list
|
|
func (l *Lending) processOrderList(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, lendingStateDB *lendingstate.LendingStateDB, tradingStateDb *tradingstate.TradingStateDB, side string, lendingOrderBook common.Hash, Interest *big.Int, quantityStillToTrade *big.Int, order *lendingstate.LendingItem) (*big.Int, []*lendingstate.LendingTrade, []*lendingstate.LendingItem, error) {
|
|
quantityToTrade := lendingstate.CloneBigInt(quantityStillToTrade)
|
|
log.Debug("Process matching between order and orderlist", "quantityToTrade", quantityToTrade)
|
|
var (
|
|
trades []*lendingstate.LendingTrade
|
|
rejects []*lendingstate.LendingItem
|
|
)
|
|
for quantityToTrade.Sign() > 0 {
|
|
orderId, amount, err := lendingStateDB.GetBestLendingIdAndAmount(lendingOrderBook, Interest, side)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
var oldestOrder lendingstate.LendingItem
|
|
if amount.Sign() > 0 {
|
|
oldestOrder = lendingStateDB.GetLendingOrder(lendingOrderBook, orderId)
|
|
}
|
|
log.Debug("found order ", "orderId ", orderId, "side", oldestOrder.Side, "amount", amount, "side", side, "Interest", Interest)
|
|
if oldestOrder.Quantity == nil || oldestOrder.Quantity.Sign() == 0 && amount.Sign() == 0 {
|
|
break
|
|
}
|
|
var (
|
|
tradedQuantity *big.Int
|
|
maxTradedQuantity *big.Int
|
|
)
|
|
if quantityToTrade.Cmp(amount) <= 0 {
|
|
maxTradedQuantity = lendingstate.CloneBigInt(quantityToTrade)
|
|
} else {
|
|
maxTradedQuantity = lendingstate.CloneBigInt(amount)
|
|
}
|
|
collateralToken := order.CollateralToken
|
|
borrowFee := lendingstate.GetFee(statedb, order.Relayer)
|
|
if order.Side == lendingstate.Investing {
|
|
collateralToken = oldestOrder.CollateralToken
|
|
borrowFee = lendingstate.GetFee(statedb, oldestOrder.Relayer)
|
|
}
|
|
if collateralToken.IsZero() {
|
|
return nil, nil, nil, errors.New("empty collateral")
|
|
}
|
|
depositRate, liquidationRate, recallRate := lendingstate.GetCollateralDetail(statedb, collateralToken)
|
|
if depositRate == nil || depositRate.Sign() <= 0 {
|
|
return nil, nil, nil, fmt.Errorf("invalid depositRate %v", depositRate)
|
|
}
|
|
if liquidationRate == nil || liquidationRate.Sign() <= 0 {
|
|
return nil, nil, nil, fmt.Errorf("invalid liquidationRate %v", liquidationRate)
|
|
}
|
|
if recallRate == nil || recallRate.Sign() <= 0 {
|
|
return nil, nil, nil, fmt.Errorf("invalid recallRate %v", recallRate)
|
|
}
|
|
|
|
lendTokenXDCPrice, collateralPrice, err := l.GetCollateralPrices(header, chain, statedb, tradingStateDb, collateralToken, order.LendingToken)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if lendTokenXDCPrice == nil || lendTokenXDCPrice.Sign() <= 0 {
|
|
return nil, nil, nil, errors.New("invalid lendToken price")
|
|
}
|
|
if collateralPrice == nil || collateralPrice.Sign() <= 0 {
|
|
return nil, nil, nil, errors.New("invalid collateral price")
|
|
}
|
|
tradedQuantity, collateralLockedAmount, rejectMaker, settleBalanceResult, err := l.getLendQuantity(lendTokenXDCPrice, collateralPrice, depositRate, borrowFee, coinbase, chain, header, statedb, order, &oldestOrder, maxTradedQuantity)
|
|
if err != nil && err == lendingstate.ErrQuantityTradeTooSmall && tradedQuantity != nil && tradedQuantity.Sign() >= 0 {
|
|
if tradedQuantity.Cmp(maxTradedQuantity) == 0 {
|
|
if quantityToTrade.Cmp(amount) == 0 { // reject Taker & maker
|
|
rejects = append(rejects, order)
|
|
quantityToTrade = lendingstate.Zero
|
|
rejects = append(rejects, &oldestOrder)
|
|
err = lendingStateDB.CancelLendingOrder(lendingOrderBook, &oldestOrder)
|
|
log.Debug("Reject order maker", "lending id ", oldestOrder.LendingId, "err", err)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
break
|
|
} else if quantityToTrade.Cmp(amount) < 0 { // reject Taker
|
|
rejects = append(rejects, order)
|
|
quantityToTrade = lendingstate.Zero
|
|
break
|
|
} else { // reject maker
|
|
rejects = append(rejects, &oldestOrder)
|
|
err = lendingStateDB.CancelLendingOrder(lendingOrderBook, &oldestOrder)
|
|
log.Debug("Reject order maker", "lending id ", oldestOrder.LendingId, "err", err)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
continue
|
|
}
|
|
} else {
|
|
if rejectMaker { // reject maker
|
|
rejects = append(rejects, &oldestOrder)
|
|
err = lendingStateDB.CancelLendingOrder(lendingOrderBook, &oldestOrder)
|
|
log.Debug("Reject order maker", "lending id ", oldestOrder.LendingId, "err", err)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
continue
|
|
} else { // reject Taker
|
|
rejects = append(rejects, order)
|
|
quantityToTrade = lendingstate.Zero
|
|
break
|
|
}
|
|
}
|
|
} else if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if tradedQuantity.Sign() == 0 && !rejectMaker {
|
|
log.Debug("Reject order Taker ", "tradedQuantity", tradedQuantity, "rejectMaker", rejectMaker)
|
|
rejects = append(rejects, order)
|
|
quantityToTrade = lendingstate.Zero
|
|
break
|
|
}
|
|
if tradedQuantity.Sign() > 0 {
|
|
quantityToTrade = lendingstate.Sub(quantityToTrade, tradedQuantity)
|
|
err := lendingStateDB.SubAmountLendingItem(lendingOrderBook, orderId, Interest, tradedQuantity, side)
|
|
if err != nil {
|
|
log.Warn("processOrderList SubAmountLendingItem", "err", err, "lendingOrderBook", lendingOrderBook, "orderId", orderId, "Interest", *Interest, "tradedQuantity", *tradedQuantity, "side", side)
|
|
}
|
|
log.Debug("Update quantity for orderId", "orderId", orderId.Hex())
|
|
log.Debug("LEND", "lendingOrderBook", lendingOrderBook.Hex(), "Taker Interest", Interest, "maker Interest", order.Interest, "Amount", tradedQuantity, "orderId", orderId, "side", side)
|
|
tradingId := lendingStateDB.GetTradeNonce(lendingOrderBook) + 1
|
|
liquidationTime := header.Time + order.Term
|
|
liquidationPrice := new(big.Int).Mul(collateralPrice, liquidationRate)
|
|
liquidationPrice = new(big.Int).Div(liquidationPrice, depositRate)
|
|
lendingTrade := lendingstate.LendingTrade{
|
|
TradeId: tradingId,
|
|
Term: oldestOrder.Term,
|
|
LendingToken: oldestOrder.LendingToken,
|
|
CollateralToken: collateralToken,
|
|
Amount: tradedQuantity,
|
|
LiquidationTime: liquidationTime,
|
|
LiquidationPrice: liquidationPrice,
|
|
Interest: oldestOrder.Interest.Uint64(),
|
|
DepositRate: depositRate,
|
|
LiquidationRate: liquidationRate,
|
|
RecallRate: recallRate,
|
|
CollateralLockedAmount: collateralLockedAmount,
|
|
}
|
|
lendingTrade.Status = lendingstate.TradeStatusOpen
|
|
lendingTrade.TakerOrderSide = order.Side
|
|
lendingTrade.TakerOrderType = order.Type
|
|
lendingTrade.MakerOrderType = oldestOrder.Type
|
|
lendingTrade.InvestingFee = lendingstate.Zero // current design: no investing fee
|
|
lendingTrade.CollateralPrice = collateralPrice
|
|
|
|
if order.Side == lendingstate.Borrowing {
|
|
// taker is a borrower
|
|
lendingTrade.BorrowingOrderHash = order.Hash
|
|
lendingTrade.InvestingOrderHash = oldestOrder.Hash
|
|
lendingTrade.BorrowingRelayer = order.Relayer
|
|
lendingTrade.InvestingRelayer = oldestOrder.Relayer
|
|
lendingTrade.Borrower = order.UserAddress
|
|
lendingTrade.Investor = oldestOrder.UserAddress
|
|
lendingTrade.AutoTopUp = order.AutoTopUp
|
|
// fee
|
|
if settleBalanceResult != nil {
|
|
lendingTrade.BorrowingFee = settleBalanceResult.Taker.Fee
|
|
}
|
|
} else if order.Side == lendingstate.Investing {
|
|
// taker is an investor
|
|
lendingTrade.BorrowingOrderHash = oldestOrder.Hash
|
|
lendingTrade.InvestingOrderHash = order.Hash
|
|
lendingTrade.BorrowingRelayer = oldestOrder.Relayer
|
|
lendingTrade.InvestingRelayer = order.Relayer
|
|
lendingTrade.Borrower = oldestOrder.UserAddress
|
|
lendingTrade.Investor = order.UserAddress
|
|
lendingTrade.AutoTopUp = oldestOrder.AutoTopUp
|
|
// fee
|
|
if settleBalanceResult != nil {
|
|
lendingTrade.BorrowingFee = settleBalanceResult.Maker.Fee
|
|
}
|
|
}
|
|
lendingTrade.Hash = lendingTrade.ComputeHash()
|
|
|
|
log.Debug("InsertTradingItem", "lendingOrderBook", lendingOrderBook.Hex(), "tradingId", tradingId, "lendingTrade", lendingTrade.Amount)
|
|
lendingStateDB.InsertTradingItem(lendingOrderBook, tradingId, lendingTrade)
|
|
log.Debug("InsertLiquidationTime", "lendingOrderBook", lendingOrderBook.Hex(), "tradingId", tradingId, "liquidationTime", liquidationTime)
|
|
lendingStateDB.InsertLiquidationTime(lendingOrderBook, new(big.Int).SetUint64(liquidationTime), tradingId)
|
|
log.Debug("SetTradeNonce", "lendingOrderBook", lendingOrderBook.Hex(), "nonce", tradingId+1)
|
|
lendingStateDB.SetTradeNonce(lendingOrderBook, tradingId)
|
|
log.Debug("InsertLiquidationPrice", "TradingOrderBookHash", tradingstate.GetTradingOrderBookHash(collateralToken, order.LendingToken).Hex(), "tradingId", tradingId, "lendingOrderBook", lendingOrderBook.Hex(), "liquidationPrice", liquidationPrice)
|
|
tradingStateDb.InsertLiquidationPrice(tradingstate.GetTradingOrderBookHash(collateralToken, order.LendingToken), liquidationPrice, lendingOrderBook, tradingId)
|
|
trades = append(trades, &lendingTrade)
|
|
}
|
|
if rejectMaker {
|
|
rejects = append(rejects, &oldestOrder)
|
|
err := lendingStateDB.CancelLendingOrder(lendingOrderBook, &oldestOrder)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
}
|
|
}
|
|
return quantityToTrade, trades, rejects, nil
|
|
}
|
|
|
|
func (l *Lending) getLendQuantity(
|
|
lendTokenXDCPrice,
|
|
collateralPrice,
|
|
depositRate,
|
|
borrowFee *big.Int,
|
|
coinbase common.Address, chain consensus.ChainContext, header *types.Header, statedb *state.StateDB, takerOrder *lendingstate.LendingItem, makerOrder *lendingstate.LendingItem, quantityToTrade *big.Int) (*big.Int, *big.Int, bool, *lendingstate.LendingSettleBalance, error) {
|
|
if collateralPrice == nil || collateralPrice.Sign() == 0 {
|
|
if takerOrder.Side == lendingstate.Borrowing {
|
|
log.Debug("Reject lending order taker , can not found collateral price ")
|
|
return lendingstate.Zero, lendingstate.Zero, false, nil, nil
|
|
} else {
|
|
log.Debug("Reject lending order maker , can not found collateral price ")
|
|
return lendingstate.Zero, lendingstate.Zero, true, nil, nil
|
|
}
|
|
}
|
|
LendingTokenDecimal, err := l.XDCx.GetTokenDecimal(chain, statedb, makerOrder.LendingToken)
|
|
if err != nil || LendingTokenDecimal.Sign() == 0 {
|
|
return lendingstate.Zero, lendingstate.Zero, false, nil, fmt.Errorf("fail to get tokenDecimal: Token: %v . Err: %v", makerOrder.LendingToken, err)
|
|
}
|
|
collateralToken := makerOrder.CollateralToken
|
|
if takerOrder.Side == lendingstate.Borrowing {
|
|
collateralToken = takerOrder.CollateralToken
|
|
}
|
|
collateralTokenDecimal, err := l.XDCx.GetTokenDecimal(chain, statedb, collateralToken)
|
|
if err != nil || collateralTokenDecimal.Sign() == 0 {
|
|
return lendingstate.Zero, lendingstate.Zero, false, nil, fmt.Errorf("fail to get tokenDecimal: Token: %v . Err: %v", collateralToken, err)
|
|
}
|
|
if takerOrder.Relayer == makerOrder.Relayer {
|
|
if err := lendingstate.CheckRelayerFee(takerOrder.Relayer, new(big.Int).Lsh(common.RelayerLendingFee, 1), statedb); err != nil {
|
|
log.Debug("Reject order Taker Exchnage = Maker Exchange , relayer not enough fee ", "err", err)
|
|
return lendingstate.Zero, lendingstate.Zero, false, nil, nil
|
|
}
|
|
} else {
|
|
if err := lendingstate.CheckRelayerFee(takerOrder.Relayer, common.RelayerLendingFee, statedb); err != nil {
|
|
log.Debug("Reject order Taker , relayer not enough fee ", "err", err)
|
|
return lendingstate.Zero, lendingstate.Zero, false, nil, nil
|
|
}
|
|
if err := lendingstate.CheckRelayerFee(makerOrder.Relayer, common.RelayerLendingFee, statedb); err != nil {
|
|
log.Debug("Reject order maker , relayer not enough fee ", "err", err)
|
|
return lendingstate.Zero, lendingstate.Zero, true, nil, nil
|
|
}
|
|
}
|
|
var takerBalance, makerBalance *big.Int
|
|
var lendToken common.Address
|
|
switch takerOrder.Side {
|
|
case lendingstate.Borrowing:
|
|
takerBalance = lendingstate.GetTokenBalance(takerOrder.UserAddress, takerOrder.CollateralToken, statedb)
|
|
makerBalance = lendingstate.GetTokenBalance(makerOrder.UserAddress, takerOrder.LendingToken, statedb)
|
|
lendToken = takerOrder.LendingToken
|
|
case lendingstate.Investing:
|
|
takerBalance = lendingstate.GetTokenBalance(takerOrder.UserAddress, makerOrder.LendingToken, statedb)
|
|
makerBalance = lendingstate.GetTokenBalance(makerOrder.UserAddress, makerOrder.CollateralToken, statedb)
|
|
lendToken = makerOrder.LendingToken
|
|
default:
|
|
takerBalance = big.NewInt(0)
|
|
makerBalance = big.NewInt(0)
|
|
}
|
|
quantity, rejectMaker := GetLendQuantity(takerOrder.Side, collateralTokenDecimal, depositRate, collateralPrice, takerBalance, makerBalance, quantityToTrade)
|
|
log.Debug("GetLendQuantity", "side", takerOrder.Side, "takerBalance", takerBalance, "makerBalance", makerBalance, "LendingToken", makerOrder.LendingToken, "CollateralToken", collateralToken, "quantity", quantity, "rejectMaker", rejectMaker)
|
|
if quantity.Sign() > 0 {
|
|
// Apply Match Order
|
|
isXDCXLendingFork := chain.Config().IsTIPXDCXLending(header.Number)
|
|
settleBalanceResult, err := lendingstate.GetSettleBalance(isXDCXLendingFork, takerOrder.Side, lendTokenXDCPrice, collateralPrice, depositRate, borrowFee, lendToken, collateralToken, LendingTokenDecimal, collateralTokenDecimal, quantity)
|
|
log.Debug("GetSettleBalance", "settleBalanceResult", settleBalanceResult, "err", err)
|
|
if err == nil {
|
|
err = DoSettleBalance(coinbase, takerOrder, makerOrder, settleBalanceResult, statedb)
|
|
}
|
|
if err != nil {
|
|
return quantity, lendingstate.Zero, rejectMaker, nil, err
|
|
}
|
|
return quantity, settleBalanceResult.CollateralLockedAmount, rejectMaker, settleBalanceResult, nil
|
|
}
|
|
return quantity, lendingstate.Zero, rejectMaker, nil, nil
|
|
}
|
|
|
|
func GetLendQuantity(takerSide string, collateralTokenDecimal *big.Int, depositRate *big.Int, collateralPrice *big.Int, takerBalance *big.Int, makerBalance *big.Int, quantityToLend *big.Int) (*big.Int, bool) {
|
|
if takerSide == lendingstate.Borrowing {
|
|
// taker = Borrower : takerOutTotal = CollateralLockedAmount = quantityToLend * collateral Token Decimal/ CollateralPrice * deposit rate
|
|
takerOutTotal := new(big.Int).Mul(quantityToLend, collateralTokenDecimal)
|
|
takerOutTotal = new(big.Int).Mul(takerOutTotal, depositRate)
|
|
takerOutTotal = new(big.Int).Div(takerOutTotal, big.NewInt(100)) // depositRate in percentage format
|
|
takerOutTotal = new(big.Int).Div(takerOutTotal, collateralPrice)
|
|
// Investor : makerOutTotal = quantityToLend
|
|
makerOutTotal := quantityToLend
|
|
if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
|
|
return quantityToLend, false
|
|
} else if takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
|
|
newQuantityLend := new(big.Int).Mul(takerBalance, collateralPrice)
|
|
newQuantityLend = new(big.Int).Mul(newQuantityLend, big.NewInt(100)) // depositRate in percentage format
|
|
newQuantityLend = new(big.Int).Div(newQuantityLend, depositRate)
|
|
newQuantityLend = new(big.Int).Div(newQuantityLend, collateralTokenDecimal)
|
|
if newQuantityLend.Sign() == 0 {
|
|
log.Debug("Reject lending order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "takerOutTotal", takerOutTotal)
|
|
}
|
|
return newQuantityLend, false
|
|
} else if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) < 0 {
|
|
log.Debug("Reject lending order maker , not enough balance ", "makerBalance", makerBalance, " makerOutTotal", makerOutTotal)
|
|
return makerBalance, true
|
|
} else {
|
|
// takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) < 0
|
|
newQuantityLend := new(big.Int).Mul(takerBalance, collateralPrice)
|
|
newQuantityLend = new(big.Int).Mul(newQuantityLend, big.NewInt(100)) // depositRate in percentage format
|
|
newQuantityLend = new(big.Int).Div(newQuantityLend, depositRate)
|
|
newQuantityLend = new(big.Int).Div(newQuantityLend, collateralTokenDecimal)
|
|
if newQuantityLend.Cmp(makerBalance) <= 0 {
|
|
if newQuantityLend.Sign() == 0 {
|
|
log.Debug("Reject lending order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityLend ", newQuantityLend)
|
|
}
|
|
return newQuantityLend, false
|
|
}
|
|
log.Debug("Reject lending order maker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityLend ", newQuantityLend)
|
|
return makerBalance, true
|
|
}
|
|
} else {
|
|
// maker = Borrower : makerOutTotal = CollateralLockedAmount = quantityToLend * collateral Token Decimal / CollateralPrice * deposit rate
|
|
makerOutTotal := new(big.Int).Mul(quantityToLend, collateralTokenDecimal)
|
|
makerOutTotal = new(big.Int).Mul(makerOutTotal, depositRate)
|
|
makerOutTotal = new(big.Int).Div(makerOutTotal, big.NewInt(100)) // depositRate in percentage format
|
|
makerOutTotal = new(big.Int).Div(makerOutTotal, collateralPrice)
|
|
// Investor : makerOutTotal = quantityToLend
|
|
takerOutTotal := quantityToLend
|
|
if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
|
|
return quantityToLend, false
|
|
} else if takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
|
|
if takerBalance.Sign() == 0 {
|
|
log.Debug("Reject lending order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "takerOutTotal", takerOutTotal)
|
|
}
|
|
return takerBalance, false
|
|
} else if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) < 0 {
|
|
newQuantityLend := new(big.Int).Mul(makerBalance, collateralPrice)
|
|
newQuantityLend = new(big.Int).Mul(newQuantityLend, big.NewInt(100)) // depositRate in percentage format
|
|
newQuantityLend = new(big.Int).Div(newQuantityLend, depositRate)
|
|
newQuantityLend = new(big.Int).Div(newQuantityLend, collateralTokenDecimal)
|
|
log.Debug("Reject lending order maker , not enough balance ", "makerBalance", makerBalance, " makerOutTotal", makerOutTotal)
|
|
return newQuantityLend, true
|
|
} else {
|
|
// takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) < 0
|
|
newQuantityLend := new(big.Int).Mul(makerBalance, collateralPrice)
|
|
newQuantityLend = new(big.Int).Mul(newQuantityLend, big.NewInt(100)) // depositRate in percentage format
|
|
newQuantityLend = new(big.Int).Div(newQuantityLend, depositRate)
|
|
newQuantityLend = new(big.Int).Div(newQuantityLend, collateralTokenDecimal)
|
|
if newQuantityLend.Cmp(takerBalance) <= 0 {
|
|
log.Debug("Reject lending order maker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityLend ", newQuantityLend)
|
|
return newQuantityLend, true
|
|
}
|
|
if takerBalance.Sign() == 0 {
|
|
log.Debug("Reject lending order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityLend ", newQuantityLend)
|
|
}
|
|
return takerBalance, false
|
|
}
|
|
}
|
|
}
|
|
|
|
func DoSettleBalance(coinbase common.Address, takerOrder, makerOrder *lendingstate.LendingItem, settleBalance *lendingstate.LendingSettleBalance, statedb *state.StateDB) error {
|
|
takerExOwner := lendingstate.GetRelayerOwner(takerOrder.Relayer, statedb)
|
|
makerExOwner := lendingstate.GetRelayerOwner(makerOrder.Relayer, statedb)
|
|
matchingFee := big.NewInt(0)
|
|
// masternodes only charge borrower relayer fee
|
|
matchingFee = new(big.Int).Add(matchingFee, common.RelayerLendingFee)
|
|
|
|
if takerExOwner.Hash().IsZero() || makerExOwner.Hash().IsZero() {
|
|
return fmt.Errorf("empty echange owner: taker: %v , maker : %v", takerExOwner, makerExOwner)
|
|
}
|
|
mapBalances := map[common.Address]map[common.Address]*big.Int{}
|
|
//Checking balance
|
|
if takerOrder.Side == lendingstate.Borrowing {
|
|
relayerFee, err := lendingstate.CheckSubRelayerFee(takerOrder.Relayer, common.RelayerLendingFee, statedb, map[common.Address]*big.Int{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lendingstate.SetSubRelayerFee(takerOrder.Relayer, relayerFee, common.RelayerLendingFee, statedb)
|
|
newTakerInTotal, err := lendingstate.CheckAddTokenBalance(takerOrder.UserAddress, settleBalance.Taker.InTotal, settleBalance.Taker.InToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mapBalances[settleBalance.Taker.InToken] == nil {
|
|
mapBalances[settleBalance.Taker.InToken] = map[common.Address]*big.Int{}
|
|
}
|
|
mapBalances[settleBalance.Taker.InToken][takerOrder.UserAddress] = newTakerInTotal
|
|
newTakerOutTotal, err := lendingstate.CheckSubTokenBalance(takerOrder.UserAddress, settleBalance.Taker.OutTotal, settleBalance.Taker.OutToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mapBalances[settleBalance.Taker.OutToken] == nil {
|
|
mapBalances[settleBalance.Taker.OutToken] = map[common.Address]*big.Int{}
|
|
}
|
|
mapBalances[settleBalance.Taker.OutToken][takerOrder.UserAddress] = newTakerOutTotal
|
|
newMakerOutTotal, err := lendingstate.CheckSubTokenBalance(makerOrder.UserAddress, settleBalance.Maker.OutTotal, settleBalance.Maker.OutToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mapBalances[settleBalance.Maker.OutToken] == nil {
|
|
mapBalances[settleBalance.Maker.OutToken] = map[common.Address]*big.Int{}
|
|
}
|
|
mapBalances[settleBalance.Maker.OutToken][makerOrder.UserAddress] = newMakerOutTotal
|
|
newTakerFee, err := lendingstate.CheckAddTokenBalance(takerExOwner, settleBalance.Taker.Fee, settleBalance.Taker.InToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mapBalances[settleBalance.Taker.InToken][takerExOwner] = newTakerFee
|
|
|
|
newCollateralTokenLock, err := lendingstate.CheckAddTokenBalance(common.LendingLockAddressBinary, settleBalance.Taker.OutTotal, settleBalance.Taker.OutToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mapBalances[settleBalance.Taker.OutToken][common.LendingLockAddressBinary] = newCollateralTokenLock
|
|
} else {
|
|
relayerFee, err := lendingstate.CheckSubRelayerFee(makerOrder.Relayer, common.RelayerLendingFee, statedb, map[common.Address]*big.Int{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lendingstate.SetSubRelayerFee(makerOrder.Relayer, relayerFee, common.RelayerLendingFee, statedb)
|
|
newTakerOutTotal, err := lendingstate.CheckSubTokenBalance(takerOrder.UserAddress, settleBalance.Taker.OutTotal, settleBalance.Taker.OutToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mapBalances[settleBalance.Taker.OutToken] == nil {
|
|
mapBalances[settleBalance.Taker.OutToken] = map[common.Address]*big.Int{}
|
|
}
|
|
mapBalances[settleBalance.Taker.OutToken][takerOrder.UserAddress] = newTakerOutTotal
|
|
newMakerInTotal, err := lendingstate.CheckAddTokenBalance(makerOrder.UserAddress, settleBalance.Maker.InTotal, settleBalance.Maker.InToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mapBalances[settleBalance.Maker.InToken] == nil {
|
|
mapBalances[settleBalance.Maker.InToken] = map[common.Address]*big.Int{}
|
|
}
|
|
mapBalances[settleBalance.Maker.InToken][makerOrder.UserAddress] = newMakerInTotal
|
|
newMakerOutTotal, err := lendingstate.CheckSubTokenBalance(makerOrder.UserAddress, settleBalance.Maker.OutTotal, settleBalance.Maker.OutToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mapBalances[settleBalance.Maker.OutToken] == nil {
|
|
mapBalances[settleBalance.Maker.OutToken] = map[common.Address]*big.Int{}
|
|
}
|
|
mapBalances[settleBalance.Maker.OutToken][makerOrder.UserAddress] = newMakerOutTotal
|
|
newMakerFee, err := lendingstate.CheckAddTokenBalance(makerExOwner, settleBalance.Maker.Fee, settleBalance.Maker.InToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mapBalances[settleBalance.Maker.InToken][makerExOwner] = newMakerFee
|
|
|
|
newCollateralTokenLock, err := lendingstate.CheckAddTokenBalance(common.LendingLockAddressBinary, settleBalance.Maker.OutTotal, settleBalance.Maker.OutToken, statedb, mapBalances)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mapBalances[settleBalance.Maker.OutToken][common.LendingLockAddressBinary] = newCollateralTokenLock
|
|
}
|
|
masternodeOwner := statedb.GetOwner(coinbase)
|
|
statedb.AddBalance(masternodeOwner, matchingFee, tracing.BalanceChangeUnspecified)
|
|
for token, balances := range mapBalances {
|
|
for adrr, value := range balances {
|
|
err := lendingstate.SetTokenBalance(adrr, value, token, statedb)
|
|
if err != nil {
|
|
log.Warn("DoSettleBalance SetTokenBalance", "err", err, "addr", adrr, "value", *value, "token", token)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Lending) ProcessCancelOrder(header *types.Header, lendingStateDB *lendingstate.LendingStateDB, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, chain consensus.ChainContext, coinbase common.Address, lendingOrderBook common.Hash, order *lendingstate.LendingItem) (error, bool) {
|
|
originOrder := lendingStateDB.GetLendingOrder(lendingOrderBook, common.BigToHash(new(big.Int).SetUint64(order.LendingId)))
|
|
if originOrder == lendingstate.EmptyLendingOrder {
|
|
return fmt.Errorf("lendingOrder not found. Id: %v. LendToken: %s . Term: %v. CollateralToken: %v", order.LendingId, order.LendingToken.Hex(), order.Term, order.CollateralToken.Hex()), false
|
|
}
|
|
if originOrder.Hash != order.Hash {
|
|
return fmt.Errorf("invalid lending hash. GotHash: %s. ExpectedHash: %s . LendToken: %s . Term: %v. CollateralToken: %v", order.Hash.Hex(), originOrder.Hash.Hex(), order.LendingToken.Hex(), order.Term, order.CollateralToken.Hex()), false
|
|
}
|
|
if originOrder.UserAddress != order.UserAddress {
|
|
return fmt.Errorf("userAddress doesnot match. Expected: %s . Got: %s", originOrder.UserAddress.Hex(), order.UserAddress.Hex()), false
|
|
}
|
|
if err := lendingstate.CheckRelayerFee(originOrder.Relayer, common.RelayerLendingCancelFee, statedb); err != nil {
|
|
log.Debug("Relayer not enough fee when cancel order", "err", err)
|
|
return nil, true
|
|
}
|
|
lendTokenDecimal, err := l.XDCx.GetTokenDecimal(chain, statedb, originOrder.LendingToken)
|
|
if err != nil || lendTokenDecimal == nil || lendTokenDecimal.Sign() <= 0 {
|
|
log.Debug("Fail to get tokenDecimal ", "Token", originOrder.LendingToken, "err", err)
|
|
return err, false
|
|
}
|
|
var tokenBalance *big.Int
|
|
switch originOrder.Side {
|
|
case lendingstate.Investing:
|
|
tokenBalance = lendingstate.GetTokenBalance(originOrder.UserAddress, originOrder.LendingToken, statedb)
|
|
case lendingstate.Borrowing:
|
|
tokenBalance = lendingstate.GetTokenBalance(originOrder.UserAddress, originOrder.CollateralToken, statedb)
|
|
default:
|
|
log.Debug("Not found order side", "Side", originOrder.Side)
|
|
return nil, true
|
|
}
|
|
log.Debug("ProcessCancelOrder", "LendingToken", originOrder.LendingToken, "CollateralToken", originOrder.CollateralToken, "makerInterest", originOrder.Interest, "lendTokenDecimal", lendTokenDecimal, "quantity", originOrder.Quantity)
|
|
collateralPrice := common.BasePrice
|
|
collateralTokenDecimal := common.BasePrice
|
|
if originOrder.Side == lendingstate.Borrowing {
|
|
_, collateralPrice, err = l.GetCollateralPrices(header, chain, statedb, tradingStateDb, originOrder.CollateralToken, originOrder.LendingToken)
|
|
if err != nil || collateralPrice == nil || collateralPrice.Sign() <= 0 {
|
|
return err, false
|
|
}
|
|
collateralTokenDecimal, err = l.XDCx.GetTokenDecimal(chain, statedb, originOrder.CollateralToken)
|
|
if err != nil || collateralTokenDecimal == nil || collateralTokenDecimal.Sign() <= 0 {
|
|
log.Debug("Fail to get tokenDecimal ", "Token", originOrder.LendingToken, "err", err)
|
|
return err, false
|
|
}
|
|
}
|
|
feeRate := lendingstate.GetFee(statedb, originOrder.Relayer)
|
|
var tokenCancelFee, tokenPriceInXDC *big.Int
|
|
if !chain.Config().IsTIPXDCXCancellationFee(header.Number) {
|
|
tokenCancelFee = getCancelFeeV1(collateralTokenDecimal, collateralPrice, feeRate, &originOrder)
|
|
tokenPriceInXDC = common.Big0
|
|
} else {
|
|
tokenCancelFee, tokenPriceInXDC = l.getCancelFee(chain, statedb, tradingStateDb, &originOrder, feeRate)
|
|
}
|
|
|
|
if tokenBalance.Cmp(tokenCancelFee) < 0 {
|
|
log.Debug("User not enough balance when cancel order", "Side", originOrder.Side, "Interest", originOrder.Interest, "Quantity", originOrder.Quantity, "balance", tokenBalance, "fee", tokenCancelFee)
|
|
return nil, true
|
|
}
|
|
err = lendingStateDB.CancelLendingOrder(lendingOrderBook, &originOrder)
|
|
if err != nil {
|
|
log.Debug("Error when cancel order", "order", &originOrder)
|
|
return err, false
|
|
}
|
|
// relayers pay XDC for masternode
|
|
lendingstate.SubRelayerFee(originOrder.Relayer, common.RelayerLendingCancelFee, statedb)
|
|
masternodeOwner := statedb.GetOwner(coinbase)
|
|
statedb.AddBalance(masternodeOwner, common.RelayerLendingCancelFee, tracing.BalanceChangeUnspecified)
|
|
relayerOwner := lendingstate.GetRelayerOwner(originOrder.Relayer, statedb)
|
|
switch originOrder.Side {
|
|
case lendingstate.Investing:
|
|
// users pay token for relayer
|
|
err := lendingstate.SubTokenBalance(originOrder.UserAddress, tokenCancelFee, originOrder.LendingToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessCancelOrder SubTokenBalance", "err", err, "originOrder.UserAddress", originOrder.UserAddress, "tokenCancelFee", *tokenCancelFee, "originOrder.LendingToken", originOrder.LendingToken)
|
|
}
|
|
err = lendingstate.AddTokenBalance(relayerOwner, tokenCancelFee, originOrder.LendingToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessCancelOrder AddTokenBalance", "err", err, "relayerOwner", relayerOwner, "tokenCancelFee", *tokenCancelFee, "originOrder.LendingToken", originOrder.LendingToken)
|
|
}
|
|
case lendingstate.Borrowing:
|
|
// users pay token for relayer
|
|
err := lendingstate.SubTokenBalance(originOrder.UserAddress, tokenCancelFee, originOrder.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessCancelOrder SubTokenBalance", "err", err, "originOrder.UserAddress", originOrder.UserAddress, "tokenCancelFee", *tokenCancelFee, "originOrder.CollateralToken", originOrder.CollateralToken)
|
|
}
|
|
err = lendingstate.AddTokenBalance(relayerOwner, tokenCancelFee, originOrder.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessCancelOrder AddTokenBalance", "err", err, "relayerOwner", relayerOwner, "tokenCancelFee", *tokenCancelFee, "originOrder.CollateralToken", originOrder.CollateralToken)
|
|
}
|
|
default:
|
|
}
|
|
extraData, _ := json.Marshal(struct {
|
|
CancelFee string
|
|
TokenPriceInXDC string
|
|
}{
|
|
CancelFee: tokenCancelFee.Text(10),
|
|
TokenPriceInXDC: tokenPriceInXDC.Text(10),
|
|
})
|
|
order.ExtraData = string(extraData)
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func (l *Lending) ProcessTopUp(lendingStateDB *lendingstate.LendingStateDB, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, order *lendingstate.LendingItem) (bool, *lendingstate.LendingTrade, error) {
|
|
lendingTradeId := common.Uint64ToHash(order.LendingTradeId)
|
|
lendingBook := lendingstate.GetLendingOrderBookHash(order.LendingToken, order.Term)
|
|
lendingTrade := lendingStateDB.GetLendingTrade(lendingBook, lendingTradeId)
|
|
if lendingTrade == lendingstate.EmptyLendingTrade {
|
|
return true, nil, fmt.Errorf("process deposit for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId.Hex())
|
|
}
|
|
if order.UserAddress != lendingTrade.Borrower {
|
|
return true, nil, fmt.Errorf("ProcessTopUp: invalid userAddress . UserAddress: %s . Borrower: %s", order.UserAddress.Hex(), lendingTrade.Borrower.Hex())
|
|
}
|
|
if order.Relayer != lendingTrade.BorrowingRelayer {
|
|
return true, nil, fmt.Errorf("ProcessTopUp: invalid relayerAddress . Got: %s . Expect: %s", order.Relayer.Hex(), lendingTrade.BorrowingRelayer.Hex())
|
|
}
|
|
if order.Quantity.Sign() <= 0 || lendingTrade.TradeId != lendingTradeId.Big().Uint64() {
|
|
log.Debug("ProcessTopUp: invalid quantity", "Quantity", order.Quantity, "lendingTradeId", lendingTradeId.Hex())
|
|
return true, nil, nil
|
|
}
|
|
return l.ProcessTopUpLendingTrade(lendingStateDB, statedb, tradingStateDb, lendingTradeId, lendingBook, order.Quantity)
|
|
}
|
|
|
|
// return hash: hash of lendingTrade
|
|
func (l *Lending) ProcessRepay(header *types.Header, chain consensus.ChainContext, lendingStateDB *lendingstate.LendingStateDB, statedb *state.StateDB, tradingstateDB *tradingstate.TradingStateDB, lendingBook common.Hash, order *lendingstate.LendingItem) (trade *lendingstate.LendingTrade, err error) {
|
|
lendingTradeId := order.LendingTradeId
|
|
lendingTradeIdHash := common.Uint64ToHash(lendingTradeId)
|
|
lendingTrade := lendingStateDB.GetLendingTrade(lendingBook, lendingTradeIdHash)
|
|
if lendingTrade == lendingstate.EmptyLendingTrade || lendingTrade.TradeId != lendingTradeIdHash.Big().Uint64() {
|
|
return nil, fmt.Errorf("ProcessRepay for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId)
|
|
}
|
|
if order.UserAddress != lendingTrade.Borrower {
|
|
return nil, fmt.Errorf("ProcessRepay: invalid userAddress . UserAddress: %s . Borrower: %s", order.UserAddress.Hex(), lendingTrade.Borrower.Hex())
|
|
}
|
|
if order.Relayer != lendingTrade.BorrowingRelayer {
|
|
return nil, fmt.Errorf("ProcessRepay: invalid relayerAddress . Got: %s . Expect: %s", order.Relayer.Hex(), lendingTrade.BorrowingRelayer.Hex())
|
|
}
|
|
return l.ProcessRepayLendingTrade(header, chain, lendingStateDB, statedb, tradingstateDB, lendingBook, lendingTradeId)
|
|
}
|
|
|
|
// return liquidatedTrade
|
|
func (l *Lending) LiquidationExpiredTrade(header *types.Header, chain consensus.ChainContext, lendingStateDB *lendingstate.LendingStateDB, statedb *state.StateDB, tradingstateDB *tradingstate.TradingStateDB, lendingBook common.Hash, lendingTradeId uint64) (*lendingstate.LendingTrade, error) {
|
|
lendingTradeIdHash := common.Uint64ToHash(lendingTradeId)
|
|
lendingTrade := lendingStateDB.GetLendingTrade(lendingBook, lendingTradeIdHash)
|
|
if lendingTrade.TradeId != lendingTradeId {
|
|
return nil, fmt.Errorf("Lending Trade Id not found : %d ", lendingTradeId)
|
|
}
|
|
repayAmount := lendingTrade.CollateralLockedAmount
|
|
|
|
_, collateralPrice, err := l.GetCollateralPrices(header, chain, statedb, tradingstateDB, lendingTrade.CollateralToken, lendingTrade.LendingToken)
|
|
if err != nil || collateralPrice == nil || collateralPrice.Sign() <= 0 {
|
|
// if cannot get collateralPrice, liquidate all collateral
|
|
log.Error("LiquidationExpiredTrade: cannot get collateralPrice", "err", err)
|
|
} else {
|
|
// repayAmount= CollateralLockedAmount * LiquidationPrice / collateralPrice + interestAmount
|
|
repayAmount = new(big.Int).Mul(lendingTrade.CollateralLockedAmount, lendingTrade.LiquidationPrice)
|
|
repayAmount = new(big.Int).Div(repayAmount, collateralPrice)
|
|
_, liquidationRate, _ := lendingstate.GetCollateralDetail(statedb, lendingTrade.CollateralToken)
|
|
collateralAmount := new(big.Int).Mul(repayAmount, big.NewInt(100))
|
|
collateralAmount = new(big.Int).Div(collateralAmount, liquidationRate)
|
|
totalCollateralAmount := lendingstate.CalculateTotalRepayValue(header.Time, lendingTrade.LiquidationTime, lendingTrade.Term, lendingTrade.Interest, collateralAmount)
|
|
interestAmount := new(big.Int).Sub(totalCollateralAmount, collateralAmount)
|
|
repayAmount = new(big.Int).Add(repayAmount, interestAmount)
|
|
}
|
|
|
|
recallAmount := common.Big0
|
|
if repayAmount.Cmp(lendingTrade.CollateralLockedAmount) < 0 {
|
|
recallAmount = new(big.Int).Sub(lendingTrade.CollateralLockedAmount, repayAmount)
|
|
err := lendingstate.AddTokenBalance(lendingTrade.Borrower, recallAmount, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("LiquidationExpiredTrade AddTokenBalance", "err", err, "lendingTrade.Borrower", lendingTrade.Borrower, "recallAmount", *recallAmount, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
} else {
|
|
repayAmount = lendingTrade.CollateralLockedAmount
|
|
}
|
|
err = lendingstate.SubTokenBalance(common.LendingLockAddressBinary, lendingTrade.CollateralLockedAmount, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("LiquidationExpiredTrade SubTokenBalance", "err", err, "LendingLockAddress", common.LendingLockAddressBinary, "lendingTrade.CollateralLockedAmount", *lendingTrade.CollateralLockedAmount, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
err = lendingstate.AddTokenBalance(lendingTrade.Investor, repayAmount, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("LiquidationExpiredTrade AddTokenBalance", "err", err, "lendingTrade.Investor", lendingTrade.Investor, "repayAmount", repayAmount, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
|
|
err = lendingStateDB.RemoveLiquidationTime(lendingBook, lendingTradeId, lendingTrade.LiquidationTime)
|
|
if err != nil {
|
|
log.Debug("LiquidationTrade RemoveLiquidationTime", "err", err)
|
|
return nil, err
|
|
}
|
|
err = tradingstateDB.RemoveLiquidationPrice(tradingstate.GetTradingOrderBookHash(lendingTrade.CollateralToken, lendingTrade.LendingToken), lendingTrade.LiquidationPrice, lendingBook, lendingTradeId)
|
|
if err != nil {
|
|
log.Debug("LiquidationTrade RemoveLiquidationPrice", "err", err)
|
|
return nil, err
|
|
}
|
|
err = lendingStateDB.CancelLendingTrade(lendingBook, lendingTradeId)
|
|
if err != nil {
|
|
log.Debug("LiquidationTrade CancelLendingTrade", "err", err)
|
|
return nil, err
|
|
}
|
|
// update liquidationData mongodb
|
|
liquidationData := lendingstate.LiquidationData{
|
|
RecallAmount: recallAmount,
|
|
LiquidationAmount: repayAmount,
|
|
CollateralPrice: collateralPrice,
|
|
Reason: lendingstate.LiquidatedByTime,
|
|
}
|
|
extraData, _ := json.Marshal(liquidationData)
|
|
lendingTrade.ExtraData = string(extraData)
|
|
return &lendingTrade, nil
|
|
}
|
|
|
|
// return liquidatedTrade
|
|
func (l *Lending) LiquidationTrade(lendingStateDB *lendingstate.LendingStateDB, statedb *state.StateDB, tradingstateDB *tradingstate.TradingStateDB, lendingBook common.Hash, lendingTradeId uint64) (*lendingstate.LendingTrade, error) {
|
|
lendingTradeIdHash := common.Uint64ToHash(lendingTradeId)
|
|
lendingTrade := lendingStateDB.GetLendingTrade(lendingBook, lendingTradeIdHash)
|
|
if lendingTrade.TradeId != lendingTradeId {
|
|
return nil, fmt.Errorf("Lending Trade Id not found : %d ", lendingTradeId)
|
|
}
|
|
err := lendingstate.SubTokenBalance(common.LendingLockAddressBinary, lendingTrade.CollateralLockedAmount, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("LiquidationTrade SubTokenBalance", "err", err, "LendingLockAddress", common.LendingLockAddressBinary, "lendingTrade.CollateralLockedAmount", *lendingTrade.CollateralLockedAmount, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
err = lendingstate.AddTokenBalance(lendingTrade.Investor, lendingTrade.CollateralLockedAmount, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("LiquidationTrade AddTokenBalance", "err", err, "lendingTrade.Investor", lendingTrade.Investor, "lendingTrade.CollateralLockedAmount", *lendingTrade.CollateralLockedAmount, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
err = lendingStateDB.RemoveLiquidationTime(lendingBook, lendingTradeId, lendingTrade.LiquidationTime)
|
|
if err != nil {
|
|
log.Debug("LiquidationTrade RemoveLiquidationTime", "err", err)
|
|
return nil, err
|
|
}
|
|
err = tradingstateDB.RemoveLiquidationPrice(tradingstate.GetTradingOrderBookHash(lendingTrade.CollateralToken, lendingTrade.LendingToken), lendingTrade.LiquidationPrice, lendingBook, lendingTradeId)
|
|
if err != nil {
|
|
log.Debug("LiquidationTrade RemoveLiquidationPrice", "err", err)
|
|
return nil, err
|
|
}
|
|
err = lendingStateDB.CancelLendingTrade(lendingBook, lendingTradeId)
|
|
if err != nil {
|
|
log.Debug("LiquidationTrade CancelLendingTrade", "err", err)
|
|
return nil, err
|
|
}
|
|
return &lendingTrade, nil
|
|
}
|
|
|
|
// cancellation fee = 1/10 borrowing fee
|
|
// deprecated after hardfork at TIPXDCXCancellationFee
|
|
func getCancelFeeV1(collateralTokenDecimal *big.Int, collateralPrice, borrowFee *big.Int, order *lendingstate.LendingItem) *big.Int {
|
|
var cancelFee *big.Int
|
|
if order.Side == lendingstate.Investing {
|
|
// cancel fee = quantityToLend*borrowFee/LendingCancelFee
|
|
cancelFee = new(big.Int).Mul(order.Quantity, borrowFee)
|
|
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
|
|
} else {
|
|
//Fee = quantityToLend * collateralTokenDecimal/collateralPrice *borrowFee/LendingCancelFee
|
|
cancelFee = new(big.Int).Mul(order.Quantity, collateralTokenDecimal)
|
|
cancelFee = new(big.Int).Mul(cancelFee, borrowFee)
|
|
cancelFee = new(big.Int).Div(cancelFee, collateralPrice)
|
|
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
|
|
}
|
|
return cancelFee
|
|
}
|
|
|
|
// return tokenQuantity, tokenPriceInXDC
|
|
func (l *Lending) getCancelFee(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, order *lendingstate.LendingItem, feeRate *big.Int) (*big.Int, *big.Int) {
|
|
if feeRate == nil || feeRate.Sign() == 0 {
|
|
return common.Big0, common.Big0
|
|
}
|
|
var cancelFee, tokenPriceInXDC *big.Int
|
|
var err error
|
|
if order.Side == lendingstate.Investing {
|
|
cancelFee, tokenPriceInXDC, err = l.XDCx.ConvertXDCToToken(chain, statedb, tradingStateDb, order.LendingToken, common.RelayerLendingCancelFee)
|
|
} else {
|
|
cancelFee, tokenPriceInXDC, err = l.XDCx.ConvertXDCToToken(chain, statedb, tradingStateDb, order.CollateralToken, common.RelayerLendingCancelFee)
|
|
}
|
|
if err != nil {
|
|
return common.Big0, common.Big0
|
|
}
|
|
return cancelFee, tokenPriceInXDC
|
|
}
|
|
|
|
func (l *Lending) GetMediumTradePriceBeforeEpoch(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, baseToken common.Address, quoteToken common.Address) (*big.Int, error) {
|
|
price := tradingStateDb.GetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
|
|
if price != nil && price.Sign() > 0 {
|
|
log.Debug("getMediumTradePriceBeforeEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "price", price)
|
|
return price, nil
|
|
} else {
|
|
inversePrice := tradingStateDb.GetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(quoteToken, baseToken))
|
|
log.Debug("getMediumTradePriceBeforeEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "inversePrice", inversePrice)
|
|
if inversePrice != nil && inversePrice.Sign() > 0 {
|
|
quoteTokenDecimal, err := l.XDCx.GetTokenDecimal(chain, statedb, quoteToken)
|
|
if err != nil || quoteTokenDecimal.Sign() == 0 {
|
|
return nil, fmt.Errorf("fail to get tokenDecimal: Token: %v . Err: %v", quoteToken, err)
|
|
}
|
|
baseTokenDecimal, err := l.XDCx.GetTokenDecimal(chain, statedb, baseToken)
|
|
if err != nil || baseTokenDecimal.Sign() == 0 {
|
|
return nil, fmt.Errorf("fail to get tokenDecimal: Token: %v . Err: %v", baseToken, err)
|
|
}
|
|
price = new(big.Int).Mul(baseTokenDecimal, quoteTokenDecimal)
|
|
price = new(big.Int).Div(price, inversePrice)
|
|
log.Debug("getMediumTradePriceBeforeEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "baseTokenDecimal", baseTokenDecimal, "quoteTokenDecimal", quoteTokenDecimal, "inversePrice", inversePrice)
|
|
return price, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// LendToken and CollateralToken must meet at least one of following conditions
|
|
// - Have direct pair in XDCX: lendToken/CollateralToken or CollateralToken/LendToken
|
|
// - Have pairs with XDC:
|
|
// - lendToken/XDC and CollateralToken/XDC
|
|
// - XDC/lendToken and XDC/CollateralToken
|
|
func (l *Lending) GetCollateralPrices(header *types.Header, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, collateralToken common.Address, lendingToken common.Address) (*big.Int, *big.Int, error) {
|
|
// lendTokenXDCPrice: price of ticker lendToken/XDC
|
|
// collateralXDCPrice: price of ticker collateralToken/XDC
|
|
// collateralPrice: price of ticker collateralToken/lendToken
|
|
|
|
collateralPriceFromContract, updatedBlock := lendingstate.GetCollateralPrice(statedb, collateralToken, lendingToken)
|
|
collateralPriceUpdatedFromContract := updatedBlock.Uint64()/chain.Config().XDPoS.Epoch == header.Number.Uint64()/chain.Config().XDPoS.Epoch
|
|
|
|
lendTokenXDCPrice, err := l.GetXDCBasePrices(header, chain, statedb, tradingStateDb, lendingToken)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if collateralPriceUpdatedFromContract {
|
|
log.Debug("Getting collateral/lending token price from contract", "price", collateralPriceFromContract)
|
|
return lendTokenXDCPrice, collateralPriceFromContract, nil
|
|
}
|
|
lendingTokenDecimal, err := l.XDCx.GetTokenDecimal(chain, statedb, lendingToken)
|
|
log.Debug("GetTokenDecimal", "lendingToken", lendingToken, "err", err)
|
|
if err != nil || lendingTokenDecimal == nil || lendingTokenDecimal.Sign() == 0 {
|
|
return nil, nil, err
|
|
}
|
|
collateralTokenDecimal, err := l.XDCx.GetTokenDecimal(chain, statedb, collateralToken)
|
|
log.Debug("GetTokenDecimal", "collateralToken", collateralToken, "err", err)
|
|
if err != nil || collateralTokenDecimal == nil || collateralTokenDecimal.Sign() == 0 {
|
|
return nil, nil, err
|
|
}
|
|
var collateralPrice *big.Int
|
|
inverseCollateralPriceFromContract, updatedBlock := lendingstate.GetCollateralPrice(statedb, lendingToken, collateralToken)
|
|
inverseCollateralPriceUpdatedFromContract := updatedBlock.Uint64()/chain.Config().XDPoS.Epoch == header.Number.Uint64()/chain.Config().XDPoS.Epoch
|
|
if inverseCollateralPriceUpdatedFromContract {
|
|
log.Debug("Getting lending/collateral token price from contract", "price", inverseCollateralPriceFromContract)
|
|
collateralPrice = new(big.Int).Mul(lendingTokenDecimal, collateralTokenDecimal)
|
|
collateralPrice = new(big.Int).Div(collateralPrice, inverseCollateralPriceFromContract)
|
|
return lendTokenXDCPrice, collateralPrice, nil
|
|
}
|
|
// if contract doesn't provide any price information
|
|
// getting price from pair in XDCx
|
|
lastAveragePrice, err := l.GetMediumTradePriceBeforeEpoch(chain, statedb, tradingStateDb, collateralToken, lendingToken)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if lastAveragePrice != nil && lastAveragePrice.Sign() > 0 {
|
|
log.Debug("Getting collateral/lending from direct pair in XDCx", "lendToken", lendingToken.Hex(), "collateralToken", collateralToken.Hex(), "price", lastAveragePrice)
|
|
return lendTokenXDCPrice, lastAveragePrice, nil
|
|
}
|
|
collateralXDCPrice, err := l.GetXDCBasePrices(header, chain, statedb, tradingStateDb, collateralToken)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if collateralXDCPrice == nil || lendTokenXDCPrice == nil {
|
|
return common.Big0, common.Big0, nil
|
|
}
|
|
// Calculate collateral/LendToken price from collateral/XDC, lendToken/XDC
|
|
collateralPrice = new(big.Int).Mul(collateralXDCPrice, lendingTokenDecimal)
|
|
collateralPrice = new(big.Int).Div(collateralPrice, lendTokenXDCPrice)
|
|
log.Debug("GetCollateralPrices: Calculate collateral/LendToken price from collateral/XDC, lendToken/XDC", "collateralPrice", collateralPrice,
|
|
"collateralXDCPrice", collateralXDCPrice, "lendingTokenDecimal", lendingTokenDecimal, "lendTokenXDCPrice", lendTokenXDCPrice)
|
|
return lendTokenXDCPrice, collateralPrice, nil
|
|
}
|
|
|
|
func (l *Lending) GetXDCBasePrices(header *types.Header, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, token common.Address) (*big.Int, error) {
|
|
tokenXDCPriceFromContract, updatedBlock := lendingstate.GetCollateralPrice(statedb, token, common.XDCNativeAddressBinary)
|
|
tokenXDCPriceUpdatedFromContract := updatedBlock.Uint64()/chain.Config().XDPoS.Epoch == header.Number.Uint64()/chain.Config().XDPoS.Epoch
|
|
|
|
if token == common.XDCNativeAddressBinary {
|
|
return common.BasePrice, nil
|
|
} else if tokenXDCPriceUpdatedFromContract {
|
|
// getting lendToken price from contract first
|
|
// otherwise, getting from XDCx lendToken/XDC
|
|
log.Debug("Getting token/XDC price from contract", "price", tokenXDCPriceFromContract)
|
|
return tokenXDCPriceFromContract, nil
|
|
} else {
|
|
XDCTokenPriceFromContract, updatedBlock := lendingstate.GetCollateralPrice(statedb, common.XDCNativeAddressBinary, token)
|
|
XDCTokenPriceUpdatedFromContract := updatedBlock.Uint64()/chain.Config().XDPoS.Epoch == header.Number.Uint64()/chain.Config().XDPoS.Epoch
|
|
if XDCTokenPriceUpdatedFromContract && XDCTokenPriceFromContract != nil && XDCTokenPriceFromContract.Sign() > 0 {
|
|
// getting lendToken price from contract first
|
|
// otherwise, getting from XDCx lendToken/XDC
|
|
log.Debug("Getting XDC/token from contract", "price", XDCTokenPriceFromContract)
|
|
tokenDecimal, err := l.XDCx.GetTokenDecimal(chain, statedb, token)
|
|
log.Debug("GetTokenDecimal", "token", token.Hex(), "err", err)
|
|
if err != nil || tokenDecimal == nil || tokenDecimal.Sign() == 0 {
|
|
return nil, err
|
|
}
|
|
tokenXDCPrice := new(big.Int).Mul(common.BasePrice, tokenDecimal)
|
|
tokenXDCPrice = new(big.Int).Div(tokenXDCPrice, XDCTokenPriceFromContract)
|
|
return tokenXDCPrice, nil
|
|
}
|
|
tokenXDCPrice, err := l.GetMediumTradePriceBeforeEpoch(chain, statedb, tradingStateDb, token, common.XDCNativeAddressBinary)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if tokenXDCPrice != nil && tokenXDCPrice.Sign() > 0 {
|
|
log.Debug("Getting token/XDC from XDCx", "price", tokenXDCPrice, "err", err)
|
|
return tokenXDCPrice, nil
|
|
}
|
|
}
|
|
log.Debug("Can't getting tokenXDCPrice ", "token", token.Hex())
|
|
return nil, nil
|
|
}
|
|
|
|
func (l *Lending) AutoTopUp(statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, lendingState *lendingstate.LendingStateDB, lendingBook, lendingTradeId common.Hash, currentPrice *big.Int) (*lendingstate.LendingTrade, error) {
|
|
lendingTrade := lendingState.GetLendingTrade(lendingBook, lendingTradeId)
|
|
if lendingTrade == lendingstate.EmptyLendingTrade {
|
|
return nil, fmt.Errorf("process deposit for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId.Hex())
|
|
}
|
|
if currentPrice.Cmp(lendingTrade.LiquidationPrice) >= 0 {
|
|
return nil, fmt.Errorf("currentPrice is still higher than or equal to LiquidationPrice. current price: %v , liquidation price : %v ", currentPrice, lendingTrade.LiquidationPrice)
|
|
}
|
|
// newLiquidationPrice = currentPrice * 90%
|
|
newLiquidationPrice := new(big.Int).Mul(currentPrice, common.RateTopUp)
|
|
newLiquidationPrice = new(big.Int).Div(newLiquidationPrice, common.BaseTopUp)
|
|
// newLockedAmount = CollateralLockedAmount * LiquidationPrice / newLiquidationPrice
|
|
newLockedAmount := new(big.Int).Mul(lendingTrade.CollateralLockedAmount, lendingTrade.LiquidationPrice)
|
|
newLockedAmount = new(big.Int).Div(newLockedAmount, newLiquidationPrice)
|
|
|
|
requiredDepositAmount := new(big.Int).Sub(newLockedAmount, lendingTrade.CollateralLockedAmount)
|
|
tokenBalance := lendingstate.GetTokenBalance(lendingTrade.Borrower, lendingTrade.CollateralToken, statedb)
|
|
if tokenBalance.Cmp(requiredDepositAmount) < 0 {
|
|
return nil, fmt.Errorf("not enough balance to AutoTopUp. requiredDepositAmount: %v . tokenBalance: %v . Token: %s", requiredDepositAmount, tokenBalance, lendingTrade.CollateralToken.Hex())
|
|
}
|
|
_, newTrade, err := l.ProcessTopUpLendingTrade(lendingState, statedb, tradingState, lendingTradeId, lendingBook, requiredDepositAmount)
|
|
return newTrade, err
|
|
}
|
|
|
|
func (l *Lending) ProcessTopUpLendingTrade(lendingStateDB *lendingstate.LendingStateDB, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, lendingTradeId common.Hash, lendingBook common.Hash, quantity *big.Int) (bool, *lendingstate.LendingTrade, error) {
|
|
lendingTrade := lendingStateDB.GetLendingTrade(lendingBook, lendingTradeId)
|
|
if lendingTrade == lendingstate.EmptyLendingTrade {
|
|
return true, nil, fmt.Errorf("process deposit for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId.Hex())
|
|
}
|
|
tokenBalance := lendingstate.GetTokenBalance(lendingTrade.Borrower, lendingTrade.CollateralToken, statedb)
|
|
if tokenBalance.Cmp(quantity) < 0 {
|
|
log.Debug("not enough balance deposit", "Quantity", quantity, "tokenBalance", tokenBalance)
|
|
return true, nil, fmt.Errorf("not enough balance deposit. lendingTradeId: %v , Quantity : %v , tokenBalance : %v", lendingTradeId.Hex(), quantity, tokenBalance)
|
|
}
|
|
err := tradingStateDb.RemoveLiquidationPrice(tradingstate.GetTradingOrderBookHash(lendingTrade.CollateralToken, lendingTrade.LendingToken), lendingTrade.LiquidationPrice, lendingBook, lendingTrade.TradeId)
|
|
if err != nil {
|
|
return true, nil, err
|
|
}
|
|
err = lendingstate.SubTokenBalance(lendingTrade.Borrower, quantity, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessTopUpLendingTrade SubTokenBalance", "err", err, "lendingTrade.Borrower", lendingTrade.Borrower, "quantity", *quantity, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
err = lendingstate.AddTokenBalance(common.LendingLockAddressBinary, quantity, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessTopUpLendingTrade AddTokenBalance", "err", err, "LendingLockAddress", common.LendingLockAddressBinary, "quantity", *quantity, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
oldLockedAmount := lendingTrade.CollateralLockedAmount
|
|
newLockedAmount := new(big.Int).Add(quantity, oldLockedAmount)
|
|
newLiquidationPrice := new(big.Int).Mul(lendingTrade.LiquidationPrice, oldLockedAmount)
|
|
newLiquidationPrice = new(big.Int).Div(newLiquidationPrice, newLockedAmount)
|
|
lendingStateDB.UpdateLiquidationPrice(lendingBook, lendingTrade.TradeId, newLiquidationPrice)
|
|
lendingStateDB.UpdateCollateralLockedAmount(lendingBook, lendingTrade.TradeId, newLockedAmount)
|
|
tradingStateDb.InsertLiquidationPrice(tradingstate.GetTradingOrderBookHash(lendingTrade.CollateralToken, lendingTrade.LendingToken), newLiquidationPrice, lendingBook, lendingTrade.TradeId)
|
|
newLendingTrade := lendingTrade
|
|
newLendingTrade.LiquidationPrice = newLiquidationPrice
|
|
newLendingTrade.CollateralLockedAmount = newLockedAmount
|
|
log.Debug("ProcessTopUp successfully", "price", newLiquidationPrice, "lockAmount", newLockedAmount)
|
|
return false, &newLendingTrade, nil
|
|
}
|
|
|
|
func (l *Lending) ProcessRepayLendingTrade(header *types.Header, chain consensus.ChainContext, lendingStateDB *lendingstate.LendingStateDB, statedb *state.StateDB, tradingstateDB *tradingstate.TradingStateDB, lendingBook common.Hash, lendingTradeId uint64) (trade *lendingstate.LendingTrade, err error) {
|
|
lendingTradeIdHash := common.Uint64ToHash(lendingTradeId)
|
|
lendingTrade := lendingStateDB.GetLendingTrade(lendingBook, lendingTradeIdHash)
|
|
if lendingTrade == lendingstate.EmptyLendingTrade {
|
|
return nil, fmt.Errorf("ProcessRepayLendingTrade for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId)
|
|
}
|
|
time := header.Time
|
|
tokenBalance := lendingstate.GetTokenBalance(lendingTrade.Borrower, lendingTrade.LendingToken, statedb)
|
|
paymentBalance := lendingstate.CalculateTotalRepayValue(time, lendingTrade.LiquidationTime, lendingTrade.Term, lendingTrade.Interest, lendingTrade.Amount)
|
|
log.Debug("ProcessRepay", "totalInterest", new(big.Int).Sub(paymentBalance, lendingTrade.Amount), "totalRepayValue", paymentBalance, "token", lendingTrade.LendingToken.Hex())
|
|
|
|
if tokenBalance.Cmp(paymentBalance) < 0 {
|
|
if lendingTrade.LiquidationTime > time {
|
|
return nil, fmt.Errorf("not enough balance need : %s , have : %s", paymentBalance, tokenBalance)
|
|
}
|
|
newLendingTrade := &lendingstate.LendingTrade{}
|
|
var err error
|
|
if chain.Config().IsTIPXDCXLending(header.Number) {
|
|
newLendingTrade, err = l.LiquidationExpiredTrade(header, chain, lendingStateDB, statedb, tradingstateDB, lendingBook, lendingTradeId)
|
|
} else {
|
|
newLendingTrade, err = l.LiquidationTrade(lendingStateDB, statedb, tradingstateDB, lendingBook, lendingTradeId)
|
|
liquidationData := lendingstate.LiquidationData{
|
|
RecallAmount: common.Big0,
|
|
LiquidationAmount: lendingTrade.CollateralLockedAmount,
|
|
CollateralPrice: common.Big0,
|
|
Reason: lendingstate.LiquidatedByTime,
|
|
}
|
|
extraData, _ := json.Marshal(liquidationData)
|
|
if newLendingTrade != nil {
|
|
newLendingTrade.ExtraData = string(extraData)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if newLendingTrade != nil {
|
|
newLendingTrade.Status = lendingstate.TradeStatusLiquidated
|
|
}
|
|
return newLendingTrade, err
|
|
} else {
|
|
err := lendingstate.SubTokenBalance(lendingTrade.Borrower, paymentBalance, lendingTrade.LendingToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessRepayLendingTrade SubTokenBalance", "err", err, "lendingTrade.Borrower", lendingTrade.Borrower, "paymentBalance", *paymentBalance, "lendingTrade.LendingToken", lendingTrade.LendingToken)
|
|
}
|
|
err = lendingstate.AddTokenBalance(lendingTrade.Investor, paymentBalance, lendingTrade.LendingToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessRepayLendingTrade AddTokenBalance", "err", err, "lendingTrade.Investor", lendingTrade.Investor, "paymentBalance", *paymentBalance, "lendingTrade.LendingToken", lendingTrade.LendingToken)
|
|
}
|
|
err = lendingstate.SubTokenBalance(common.LendingLockAddressBinary, lendingTrade.CollateralLockedAmount, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessRepayLendingTrade SubTokenBalance", "err", err, "LendingLockAddress", common.LendingLockAddressBinary, "lendingTrade.CollateralLockedAmount", *lendingTrade.CollateralLockedAmount, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
err = lendingstate.AddTokenBalance(lendingTrade.Borrower, lendingTrade.CollateralLockedAmount, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessRepayLendingTrade AddTokenBalance", "err", err, "lendingTrade.Borrower", lendingTrade.Borrower, "lendingTrade.CollateralLockedAmount", *lendingTrade.CollateralLockedAmount, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
|
|
err = lendingStateDB.RemoveLiquidationTime(lendingBook, lendingTradeId, lendingTrade.LiquidationTime)
|
|
if err != nil {
|
|
log.Debug("ProcessRepay RemoveLiquidationTime", "err", err, "lendingHash", lendingTrade.Hash, "trade", lendingstate.ToJSON(lendingTrade))
|
|
return nil, err
|
|
}
|
|
err = tradingstateDB.RemoveLiquidationPrice(tradingstate.GetTradingOrderBookHash(lendingTrade.CollateralToken, lendingTrade.LendingToken), lendingTrade.LiquidationPrice, lendingBook, lendingTradeId)
|
|
if err != nil {
|
|
log.Debug("ProcessRepay RemoveLiquidationPrice", "err", err)
|
|
return nil, err
|
|
}
|
|
err = lendingStateDB.CancelLendingTrade(lendingBook, lendingTradeId)
|
|
if err != nil {
|
|
log.Debug("ProcessRepay CancelLendingTrade", "err", err)
|
|
return nil, err
|
|
}
|
|
lendingTrade.Status = lendingstate.TradeStatusClosed
|
|
extraData, _ := json.Marshal(struct {
|
|
Profit *big.Int
|
|
}{
|
|
Profit: new(big.Int).Sub(paymentBalance, lendingTrade.Amount),
|
|
})
|
|
lendingTrade.ExtraData = string(extraData)
|
|
}
|
|
return &lendingTrade, nil
|
|
}
|
|
|
|
func (l *Lending) ProcessRecallLendingTrade(lendingStateDB *lendingstate.LendingStateDB, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, lendingBook common.Hash, lendingTradeId common.Hash, newLiquidationPrice *big.Int) (bool, *lendingstate.LendingTrade, error) {
|
|
log.Debug("ProcessRecallLendingTrade", "lendingTradeId", lendingTradeId.Hex(), "lendingBook", lendingBook.Hex(), "newLiquidationPrice", newLiquidationPrice)
|
|
lendingTrade := lendingStateDB.GetLendingTrade(lendingBook, lendingTradeId)
|
|
if lendingTrade == lendingstate.EmptyLendingTrade {
|
|
return true, nil, fmt.Errorf("process recall for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId.Hex())
|
|
}
|
|
if newLiquidationPrice.Cmp(lendingTrade.LiquidationPrice) <= 0 {
|
|
return true, nil, fmt.Errorf("New liquidation price must higher than old liquidation price. current liquidation price: %v , new liquidation price : %v ", lendingTrade.LiquidationPrice, newLiquidationPrice)
|
|
}
|
|
newLockedAmount := new(big.Int).Mul(lendingTrade.CollateralLockedAmount, lendingTrade.LiquidationPrice)
|
|
newLockedAmount = new(big.Int).Div(newLockedAmount, newLiquidationPrice)
|
|
recallAmount := new(big.Int).Sub(lendingTrade.CollateralLockedAmount, newLockedAmount)
|
|
log.Debug("ProcessRecallLendingTrade", "newLockedAmount", newLockedAmount, "recallAmount", recallAmount, "oldLiquidationPrice", lendingTrade.LiquidationPrice, "newLiquidationPrice", newLiquidationPrice)
|
|
err := tradingStateDb.RemoveLiquidationPrice(tradingstate.GetTradingOrderBookHash(lendingTrade.CollateralToken, lendingTrade.LendingToken), lendingTrade.LiquidationPrice, lendingBook, lendingTrade.TradeId)
|
|
if err != nil {
|
|
return true, nil, err
|
|
}
|
|
err = lendingstate.AddTokenBalance(lendingTrade.Borrower, recallAmount, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessRecallLendingTrade AddTokenBalance", "err", err, "lendingTrade.Borrower", lendingTrade.Borrower, "recallAmount", *recallAmount, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
err = lendingstate.SubTokenBalance(common.LendingLockAddressBinary, recallAmount, lendingTrade.CollateralToken, statedb)
|
|
if err != nil {
|
|
log.Warn("ProcessRecallLendingTrade SubTokenBalance", "err", err, "LendingLockAddress", common.LendingLockAddressBinary, "recallAmount", *recallAmount, "lendingTrade.CollateralToken", lendingTrade.CollateralToken)
|
|
}
|
|
|
|
lendingStateDB.UpdateLiquidationPrice(lendingBook, lendingTrade.TradeId, newLiquidationPrice)
|
|
lendingStateDB.UpdateCollateralLockedAmount(lendingBook, lendingTrade.TradeId, newLockedAmount)
|
|
tradingStateDb.InsertLiquidationPrice(tradingstate.GetTradingOrderBookHash(lendingTrade.CollateralToken, lendingTrade.LendingToken), newLiquidationPrice, lendingBook, lendingTrade.TradeId)
|
|
newLendingTrade := lendingTrade
|
|
newLendingTrade.LiquidationPrice = newLiquidationPrice
|
|
newLendingTrade.CollateralLockedAmount = newLockedAmount
|
|
log.Debug("ProcessRecall", "price", newLiquidationPrice, "lockAmount", newLockedAmount, "recall amount", recallAmount)
|
|
return false, &newLendingTrade, nil
|
|
}
|