mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-16 18:00:46 +00:00
- Solidity Upgraded up to v0.8.0 - Fixed and Added eth_chainId - Fix error in TransactionRecipet - Reward halving issue fixed
464 lines
17 KiB
Go
464 lines
17 KiB
Go
package lendingstate
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/XinFinOrg/XDPoSChain/common"
|
|
"github.com/XinFinOrg/XDPoSChain/core/state"
|
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
|
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
|
"github.com/globalsign/mgo/bson"
|
|
"math/big"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
Investing = "INVEST"
|
|
Borrowing = "BORROW"
|
|
TopUp = "TOPUP"
|
|
Repay = "REPAY"
|
|
Recall = "RECALL"
|
|
LendingStatusNew = "NEW"
|
|
LendingStatusOpen = "OPEN"
|
|
LendingStatusReject = "REJECTED"
|
|
LendingStatusFilled = "FILLED"
|
|
LendingStatusPartialFilled = "PARTIAL_FILLED"
|
|
LendingStatusCancelled = "CANCELLED"
|
|
Market = "MO"
|
|
Limit = "LO"
|
|
)
|
|
|
|
var ValidInputLendingStatus = map[string]bool{
|
|
LendingStatusNew: true,
|
|
LendingStatusCancelled: true,
|
|
}
|
|
|
|
var ValidInputLendingType = map[string]bool{
|
|
Market: true,
|
|
Limit: true,
|
|
Repay: true,
|
|
TopUp: true,
|
|
Recall: true,
|
|
}
|
|
|
|
// Signature struct
|
|
type Signature struct {
|
|
V byte `bson:"v" json:"v"`
|
|
R common.Hash `bson:"r" json:"r"`
|
|
S common.Hash `bson:"s" json:"s"`
|
|
}
|
|
|
|
type SignatureRecord struct {
|
|
V byte `bson:"v" json:"v"`
|
|
R string `bson:"r" json:"r"`
|
|
S string `bson:"s" json:"s"`
|
|
}
|
|
|
|
type LendingItem struct {
|
|
Quantity *big.Int `bson:"quantity" json:"quantity"`
|
|
Interest *big.Int `bson:"interest" json:"interest"`
|
|
Side string `bson:"side" json:"side"` // INVESTING/BORROWING
|
|
Type string `bson:"type" json:"type"` // LIMIT/MARKET
|
|
LendingToken common.Address `bson:"lendingToken" json:"lendingToken"`
|
|
CollateralToken common.Address `bson:"collateralToken" json:"collateralToken"`
|
|
AutoTopUp bool `bson:"autoTopUp" json:"autoTopUp"`
|
|
FilledAmount *big.Int `bson:"filledAmount" json:"filledAmount"`
|
|
Status string `bson:"status" json:"status"`
|
|
Relayer common.Address `bson:"relayer" json:"relayer"`
|
|
Term uint64 `bson:"term" json:"term"`
|
|
UserAddress common.Address `bson:"userAddress" json:"userAddress"`
|
|
Signature *Signature `bson:"signature" json:"signature"`
|
|
Hash common.Hash `bson:"hash" json:"hash"`
|
|
TxHash common.Hash `bson:"txHash" json:"txHash"`
|
|
Nonce *big.Int `bson:"nonce" json:"nonce"`
|
|
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
|
|
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
|
|
LendingId uint64 `bson:"lendingId" json:"lendingId"`
|
|
LendingTradeId uint64 `bson:"tradeId" json:"tradeId"`
|
|
ExtraData string `bson:"extraData" json:"extraData"`
|
|
}
|
|
|
|
type LendingItemBSON struct {
|
|
Quantity string `bson:"quantity" json:"quantity"`
|
|
Interest string `bson:"interest" json:"interest"`
|
|
Side string `bson:"side" json:"side"` // INVESTING/BORROWING
|
|
Type string `bson:"type" json:"type"` // LIMIT/MARKET
|
|
LendingToken string `bson:"lendingToken" json:"lendingToken"`
|
|
CollateralToken string `bson:"collateralToken" json:"collateralToken"`
|
|
AutoTopUp bool `bson:"autoTopUp" json:"autoTopUp"`
|
|
FilledAmount string `bson:"filledAmount" json:"filledAmount"`
|
|
Status string `bson:"status" json:"status"`
|
|
Relayer string `bson:"relayer" json:"relayer"`
|
|
Term string `bson:"term" json:"term"`
|
|
UserAddress string `bson:"userAddress" json:"userAddress"`
|
|
Signature *SignatureRecord `bson:"signature" json:"signature"`
|
|
Hash string `bson:"hash" json:"hash"`
|
|
TxHash string `bson:"txHash" json:"txHash"`
|
|
Nonce string `bson:"nonce" json:"nonce"`
|
|
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
|
|
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
|
|
LendingId string `bson:"lendingId" json:"lendingId"`
|
|
LendingTradeId string `bson:"tradeId" json:"tradeId"`
|
|
ExtraData string `bson:"extraData" json:"extraData"`
|
|
}
|
|
|
|
func (l *LendingItem) GetBSON() (interface{}, error) {
|
|
lr := LendingItemBSON{
|
|
Quantity: l.Quantity.String(),
|
|
Interest: l.Interest.String(),
|
|
Side: l.Side,
|
|
Type: l.Type,
|
|
LendingToken: l.LendingToken.Hex(),
|
|
CollateralToken: l.CollateralToken.Hex(),
|
|
AutoTopUp: l.AutoTopUp,
|
|
Status: l.Status,
|
|
Relayer: l.Relayer.Hex(),
|
|
Term: strconv.FormatUint(l.Term, 10),
|
|
UserAddress: l.UserAddress.Hex(),
|
|
Hash: l.Hash.Hex(),
|
|
TxHash: l.TxHash.Hex(),
|
|
Nonce: l.Nonce.String(),
|
|
CreatedAt: l.CreatedAt,
|
|
UpdatedAt: l.UpdatedAt,
|
|
LendingId: strconv.FormatUint(l.LendingId, 10),
|
|
LendingTradeId: strconv.FormatUint(l.LendingTradeId, 10),
|
|
ExtraData: l.ExtraData,
|
|
}
|
|
|
|
if l.FilledAmount != nil {
|
|
lr.FilledAmount = l.FilledAmount.String()
|
|
}
|
|
|
|
if l.Signature != nil {
|
|
lr.Signature = &SignatureRecord{
|
|
V: l.Signature.V,
|
|
R: l.Signature.R.Hex(),
|
|
S: l.Signature.S.Hex(),
|
|
}
|
|
}
|
|
|
|
return lr, nil
|
|
}
|
|
|
|
func (l *LendingItem) SetBSON(raw bson.Raw) error {
|
|
decoded := new(LendingItemBSON)
|
|
|
|
err := raw.Unmarshal(decoded)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if decoded.Quantity != "" {
|
|
l.Quantity = ToBigInt(decoded.Quantity)
|
|
}
|
|
l.Interest = ToBigInt(decoded.Interest)
|
|
l.Side = decoded.Side
|
|
l.Type = decoded.Type
|
|
l.LendingToken = common.HexToAddress(decoded.LendingToken)
|
|
l.CollateralToken = common.HexToAddress(decoded.CollateralToken)
|
|
l.AutoTopUp = decoded.AutoTopUp
|
|
l.FilledAmount = ToBigInt(decoded.FilledAmount)
|
|
l.Status = decoded.Status
|
|
l.Relayer = common.HexToAddress(decoded.Relayer)
|
|
term, err := strconv.ParseInt(decoded.Term, 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse lendingItem.term. Err: %v", err)
|
|
}
|
|
l.Term = uint64(term)
|
|
l.UserAddress = common.HexToAddress(decoded.UserAddress)
|
|
|
|
if decoded.Signature != nil {
|
|
l.Signature = &Signature{
|
|
V: byte(decoded.Signature.V),
|
|
R: common.HexToHash(decoded.Signature.R),
|
|
S: common.HexToHash(decoded.Signature.S),
|
|
}
|
|
}
|
|
|
|
l.Hash = common.HexToHash(decoded.Hash)
|
|
l.TxHash = common.HexToHash(decoded.TxHash)
|
|
l.Nonce = ToBigInt(decoded.Nonce)
|
|
|
|
l.CreatedAt = decoded.CreatedAt
|
|
l.UpdatedAt = decoded.UpdatedAt
|
|
lendingId, err := strconv.ParseInt(decoded.LendingId, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
l.LendingId = uint64(lendingId)
|
|
lendingTradeId, err := strconv.ParseInt(decoded.LendingTradeId, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
l.LendingTradeId = uint64(lendingTradeId)
|
|
l.ExtraData = decoded.ExtraData
|
|
return nil
|
|
}
|
|
|
|
func (l *LendingItem) VerifyLendingItem(state *state.StateDB) error {
|
|
if err := l.VerifyLendingStatus(); err != nil {
|
|
return err
|
|
}
|
|
if valid, _ := IsValidPair(state, l.Relayer, l.LendingToken, l.Term); valid == false {
|
|
return fmt.Errorf("invalid pair . LendToken %s . Term: %v", l.LendingToken.Hex(), l.Term)
|
|
}
|
|
if l.Status == LendingStatusNew {
|
|
if err := l.VerifyLendingType(); err != nil {
|
|
return err
|
|
}
|
|
if l.Type != Repay {
|
|
if err := l.VerifyLendingQuantity(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if l.Type == Limit || l.Type == Market {
|
|
if err := l.VerifyLendingSide(); err != nil {
|
|
return err
|
|
}
|
|
if l.Side == Borrowing {
|
|
if err := l.VerifyCollateral(state); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if l.Type == Limit {
|
|
if err := l.VerifyLendingInterest(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if !IsValidRelayer(state, l.Relayer) {
|
|
return fmt.Errorf("VerifyLendingItem: invalid relayer. address: %s", l.Relayer.Hex())
|
|
}
|
|
if err := l.VerifyLendingSignature(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LendingItem) VerifyLendingSide() error {
|
|
if l.Side != Borrowing && l.Side != Investing {
|
|
return fmt.Errorf("VerifyLendingSide: invalid side . Side: %s", l.Side)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LendingItem) VerifyCollateral(state *state.StateDB) error {
|
|
if l.CollateralToken.String() == EmptyAddress || l.CollateralToken.String() == l.LendingToken.String() {
|
|
return fmt.Errorf("invalid collateral %s", l.CollateralToken.Hex())
|
|
}
|
|
validCollateral := false
|
|
collateralList, _ := GetCollaterals(state, l.Relayer, l.LendingToken, l.Term)
|
|
for _, collateral := range collateralList {
|
|
if l.CollateralToken.String() == collateral.String() {
|
|
validCollateral = true
|
|
break
|
|
}
|
|
}
|
|
if !validCollateral {
|
|
return fmt.Errorf("invalid collateral %s", l.CollateralToken.Hex())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LendingItem) VerifyLendingInterest() error {
|
|
if l.Interest == nil || l.Interest.Sign() <= 0 {
|
|
return fmt.Errorf("VerifyLendingInterest: invalid interest. Interest: %v", l.Interest)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LendingItem) VerifyLendingQuantity() error {
|
|
if l.Quantity == nil || l.Quantity.Sign() <= 0 {
|
|
return fmt.Errorf("VerifyLendingQuantity: invalid quantity. Quantity: %v", l.Quantity)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LendingItem) VerifyLendingType() error {
|
|
if valid, ok := ValidInputLendingType[l.Type]; !ok && !valid {
|
|
return fmt.Errorf("VerifyLendingType: invalid lending type. Type: %s", l.Type)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LendingItem) VerifyLendingStatus() error {
|
|
if valid, ok := ValidInputLendingStatus[l.Status]; !ok && !valid {
|
|
return fmt.Errorf("VerifyLendingStatus: invalid lending status. Status: %s", l.Status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LendingItem) ComputeHash() common.Hash {
|
|
sha := sha3.NewKeccak256()
|
|
if l.Status == LendingStatusNew {
|
|
sha.Write(l.Relayer.Bytes())
|
|
sha.Write(l.UserAddress.Bytes())
|
|
sha.Write(l.LendingToken.Bytes())
|
|
sha.Write(l.CollateralToken.Bytes())
|
|
sha.Write([]byte(strconv.FormatInt(int64(l.Term), 10)))
|
|
sha.Write(common.BigToHash(l.Quantity).Bytes())
|
|
if l.Type == Limit {
|
|
if l.Interest != nil {
|
|
sha.Write(common.BigToHash(l.Interest).Bytes())
|
|
}
|
|
}
|
|
sha.Write(common.BigToHash(l.EncodedSide()).Bytes())
|
|
sha.Write([]byte(l.Status))
|
|
sha.Write([]byte(l.Type))
|
|
sha.Write(common.BigToHash(l.Nonce).Bytes())
|
|
} else if l.Status == LendingStatusCancelled {
|
|
sha.Write(l.Hash.Bytes())
|
|
sha.Write(common.BigToHash(l.Nonce).Bytes())
|
|
sha.Write(l.UserAddress.Bytes())
|
|
sha.Write(common.BigToHash(big.NewInt(int64(l.LendingId))).Bytes())
|
|
sha.Write([]byte(l.Status))
|
|
sha.Write(l.Relayer.Bytes())
|
|
sha.Write(l.LendingToken.Bytes())
|
|
sha.Write(l.CollateralToken.Bytes())
|
|
} else {
|
|
return common.Hash{}
|
|
}
|
|
|
|
return common.BytesToHash(sha.Sum(nil))
|
|
}
|
|
|
|
func (l *LendingItem) EncodedSide() *big.Int {
|
|
if l.Side == Borrowing {
|
|
return big.NewInt(0)
|
|
}
|
|
return big.NewInt(1)
|
|
}
|
|
|
|
//verify signatures
|
|
func (l *LendingItem) VerifyLendingSignature() error {
|
|
V := big.NewInt(int64(l.Signature.V))
|
|
R := l.Signature.R.Big()
|
|
S := l.Signature.S.Big()
|
|
|
|
//(nonce uint64, quantity *big.Int, interest, duration uint64, relayerAddress, userAddress, lendingToken, collateralToken common.Address, status, side, typeLending string, hash common.Hash, id uint64
|
|
tx := types.NewLendingTransaction(l.Nonce.Uint64(), l.Quantity, l.Interest.Uint64(), l.Term, l.Relayer, l.UserAddress,
|
|
l.LendingToken, l.CollateralToken, l.AutoTopUp, l.Status, l.Side, l.Type, l.Hash, l.LendingId, l.LendingTradeId, l.ExtraData)
|
|
tx.ImportSignature(V, R, S)
|
|
from, _ := types.LendingSender(types.LendingTxSigner{}, tx)
|
|
if from != tx.UserAddress() {
|
|
return fmt.Errorf("verify lending item: invalid signature")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func VerifyBalance(isXDCXLendingFork bool, statedb *state.StateDB, lendingStateDb *LendingStateDB,
|
|
orderType, side, status string, userAddress, relayer, lendingToken, collateralToken common.Address,
|
|
quantity, lendingTokenDecimal, collateralTokenDecimal, lendTokenXDCPrice, collateralPrice *big.Int,
|
|
term uint64, lendingId uint64, lendingTradeId uint64) error {
|
|
borrowingFeeRate := GetFee(statedb, relayer)
|
|
switch orderType {
|
|
case TopUp:
|
|
lendingBook := GetLendingOrderBookHash(lendingToken, term)
|
|
lendingTrade := lendingStateDb.GetLendingTrade(lendingBook, common.Uint64ToHash(lendingTradeId))
|
|
if lendingTrade == EmptyLendingTrade {
|
|
return fmt.Errorf("VerifyBalance: process deposit for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId)
|
|
}
|
|
tokenBalance := GetTokenBalance(lendingTrade.Borrower, lendingTrade.CollateralToken, statedb)
|
|
if tokenBalance.Cmp(quantity) < 0 {
|
|
return fmt.Errorf("VerifyBalance: not enough balance to process deposit for lendingTrade."+
|
|
"lendingTradeId: %v. Token: %s. ExpectedBalance: %s. ActualBalance: %s",
|
|
lendingTradeId, lendingTrade.CollateralToken.Hex(), quantity.String(), tokenBalance.String())
|
|
}
|
|
case Repay:
|
|
lendingBook := GetLendingOrderBookHash(lendingToken, term)
|
|
lendingTrade := lendingStateDb.GetLendingTrade(lendingBook, common.Uint64ToHash(lendingTradeId))
|
|
if lendingTrade == EmptyLendingTrade {
|
|
return fmt.Errorf("VerifyBalance: process payment for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId)
|
|
}
|
|
tokenBalance := GetTokenBalance(lendingTrade.Borrower, lendingTrade.LendingToken, statedb)
|
|
paymentBalance := CalculateTotalRepayValue(uint64(time.Now().Unix()), lendingTrade.LiquidationTime, lendingTrade.Term, lendingTrade.Interest, lendingTrade.Amount)
|
|
|
|
if tokenBalance.Cmp(paymentBalance) < 0 {
|
|
return fmt.Errorf("VerifyBalance: not enough balance to process payment for lendingTrade."+
|
|
"lendingTradeId: %v. Token: %s. ExpectedBalance: %s. ActualBalance: %s",
|
|
lendingTradeId, lendingTrade.LendingToken.Hex(), paymentBalance.String(), tokenBalance.String())
|
|
|
|
}
|
|
case Market, Limit:
|
|
switch side {
|
|
case Investing:
|
|
switch status {
|
|
case LendingStatusNew:
|
|
// make sure that investor have enough lendingToken
|
|
if balance := GetTokenBalance(userAddress, lendingToken, statedb); balance.Cmp(quantity) < 0 {
|
|
return fmt.Errorf("VerifyBalance: investor doesn't have enough lendingToken. User: %s. Token: %s. Expected: %v. Have: %v", userAddress.Hex(), lendingToken.Hex(), quantity, balance)
|
|
}
|
|
// check quantity: reject if it's too small
|
|
if lendTokenXDCPrice != nil && lendTokenXDCPrice.Sign() > 0 {
|
|
defaultFee := new(big.Int).Mul(quantity, new(big.Int).SetUint64(DefaultFeeRate))
|
|
defaultFee = new(big.Int).Div(defaultFee, common.XDCXBaseFee)
|
|
defaultFeeInXDC := common.Big0
|
|
if lendingToken.String() != common.XDCNativeAddress {
|
|
defaultFeeInXDC = new(big.Int).Mul(defaultFee, lendTokenXDCPrice)
|
|
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, lendingTokenDecimal)
|
|
} else {
|
|
defaultFeeInXDC = defaultFee
|
|
}
|
|
if defaultFeeInXDC.Cmp(common.RelayerLendingFee) <= 0 {
|
|
return ErrQuantityTradeTooSmall
|
|
}
|
|
|
|
}
|
|
|
|
case LendingStatusCancelled:
|
|
// in case of cancel, investor need to pay cancel fee in lendingToken
|
|
// make sure actualBalance >= cancel fee
|
|
lendingBook := GetLendingOrderBookHash(lendingToken, term)
|
|
item := lendingStateDb.GetLendingOrder(lendingBook, common.BigToHash(new(big.Int).SetUint64(lendingId)))
|
|
cancelFee := big.NewInt(0)
|
|
cancelFee = new(big.Int).Mul(item.Quantity, borrowingFeeRate)
|
|
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
|
|
|
|
actualBalance := GetTokenBalance(userAddress, lendingToken, statedb)
|
|
if actualBalance.Cmp(cancelFee) < 0 {
|
|
return fmt.Errorf("VerifyBalance: investor doesn't have enough lendingToken to pay cancel fee. LendingToken: %s . ExpectedBalance: %s . ActualBalance: %s",
|
|
lendingToken.Hex(), cancelFee.String(), actualBalance.String())
|
|
}
|
|
default:
|
|
return fmt.Errorf("VerifyBalance: invalid status of investing lendingitem. Status: %s", status)
|
|
}
|
|
return nil
|
|
case Borrowing:
|
|
switch status {
|
|
case LendingStatusNew:
|
|
depositRate, _, _ := GetCollateralDetail(statedb, collateralToken)
|
|
settleBalanceResult, err := GetSettleBalance(isXDCXLendingFork, Borrowing, lendTokenXDCPrice, collateralPrice, depositRate, borrowingFeeRate, lendingToken, collateralToken, lendingTokenDecimal, collateralTokenDecimal, quantity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expectedBalance := settleBalanceResult.CollateralLockedAmount
|
|
actualBalance := GetTokenBalance(userAddress, collateralToken, statedb)
|
|
if actualBalance.Cmp(expectedBalance) < 0 {
|
|
return fmt.Errorf("VerifyBalance: borrower doesn't have enough collateral token. User: %s. CollateralToken: %s . ExpectedBalance: %s . ActualBalance: %s",
|
|
userAddress.Hex(), collateralToken.Hex(), expectedBalance.String(), actualBalance.String())
|
|
}
|
|
case LendingStatusCancelled:
|
|
lendingBook := GetLendingOrderBookHash(lendingToken, term)
|
|
item := lendingStateDb.GetLendingOrder(lendingBook, common.BigToHash(new(big.Int).SetUint64(lendingId)))
|
|
cancelFee := big.NewInt(0)
|
|
// Fee == quantityToLend/base lend token decimal *price*borrowFee/LendingCancelFee
|
|
cancelFee = new(big.Int).Div(item.Quantity, collateralPrice)
|
|
cancelFee = new(big.Int).Mul(cancelFee, borrowingFeeRate)
|
|
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
|
|
actualBalance := GetTokenBalance(userAddress, collateralToken, statedb)
|
|
if actualBalance.Cmp(cancelFee) < 0 {
|
|
return fmt.Errorf("VerifyBalance: borrower doesn't have enough collateralToken to pay cancel fee. User: %s. CollateralToken: %s . ExpectedBalance: %s . ActualBalance: %s",
|
|
userAddress.Hex(), lendingToken.Hex(), cancelFee.String(), actualBalance.String())
|
|
}
|
|
default:
|
|
return fmt.Errorf("VerifyBalance: invalid status of borrowing lendingitem. Status: %s", status)
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("VerifyBalance: unknown lending side")
|
|
}
|
|
default:
|
|
return fmt.Errorf("VerifyBalance: unknown lending type")
|
|
}
|
|
return nil
|
|
}
|